From f7460afbb41c80d724fae3d21a1d647cbfed4c3e Mon Sep 17 00:00:00 2001 From: Christian Marillat Date: Tue, 28 Jan 2025 15:33:12 +0100 Subject: [PATCH] Import libtorrent-rasterbar_2.0.11.orig.tar.xz [dgit import orig libtorrent-rasterbar_2.0.11.orig.tar.xz] --- .cirrus.yml | 35 + .pre-commit-config.yaml | 213 + AUTHORS | 25 + CMakeLists.txt | 994 + COPYING | 28 + ChangeLog | 2235 + Jamfile | 1164 + Jamroot.jam | 0 LICENSE | 183 + LibtorrentRasterbarConfig.cmake.in | 9 + Makefile | 1149 + NEWS | 1 + README.rst | 64 + appveyor.yml | 107 + bindings/CMakeLists.txt | 3 + bindings/Makefile.am | 4 + bindings/README.txt | 3 + bindings/c/Jamfile | 38 + bindings/c/library.cpp | 611 + bindings/c/libtorrent.h | 296 + bindings/c/simple_client.c | 123 + bindings/python/CMakeLists.txt | 128 + bindings/python/Jamfile | 364 + bindings/python/client.py | 381 + bindings/python/dummy_data.py | 34 + bindings/python/make_torrent.py | 61 + bindings/python/setup.cfg | 5 + bindings/python/setup.py | 521 + bindings/python/setup.py.cmake.in | 15 + bindings/python/simple_client.py | 34 + bindings/python/src/alert.cpp | 1184 + bindings/python/src/boost_python.hpp | 45 + bindings/python/src/bytes.hpp | 22 + bindings/python/src/converters.cpp | 559 + bindings/python/src/create_torrent.cpp | 279 + bindings/python/src/datetime.cpp | 144 + bindings/python/src/entry.cpp | 185 + bindings/python/src/error_code.cpp | 240 + bindings/python/src/fingerprint.cpp | 34 + bindings/python/src/gil.hpp | 186 + bindings/python/src/info_hash.cpp | 41 + bindings/python/src/ip_filter.cpp | 49 + bindings/python/src/load_torrent.cpp | 55 + bindings/python/src/magnet_uri.cpp | 101 + bindings/python/src/module.cpp | 62 + bindings/python/src/optional.hpp | 31 + bindings/python/src/peer_info.cpp | 164 + bindings/python/src/session.cpp | 1377 + bindings/python/src/session_settings.cpp | 137 + bindings/python/src/sha1_hash.cpp | 46 + bindings/python/src/sha256_hash.cpp | 44 + bindings/python/src/string.cpp | 53 + bindings/python/src/torrent_handle.cpp | 679 + bindings/python/src/torrent_info.cpp | 514 + bindings/python/src/torrent_status.cpp | 143 + bindings/python/src/utility.cpp | 130 + bindings/python/src/version.cpp | 21 + bindings/python/test.py | 1357 + clang_tidy.jam | 105 + cmake/Modules/FindLibGcrypt.cmake | 127 + cmake/Modules/GeneratePkgConfig.cmake | 183 + .../generate-pkg-config.cmake.in | 60 + .../GeneratePkgConfig/pkg-config.cmake.in | 8 + .../target-compile-settings.cmake.in | 13 + cmake/Modules/LibtorrentMacros.cmake | 80 + cmake/Modules/ucm_flags.cmake | 118 + deps/try_signal/.travis.yml | 44 + deps/try_signal/CMakeLists.txt | 6 + deps/try_signal/Jamfile | 18 + deps/try_signal/LICENSE | 29 + deps/try_signal/README.rst | 53 + deps/try_signal/appveyor.yml | 50 + deps/try_signal/example.cpp | 31 + deps/try_signal/project-root.jam | 0 deps/try_signal/signal_error_code.cpp | 209 + deps/try_signal/signal_error_code.hpp | 162 + deps/try_signal/test.cpp | 47 + deps/try_signal/try_signal.cpp | 144 + deps/try_signal/try_signal.hpp | 49 + deps/try_signal/try_signal_mingw.hpp | 78 + deps/try_signal/try_signal_msvc.hpp | 61 + deps/try_signal/try_signal_posix.hpp | 82 + ...ozilla Libtorrent Report Public Report.pdf | Bin 0 -> 747116 bytes docs/build_version.sh | 14 + docs/building.rst | 781 + docs/client_test.rst | 49 + docs/contributing.rst | 63 + docs/dht_extensions.rst | 68 + docs/dht_rss.rst | 396 + docs/dht_sec.rst | 255 + docs/dht_store.rst | 480 + docs/examples.rst | 46 + docs/extension_protocol.rst | 324 + docs/features.rst | 323 + docs/filter-rst.py | 46 + docs/fuzzing.rst | 99 + docs/gen_reference_doc.py | 1557 + docs/gen_settings_doc.py | 136 + docs/gen_stats_doc.py | 133 + docs/gen_todo.py | 166 + docs/hacking.rst | 123 + docs/header.rst | 1 + docs/hunspell/en_US.aff | 466 + docs/hunspell/en_US.dic | 62155 ++++++++++++++++ docs/hunspell/libtorrent.dic | 638 + docs/img/bitcoin.png | Bin 0 -> 2611 bytes docs/img/complete_bit_prefixes.png | Bin 0 -> 7795 bytes docs/img/cwnd.png | Bin 0 -> 18998 bytes docs/img/delays.png | Bin 0 -> 10875 bytes docs/img/hacking.diagram | 30 + docs/img/hash_distribution.png | Bin 0 -> 9035 bytes docs/img/ip_id_v4.png | Bin 0 -> 5825 bytes docs/img/ip_id_v6.png | Bin 0 -> 6416 bytes docs/img/logo-bw.svg | 112 + docs/img/logo-color-text-vertical.svg | 191 + docs/img/logo-color-text.png | Bin 0 -> 17420 bytes docs/img/logo-color-text.svg | 161 + docs/img/logo-color.svg | 155 + docs/img/logo.svg | 1309 + docs/img/our_delay_base.png | Bin 0 -> 34999 bytes docs/img/pp-acceptance-medium.png | Bin 0 -> 1159 bytes docs/img/read_disk_buffers.diagram | 16 + docs/img/screenshot.png | Bin 0 -> 501532 bytes docs/img/storage.diagram | 35 + docs/img/troubleshooting.dot | 108 + docs/img/utp_stack.diagram | 9 + docs/img/write_disk_buffers.diagram | 15 + .../include/libtorrent/add_torrent_params.hpp | 414 + docs/include/libtorrent/address.hpp | 79 + docs/include/libtorrent/alert.hpp | 333 + docs/include/libtorrent/alert_types.hpp | 3126 + docs/include/libtorrent/announce_entry.hpp | 291 + docs/include/libtorrent/assert.hpp | 135 + docs/include/libtorrent/bdecode.hpp | 483 + docs/include/libtorrent/bencode.hpp | 408 + docs/include/libtorrent/bitfield.hpp | 335 + docs/include/libtorrent/bloom_filter.hpp | 80 + .../include/libtorrent/bt_peer_connection.hpp | 519 + docs/include/libtorrent/choker.hpp | 60 + docs/include/libtorrent/client_data.hpp | 104 + docs/include/libtorrent/close_reason.hpp | 156 + docs/include/libtorrent/config.hpp | 682 + docs/include/libtorrent/copy_ptr.hpp | 68 + docs/include/libtorrent/crc32c.hpp | 46 + docs/include/libtorrent/create_torrent.hpp | 577 + docs/include/libtorrent/deadline_timer.hpp | 56 + docs/include/libtorrent/debug.hpp | 288 + docs/include/libtorrent/disabled_disk_io.hpp | 55 + .../include/libtorrent/disk_buffer_holder.hpp | 112 + docs/include/libtorrent/disk_interface.hpp | 428 + docs/include/libtorrent/disk_observer.hpp | 52 + docs/include/libtorrent/download_priority.hpp | 57 + docs/include/libtorrent/entry.hpp | 334 + docs/include/libtorrent/enum_net.hpp | 234 + docs/include/libtorrent/error.hpp | 53 + docs/include/libtorrent/error_code.hpp | 602 + docs/include/libtorrent/extensions.hpp | 552 + docs/include/libtorrent/file.hpp | 128 + docs/include/libtorrent/file_layout.hpp | 44 + docs/include/libtorrent/file_storage.hpp | 744 + docs/include/libtorrent/fingerprint.hpp | 103 + docs/include/libtorrent/flags.hpp | 141 + docs/include/libtorrent/fwd.hpp | 323 + docs/include/libtorrent/gzip.hpp | 137 + docs/include/libtorrent/hash_picker.hpp | 246 + docs/include/libtorrent/hasher.hpp | 185 + docs/include/libtorrent/hex.hpp | 97 + docs/include/libtorrent/http_connection.hpp | 262 + docs/include/libtorrent/http_parser.hpp | 168 + .../libtorrent/http_seed_connection.hpp | 116 + docs/include/libtorrent/http_stream.hpp | 230 + .../libtorrent/http_tracker_connection.hpp | 97 + docs/include/libtorrent/i2p_stream.hpp | 642 + docs/include/libtorrent/identify_client.hpp | 86 + docs/include/libtorrent/index_range.hpp | 72 + docs/include/libtorrent/info_hash.hpp | 167 + docs/include/libtorrent/io.hpp | 189 + docs/include/libtorrent/io_context.hpp | 63 + docs/include/libtorrent/io_service.hpp | 47 + docs/include/libtorrent/ip_filter.hpp | 241 + docs/include/libtorrent/ip_voter.hpp | 136 + docs/include/libtorrent/libtorrent.hpp | 168 + docs/include/libtorrent/link.hpp | 83 + docs/include/libtorrent/load_torrent.hpp | 69 + docs/include/libtorrent/lsd.hpp | 97 + docs/include/libtorrent/magnet_uri.hpp | 111 + docs/include/libtorrent/mmap_disk_io.hpp | 57 + docs/include/libtorrent/mmap_storage.hpp | 232 + docs/include/libtorrent/natpmp.hpp | 220 + docs/include/libtorrent/netlink.hpp | 203 + docs/include/libtorrent/operations.hpp | 265 + docs/include/libtorrent/optional.hpp | 51 + docs/include/libtorrent/parse_url.hpp | 66 + docs/include/libtorrent/part_file.hpp | 138 + docs/include/libtorrent/pe_crypto.hpp | 162 + docs/include/libtorrent/peer.hpp | 76 + docs/include/libtorrent/peer_class.hpp | 159 + docs/include/libtorrent/peer_class_set.hpp | 70 + .../libtorrent/peer_class_type_filter.hpp | 146 + docs/include/libtorrent/peer_connection.hpp | 1262 + .../libtorrent/peer_connection_handle.hpp | 158 + .../libtorrent/peer_connection_interface.hpp | 88 + docs/include/libtorrent/peer_id.hpp | 43 + docs/include/libtorrent/peer_info.hpp | 481 + docs/include/libtorrent/peer_list.hpp | 271 + docs/include/libtorrent/peer_request.hpp | 59 + .../libtorrent/performance_counters.hpp | 500 + docs/include/libtorrent/pex_flags.hpp | 65 + docs/include/libtorrent/piece_block.hpp | 69 + .../libtorrent/piece_block_progress.hpp | 61 + docs/include/libtorrent/piece_picker.hpp | 925 + docs/include/libtorrent/platform_util.hpp | 14 + docs/include/libtorrent/portmap.hpp | 58 + docs/include/libtorrent/posix_disk_io.hpp | 55 + docs/include/libtorrent/proxy_base.hpp | 377 + docs/include/libtorrent/puff.hpp | 35 + docs/include/libtorrent/random.hpp | 85 + docs/include/libtorrent/read_resume_data.hpp | 71 + docs/include/libtorrent/request_blocks.hpp | 56 + docs/include/libtorrent/resolve_links.hpp | 97 + docs/include/libtorrent/session.hpp | 299 + docs/include/libtorrent/session_handle.hpp | 1137 + docs/include/libtorrent/session_params.hpp | 156 + docs/include/libtorrent/session_settings.hpp | 118 + docs/include/libtorrent/session_stats.hpp | 79 + docs/include/libtorrent/session_status.hpp | 238 + docs/include/libtorrent/session_types.hpp | 56 + docs/include/libtorrent/settings_pack.hpp | 2263 + docs/include/libtorrent/sha1.hpp | 43 + docs/include/libtorrent/sha1_hash.hpp | 316 + docs/include/libtorrent/sha256.hpp | 33 + docs/include/libtorrent/sliding_average.hpp | 93 + docs/include/libtorrent/socket.hpp | 298 + docs/include/libtorrent/socket_io.hpp | 146 + docs/include/libtorrent/socket_type.hpp | 65 + docs/include/libtorrent/socks5_stream.hpp | 561 + docs/include/libtorrent/span.hpp | 194 + docs/include/libtorrent/ssl.hpp | 186 + docs/include/libtorrent/ssl_stream.hpp | 355 + docs/include/libtorrent/stack_allocator.hpp | 96 + docs/include/libtorrent/stat.hpp | 287 + docs/include/libtorrent/stat_cache.hpp | 108 + docs/include/libtorrent/storage.hpp | 7 + docs/include/libtorrent/storage_defs.hpp | 159 + docs/include/libtorrent/string_util.hpp | 153 + docs/include/libtorrent/string_view.hpp | 109 + docs/include/libtorrent/tailqueue.hpp | 214 + docs/include/libtorrent/time.hpp | 92 + docs/include/libtorrent/torrent.hpp | 1814 + docs/include/libtorrent/torrent_flags.hpp | 334 + docs/include/libtorrent/torrent_handle.hpp | 1430 + docs/include/libtorrent/torrent_info.hpp | 792 + docs/include/libtorrent/torrent_peer.hpp | 312 + .../libtorrent/torrent_peer_allocator.hpp | 102 + docs/include/libtorrent/torrent_status.hpp | 610 + docs/include/libtorrent/tracker_manager.hpp | 413 + docs/include/libtorrent/truncate.hpp | 48 + docs/include/libtorrent/udp_socket.hpp | 174 + .../libtorrent/udp_tracker_connection.hpp | 134 + docs/include/libtorrent/union_endpoint.hpp | 115 + docs/include/libtorrent/units.hpp | 186 + docs/include/libtorrent/upnp.hpp | 406 + docs/include/libtorrent/utf8.hpp | 52 + docs/include/libtorrent/vector_utils.hpp | 59 + docs/include/libtorrent/version.hpp | 72 + .../libtorrent/web_connection_base.hpp | 137 + .../libtorrent/web_peer_connection.hpp | 147 + docs/include/libtorrent/write_resume_data.hpp | 84 + docs/include/libtorrent/xml_parse.hpp | 70 + docs/index.rst | 225 + docs/makefile | 231 + docs/manual.rst | 1325 + docs/plain_text_out.txt | 6862 ++ docs/projects.rst | 211 + docs/python_binding.rst | 263 + docs/security-audit.rst | 491 + docs/settings-ref.rst | 3546 + docs/streaming.rst | 147 + docs/style.css | 582 + docs/stylesheet | 287 + docs/template.txt | 42 + docs/template2.txt | 45 + docs/troubleshooting.rst | 20 + docs/troubleshooting_thumb.png | Bin 0 -> 45948 bytes docs/tuning.rst | 344 + docs/tutorial.rst | 305 + docs/udp_tracker_protocol.rst | 320 + docs/upgrade_to_1.2.rst | 164 + docs/upgrade_to_2.0.rst | 336 + docs/utp.rst | 345 + examples/CMakeLists.txt | 27 + examples/Jamfile | 62 + examples/Makefile.am | 35 + examples/bt-get.cpp | 82 + examples/bt-get2.cpp | 182 + examples/bt-get3.cpp | 217 + examples/check_files.cpp | 170 + examples/client_test.cpp | 2372 + examples/cmake/FindLibtorrentRasterbar.cmake | 190 + examples/connection_tester.cpp | 1217 + examples/custom_storage.cpp | 328 + examples/dump_bdecode.cpp | 120 + examples/dump_torrent.cpp | 198 + examples/magnet2torrent.cpp | 125 + examples/make_torrent.cpp | 319 + examples/print.cpp | 605 + examples/print.hpp | 56 + examples/run_benchmarks.py | 438 + examples/session_view.cpp | 162 + examples/session_view.hpp | 103 + examples/simple_client.cpp | 63 + examples/stats_counters.cpp | 50 + examples/torrent2magnet.cpp | 95 + examples/torrent_view.cpp | 570 + examples/torrent_view.hpp | 126 + examples/upnp_test.cpp | 108 + fuzzers/Jamfile | 102 + fuzzers/LICENSE | 29 + fuzzers/README.rst | 64 + fuzzers/main.cpp | 56 + fuzzers/minimize.sh | 18 + fuzzers/run.sh | 13 + fuzzers/src/add_torrent.cpp | 243 + fuzzers/src/base32decode.cpp | 40 + fuzzers/src/base32encode.cpp | 40 + fuzzers/src/base64encode.cpp | 40 + fuzzers/src/bdecode_node.cpp | 41 + fuzzers/src/convert_from_native.cpp | 40 + fuzzers/src/convert_to_native.cpp | 40 + fuzzers/src/dht_node.cpp | 102 + fuzzers/src/escape_path.cpp | 40 + fuzzers/src/escape_string.cpp | 40 + fuzzers/src/file_storage_add_file.cpp | 45 + fuzzers/src/gzip.cpp | 43 + fuzzers/src/http_parser.cpp | 60 + fuzzers/src/http_tracker.cpp | 49 + fuzzers/src/idna.cpp | 42 + fuzzers/src/parse_int.cpp | 41 + fuzzers/src/parse_magnet_uri.cpp | 45 + fuzzers/src/parse_url.cpp | 43 + fuzzers/src/peer_conn.cpp | 222 + fuzzers/src/read_bits.hpp | 65 + fuzzers/src/resume_data.cpp | 45 + fuzzers/src/sanitize_path.cpp | 41 + fuzzers/src/session_params.cpp | 44 + fuzzers/src/torrent_info.cpp | 41 + fuzzers/src/upnp.cpp | 45 + fuzzers/src/utf8_codepoint.cpp | 43 + fuzzers/src/utp.cpp | 70 + fuzzers/src/verify_encoding.cpp | 42 + fuzzers/tools/generate_initial_corpus.py | 214 + fuzzers/tools/unify_corpus_names.py | 24 + include/libtorrent/add_torrent_params.hpp | 414 + include/libtorrent/address.hpp | 79 + include/libtorrent/alert.hpp | 333 + include/libtorrent/alert_types.hpp | 3126 + include/libtorrent/announce_entry.hpp | 291 + include/libtorrent/assert.hpp | 135 + include/libtorrent/aux_/alert_manager.hpp | 180 + include/libtorrent/aux_/aligned_union.hpp | 65 + include/libtorrent/aux_/alloca.hpp | 117 + .../libtorrent/aux_/allocating_handler.hpp | 366 + include/libtorrent/aux_/announce_entry.hpp | 216 + include/libtorrent/aux_/apply_pad_files.hpp | 105 + include/libtorrent/aux_/array.hpp | 48 + include/libtorrent/aux_/bandwidth_limit.hpp | 105 + include/libtorrent/aux_/bandwidth_manager.hpp | 94 + .../libtorrent/aux_/bandwidth_queue_entry.hpp | 77 + include/libtorrent/aux_/bandwidth_socket.hpp | 51 + include/libtorrent/aux_/bind_to_device.hpp | 137 + include/libtorrent/aux_/buffer.hpp | 170 + include/libtorrent/aux_/byteswap.hpp | 100 + include/libtorrent/aux_/chained_buffer.hpp | 235 + include/libtorrent/aux_/container_wrapper.hpp | 136 + include/libtorrent/aux_/cpuid.hpp | 47 + include/libtorrent/aux_/deferred_handler.hpp | 81 + include/libtorrent/aux_/deprecated.hpp | 68 + include/libtorrent/aux_/deque.hpp | 47 + include/libtorrent/aux_/dev_random.hpp | 80 + include/libtorrent/aux_/directory.hpp | 79 + .../disable_deprecation_warnings_push.hpp | 51 + .../libtorrent/aux_/disable_warnings_pop.hpp | 43 + .../libtorrent/aux_/disable_warnings_push.hpp | 116 + include/libtorrent/aux_/disk_buffer_pool.hpp | 132 + .../libtorrent/aux_/disk_io_thread_pool.hpp | 150 + include/libtorrent/aux_/disk_job_fence.hpp | 118 + include/libtorrent/aux_/disk_job_pool.hpp | 75 + include/libtorrent/aux_/drive_info.hpp | 49 + include/libtorrent/aux_/ed25519.hpp | 18 + include/libtorrent/aux_/escape_string.hpp | 96 + include/libtorrent/aux_/export.hpp | 157 + include/libtorrent/aux_/ffs.hpp | 71 + include/libtorrent/aux_/file_descriptor.hpp | 59 + include/libtorrent/aux_/file_pointer.hpp | 78 + include/libtorrent/aux_/file_progress.hpp | 109 + include/libtorrent/aux_/file_view_pool.hpp | 248 + include/libtorrent/aux_/generate_peer_id.hpp | 48 + include/libtorrent/aux_/has_block.hpp | 57 + include/libtorrent/aux_/hasher512.hpp | 134 + .../libtorrent/aux_/heterogeneous_queue.hpp | 301 + .../aux_/instantiate_connection.hpp | 58 + include/libtorrent/aux_/invariant_check.hpp | 86 + include/libtorrent/aux_/io.hpp | 180 + include/libtorrent/aux_/ip_helpers.hpp | 68 + include/libtorrent/aux_/ip_notifier.hpp | 59 + include/libtorrent/aux_/keepalive.hpp | 102 + .../libtorrent/aux_/listen_socket_handle.hpp | 94 + include/libtorrent/aux_/lsd.hpp | 55 + include/libtorrent/aux_/merkle.hpp | 162 + include/libtorrent/aux_/merkle_tree.hpp | 226 + include/libtorrent/aux_/mmap.hpp | 143 + include/libtorrent/aux_/mmap_disk_job.hpp | 233 + include/libtorrent/aux_/netlink_utils.hpp | 99 + include/libtorrent/aux_/noexcept_movable.hpp | 112 + include/libtorrent/aux_/numeric_cast.hpp | 71 + include/libtorrent/aux_/open_mode.hpp | 63 + include/libtorrent/aux_/packet_buffer.hpp | 122 + include/libtorrent/aux_/packet_pool.hpp | 244 + include/libtorrent/aux_/path.hpp | 180 + .../libtorrent/aux_/polymorphic_socket.hpp | 188 + include/libtorrent/aux_/pool.hpp | 64 + include/libtorrent/aux_/portmap.hpp | 105 + include/libtorrent/aux_/posix_part_file.hpp | 139 + include/libtorrent/aux_/posix_storage.hpp | 122 + include/libtorrent/aux_/proxy_settings.hpp | 93 + include/libtorrent/aux_/range.hpp | 70 + include/libtorrent/aux_/receive_buffer.hpp | 230 + include/libtorrent/aux_/resolver.hpp | 106 + .../libtorrent/aux_/resolver_interface.hpp | 79 + include/libtorrent/aux_/route.h | 478 + include/libtorrent/aux_/scope_end.hpp | 64 + include/libtorrent/aux_/session_call.hpp | 52 + include/libtorrent/aux_/session_impl.hpp | 1398 + include/libtorrent/aux_/session_interface.hpp | 316 + include/libtorrent/aux_/session_settings.hpp | 190 + .../libtorrent/aux_/session_udp_sockets.hpp | 78 + include/libtorrent/aux_/set_socket_buffer.hpp | 92 + include/libtorrent/aux_/set_traffic_class.hpp | 60 + include/libtorrent/aux_/sha512.hpp | 33 + include/libtorrent/aux_/socket_type.hpp | 100 + include/libtorrent/aux_/storage_free_list.hpp | 73 + include/libtorrent/aux_/storage_utils.hpp | 130 + include/libtorrent/aux_/store_buffer.hpp | 150 + include/libtorrent/aux_/string_ptr.hpp | 76 + include/libtorrent/aux_/strview_less.hpp | 52 + include/libtorrent/aux_/suggest_piece.hpp | 128 + include/libtorrent/aux_/throw.hpp | 54 + include/libtorrent/aux_/time.hpp | 56 + include/libtorrent/aux_/timestamp_history.hpp | 88 + include/libtorrent/aux_/torrent_impl.hpp | 80 + include/libtorrent/aux_/torrent_list.hpp | 267 + include/libtorrent/aux_/unique_ptr.hpp | 66 + .../libtorrent/aux_/utp_socket_manager.hpp | 203 + include/libtorrent/aux_/utp_stream.hpp | 1063 + include/libtorrent/aux_/vector.hpp | 48 + include/libtorrent/aux_/win_cng.hpp | 208 + .../libtorrent/aux_/win_crypto_provider.hpp | 148 + include/libtorrent/aux_/win_file_handle.hpp | 63 + include/libtorrent/aux_/win_util.hpp | 108 + include/libtorrent/aux_/windows.hpp | 51 + include/libtorrent/bdecode.hpp | 483 + include/libtorrent/bencode.hpp | 408 + include/libtorrent/bitfield.hpp | 335 + include/libtorrent/bloom_filter.hpp | 80 + include/libtorrent/bt_peer_connection.hpp | 519 + include/libtorrent/choker.hpp | 60 + include/libtorrent/client_data.hpp | 104 + include/libtorrent/close_reason.hpp | 156 + include/libtorrent/config.hpp | 682 + include/libtorrent/copy_ptr.hpp | 68 + include/libtorrent/crc32c.hpp | 46 + include/libtorrent/create_torrent.hpp | 577 + include/libtorrent/deadline_timer.hpp | 56 + include/libtorrent/debug.hpp | 288 + include/libtorrent/disabled_disk_io.hpp | 55 + include/libtorrent/disk_buffer_holder.hpp | 112 + include/libtorrent/disk_interface.hpp | 428 + include/libtorrent/disk_observer.hpp | 52 + include/libtorrent/download_priority.hpp | 57 + include/libtorrent/entry.hpp | 334 + include/libtorrent/enum_net.hpp | 234 + include/libtorrent/error.hpp | 53 + include/libtorrent/error_code.hpp | 602 + include/libtorrent/extensions.hpp | 552 + include/libtorrent/extensions/smart_ban.hpp | 61 + include/libtorrent/extensions/ut_metadata.hpp | 63 + include/libtorrent/extensions/ut_pex.hpp | 64 + include/libtorrent/file.hpp | 128 + include/libtorrent/file_layout.hpp | 44 + include/libtorrent/file_storage.hpp | 744 + include/libtorrent/fingerprint.hpp | 103 + include/libtorrent/flags.hpp | 141 + include/libtorrent/fwd.hpp | 323 + include/libtorrent/gzip.hpp | 137 + include/libtorrent/hash_picker.hpp | 246 + include/libtorrent/hasher.hpp | 185 + include/libtorrent/hex.hpp | 97 + include/libtorrent/http_connection.hpp | 262 + include/libtorrent/http_parser.hpp | 168 + include/libtorrent/http_seed_connection.hpp | 116 + include/libtorrent/http_stream.hpp | 230 + .../libtorrent/http_tracker_connection.hpp | 97 + include/libtorrent/i2p_stream.hpp | 642 + include/libtorrent/identify_client.hpp | 86 + include/libtorrent/index_range.hpp | 72 + include/libtorrent/info_hash.hpp | 167 + include/libtorrent/io.hpp | 189 + include/libtorrent/io_context.hpp | 63 + include/libtorrent/io_service.hpp | 47 + include/libtorrent/ip_filter.hpp | 241 + include/libtorrent/ip_voter.hpp | 136 + .../libtorrent/kademlia/announce_flags.hpp | 63 + include/libtorrent/kademlia/dht_observer.hpp | 100 + include/libtorrent/kademlia/dht_settings.hpp | 184 + include/libtorrent/kademlia/dht_state.hpp | 81 + include/libtorrent/kademlia/dht_storage.hpp | 245 + include/libtorrent/kademlia/dht_tracker.hpp | 230 + .../libtorrent/kademlia/direct_request.hpp | 98 + include/libtorrent/kademlia/dos_blocker.hpp | 94 + include/libtorrent/kademlia/ed25519.hpp | 105 + include/libtorrent/kademlia/find_data.hpp | 91 + include/libtorrent/kademlia/get_item.hpp | 98 + include/libtorrent/kademlia/get_peers.hpp | 115 + include/libtorrent/kademlia/io.hpp | 65 + include/libtorrent/kademlia/item.hpp | 130 + include/libtorrent/kademlia/msg.hpp | 103 + include/libtorrent/kademlia/node.hpp | 305 + include/libtorrent/kademlia/node_entry.hpp | 97 + include/libtorrent/kademlia/node_id.hpp | 77 + include/libtorrent/kademlia/observer.hpp | 171 + include/libtorrent/kademlia/put_data.hpp | 93 + include/libtorrent/kademlia/refresh.hpp | 67 + include/libtorrent/kademlia/routing_table.hpp | 345 + include/libtorrent/kademlia/rpc_manager.hpp | 147 + .../libtorrent/kademlia/sample_infohashes.hpp | 84 + .../kademlia/traversal_algorithm.hpp | 189 + include/libtorrent/kademlia/types.hpp | 100 + include/libtorrent/libtorrent.hpp | 168 + include/libtorrent/link.hpp | 83 + include/libtorrent/load_torrent.hpp | 69 + include/libtorrent/lsd.hpp | 97 + include/libtorrent/magnet_uri.hpp | 111 + include/libtorrent/mmap_disk_io.hpp | 57 + include/libtorrent/mmap_storage.hpp | 232 + include/libtorrent/natpmp.hpp | 220 + include/libtorrent/netlink.hpp | 203 + include/libtorrent/operations.hpp | 265 + include/libtorrent/optional.hpp | 51 + include/libtorrent/parse_url.hpp | 66 + include/libtorrent/part_file.hpp | 138 + include/libtorrent/pe_crypto.hpp | 162 + include/libtorrent/peer.hpp | 76 + include/libtorrent/peer_class.hpp | 159 + include/libtorrent/peer_class_set.hpp | 70 + include/libtorrent/peer_class_type_filter.hpp | 146 + include/libtorrent/peer_connection.hpp | 1262 + include/libtorrent/peer_connection_handle.hpp | 158 + .../libtorrent/peer_connection_interface.hpp | 88 + include/libtorrent/peer_id.hpp | 43 + include/libtorrent/peer_info.hpp | 481 + include/libtorrent/peer_list.hpp | 271 + include/libtorrent/peer_request.hpp | 59 + include/libtorrent/performance_counters.hpp | 500 + include/libtorrent/pex_flags.hpp | 65 + include/libtorrent/piece_block.hpp | 69 + include/libtorrent/piece_block_progress.hpp | 61 + include/libtorrent/piece_picker.hpp | 925 + include/libtorrent/platform_util.hpp | 14 + include/libtorrent/portmap.hpp | 58 + include/libtorrent/posix_disk_io.hpp | 55 + include/libtorrent/proxy_base.hpp | 377 + include/libtorrent/puff.hpp | 35 + include/libtorrent/random.hpp | 85 + include/libtorrent/read_resume_data.hpp | 71 + include/libtorrent/request_blocks.hpp | 56 + include/libtorrent/resolve_links.hpp | 97 + include/libtorrent/session.hpp | 299 + include/libtorrent/session_handle.hpp | 1137 + include/libtorrent/session_params.hpp | 156 + include/libtorrent/session_settings.hpp | 118 + include/libtorrent/session_stats.hpp | 79 + include/libtorrent/session_status.hpp | 238 + include/libtorrent/session_types.hpp | 56 + include/libtorrent/settings_pack.hpp | 2263 + include/libtorrent/sha1.hpp | 43 + include/libtorrent/sha1_hash.hpp | 316 + include/libtorrent/sha256.hpp | 33 + include/libtorrent/sliding_average.hpp | 93 + include/libtorrent/socket.hpp | 298 + include/libtorrent/socket_io.hpp | 146 + include/libtorrent/socket_type.hpp | 65 + include/libtorrent/socks5_stream.hpp | 561 + include/libtorrent/span.hpp | 194 + include/libtorrent/ssl.hpp | 186 + include/libtorrent/ssl_stream.hpp | 355 + include/libtorrent/stack_allocator.hpp | 96 + include/libtorrent/stat.hpp | 287 + include/libtorrent/stat_cache.hpp | 108 + include/libtorrent/storage.hpp | 7 + include/libtorrent/storage_defs.hpp | 159 + include/libtorrent/string_util.hpp | 153 + include/libtorrent/string_view.hpp | 109 + include/libtorrent/tailqueue.hpp | 214 + include/libtorrent/time.hpp | 92 + include/libtorrent/torrent.hpp | 1814 + include/libtorrent/torrent_flags.hpp | 334 + include/libtorrent/torrent_handle.hpp | 1430 + include/libtorrent/torrent_info.hpp | 792 + include/libtorrent/torrent_peer.hpp | 312 + include/libtorrent/torrent_peer_allocator.hpp | 102 + include/libtorrent/torrent_status.hpp | 610 + include/libtorrent/tracker_manager.hpp | 413 + include/libtorrent/truncate.hpp | 48 + include/libtorrent/udp_socket.hpp | 174 + include/libtorrent/udp_tracker_connection.hpp | 134 + include/libtorrent/union_endpoint.hpp | 115 + include/libtorrent/units.hpp | 186 + include/libtorrent/upnp.hpp | 406 + include/libtorrent/utf8.hpp | 52 + include/libtorrent/vector_utils.hpp | 59 + include/libtorrent/version.hpp | 72 + include/libtorrent/web_connection_base.hpp | 137 + include/libtorrent/web_peer_connection.hpp | 147 + include/libtorrent/write_resume_data.hpp | 84 + include/libtorrent/xml_parse.hpp | 70 + project-config.jam | 0 pyproject.toml | 86 + setup.cfg | 15 + setup.py | 6 + simulation/Jamfile | 68 + simulation/create_torrent.cpp | 73 + simulation/create_torrent.hpp | 45 + simulation/disk_io.cpp | 728 + simulation/disk_io.hpp | 131 + simulation/fake_peer.hpp | 473 + simulation/libsimulator/.travis.yml | 65 + simulation/libsimulator/CMakeLists.txt | 57 + simulation/libsimulator/Jamfile | 129 + simulation/libsimulator/Jamroot.jam | 0 simulation/libsimulator/LICENSE | 675 + simulation/libsimulator/README.rst | 202 + simulation/libsimulator/appveyor.yml | 27 + .../libsimulator/include/simulator/chrono.hpp | 88 + .../libsimulator/include/simulator/config.hpp | 51 + .../include/simulator/function.hpp | 220 + .../include/simulator/handler_allocator.hpp | 81 + .../include/simulator/http_proxy.hpp | 103 + .../include/simulator/http_server.hpp | 122 + .../include/simulator/mallocator.hpp | 69 + .../libsimulator/include/simulator/nat.hpp | 51 + .../include/simulator/noexcept_movable.hpp | 56 + .../libsimulator/include/simulator/packet.hpp | 94 + .../libsimulator/include/simulator/pcap.hpp | 42 + .../include/simulator/pop_warnings.hpp | 44 + .../include/simulator/push_warnings.hpp | 89 + .../libsimulator/include/simulator/queue.hpp | 100 + .../include/simulator/simulator.hpp | 1358 + .../libsimulator/include/simulator/sink.hpp | 47 + .../include/simulator/sink_forwarder.hpp | 43 + .../include/simulator/socks_server.hpp | 202 + .../libsimulator/include/simulator/utils.hpp | 47 + simulation/libsimulator/src/acceptor.cpp | 379 + .../libsimulator/src/default_config.cpp | 106 + .../src/high_resolution_clock.cpp | 51 + .../src/high_resolution_timer.cpp | 141 + simulation/libsimulator/src/http_proxy.cpp | 362 + simulation/libsimulator/src/http_server.cpp | 393 + simulation/libsimulator/src/io_service.cpp | 257 + simulation/libsimulator/src/nat.cpp | 47 + simulation/libsimulator/src/pcap.cpp | 204 + simulation/libsimulator/src/queue.cpp | 142 + simulation/libsimulator/src/resolver.cpp | 143 + simulation/libsimulator/src/simulation.cpp | 354 + simulation/libsimulator/src/simulator.cpp | 262 + .../libsimulator/src/sink_forwarder.cpp | 45 + simulation/libsimulator/src/socks_server.cpp | 1033 + simulation/libsimulator/src/tcp_socket.cpp | 884 + simulation/libsimulator/src/udp_socket.cpp | 492 + simulation/libsimulator/test/acceptor.cpp | 183 + simulation/libsimulator/test/catch.hpp | 14057 ++++ simulation/libsimulator/test/main.cpp | 11638 +++ simulation/libsimulator/test/multi_accept.cpp | 129 + simulation/libsimulator/test/multi_homed.cpp | 212 + simulation/libsimulator/test/null_buffers.cpp | 200 + .../libsimulator/test/parse_request.cpp | 76 + simulation/libsimulator/test/resolver.cpp | 192 + simulation/libsimulator/test/timer.cpp | 91 + simulation/libsimulator/test/udp_socket.cpp | 114 + simulation/libsimulator/user-config.jam | 3 + simulation/make_proxy_settings.hpp | 57 + simulation/setup_dht.cpp | 318 + simulation/setup_dht.hpp | 74 + simulation/setup_swarm.cpp | 445 + simulation/setup_swarm.hpp | 104 + simulation/test_auto_manage.cpp | 923 + simulation/test_checking.cpp | 296 + simulation/test_dht.cpp | 331 + simulation/test_dht_bootstrap.cpp | 112 + simulation/test_dht_rate_limit.cpp | 271 + simulation/test_dht_storage.cpp | 219 + simulation/test_error_handling.cpp | 250 + simulation/test_fast_extensions.cpp | 360 + simulation/test_file_pool.cpp | 138 + simulation/test_http_connection.cpp | 663 + simulation/test_ip_filter.cpp | 239 + simulation/test_metadata_extension.cpp | 250 + simulation/test_optimistic_unchoke.cpp | 173 + simulation/test_pause.cpp | 335 + simulation/test_pe_crypto.cpp | 197 + simulation/test_peer_connection.cpp | 309 + simulation/test_save_resume.cpp | 88 + simulation/test_session.cpp | 254 + simulation/test_socks5.cpp | 324 + simulation/test_super_seeding.cpp | 78 + simulation/test_swarm.cpp | 1032 + simulation/test_thread_pool.cpp | 216 + simulation/test_timeout.cpp | 308 + simulation/test_torrent_status.cpp | 589 + simulation/test_tracker.cpp | 2118 + simulation/test_transfer.cpp | 555 + .../test_transfer_full_invalid_files.cpp | 43 + simulation/test_transfer_no_files.cpp | 43 + .../test_transfer_partial_valid_files.cpp | 43 + simulation/test_utp.cpp | 262 + simulation/test_v2.cpp | 343 + simulation/test_web_seed.cpp | 869 + simulation/transfer_sim.cpp | 202 + simulation/transfer_sim.hpp | 334 + simulation/utils.cpp | 256 + simulation/utils.hpp | 108 + src/add_torrent_params.cpp | 99 + src/alert.cpp | 3297 + src/alert_manager.cpp | 148 + src/announce_entry.cpp | 289 + src/assert.cpp | 412 + src/bandwidth_limit.cpp | 110 + src/bandwidth_manager.cpp | 225 + src/bandwidth_queue_entry.cpp | 81 + src/bdecode.cpp | 1187 + src/bitfield.cpp | 250 + src/bloom_filter.cpp | 77 + src/bt_peer_connection.cpp | 3830 + src/chained_buffer.cpp | 174 + src/choker.cpp | 314 + src/close_reason.cpp | 167 + src/copy_file.cpp | 457 + src/cpuid.cpp | 162 + src/crc32c.cpp | 151 + src/create_torrent.cpp | 1017 + src/directory.cpp | 126 + src/disabled_disk_io.cpp | 206 + src/disk_buffer_holder.cpp | 65 + src/disk_buffer_pool.cpp | 239 + src/disk_interface.cpp | 44 + src/disk_io_thread_pool.cpp | 209 + src/disk_job_fence.cpp | 221 + src/disk_job_pool.cpp | 107 + src/drive_info.cpp | 281 + src/ed25519/LICENSE | 22 + src/ed25519/add_scalar.cpp | 75 + src/ed25519/fe.cpp | 1490 + src/ed25519/fe.h | 41 + src/ed25519/fixedint.h | 10 + src/ed25519/ge.cpp | 472 + src/ed25519/ge.h | 74 + src/ed25519/hasher512.cpp | 152 + src/ed25519/key_exchange.cpp | 88 + src/ed25519/keypair.cpp | 24 + src/ed25519/precomp_data.h | 1392 + src/ed25519/sc.cpp | 816 + src/ed25519/sc.h | 13 + src/ed25519/sha512.cpp | 291 + src/ed25519/sign.cpp | 37 + src/ed25519/verify.cpp | 84 + src/entry.cpp | 775 + src/enum_net.cpp | 1491 + src/error_code.cpp | 371 + src/escape_string.cpp | 625 + src/ffs.cpp | 185 + src/file.cpp | 640 + src/file_progress.cpp | 215 + src/file_storage.cpp | 1606 + src/file_view_pool.cpp | 483 + src/fingerprint.cpp | 103 + src/generate_peer_id.cpp | 56 + src/gzip.cpp | 268 + src/hash_picker.cpp | 429 + src/hasher.cpp | 283 + src/hex.cpp | 106 + src/http_connection.cpp | 911 + src/http_parser.cpp | 648 + src/http_seed_connection.cpp | 504 + src/http_tracker_connection.cpp | 686 + src/i2p_stream.cpp | 128 + src/identify_client.cpp | 441 + src/instantiate_connection.cpp | 154 + src/ip_filter.cpp | 285 + src/ip_helpers.cpp | 130 + src/ip_notifier.cpp | 479 + src/ip_voter.cpp | 194 + src/kademlia/dht_settings.cpp | 116 + src/kademlia/dht_state.cpp | 135 + src/kademlia/dht_storage.cpp | 634 + src/kademlia/dht_tracker.cpp | 737 + src/kademlia/dos_blocker.cpp | 114 + src/kademlia/ed25519.cpp | 127 + src/kademlia/find_data.cpp | 197 + src/kademlia/get_item.cpp | 222 + src/kademlia/get_peers.cpp | 311 + src/kademlia/item.cpp | 212 + src/kademlia/msg.cpp | 138 + src/kademlia/node.cpp | 1255 + src/kademlia/node_entry.cpp | 65 + src/kademlia/node_id.cpp | 222 + src/kademlia/put_data.cpp | 118 + src/kademlia/refresh.cpp | 107 + src/kademlia/routing_table.cpp | 1239 + src/kademlia/rpc_manager.cpp | 534 + src/kademlia/sample_infohashes.cpp | 161 + src/kademlia/traversal_algorithm.cpp | 757 + src/listen_socket_handle.cpp | 83 + src/load_torrent.cpp | 147 + src/lsd.cpp | 339 + src/magnet_uri.cpp | 516 + src/merkle.cpp | 454 + src/merkle_tree.cpp | 1067 + src/mmap.cpp | 334 + src/mmap_disk_io.cpp | 1817 + src/mmap_disk_job.cpp | 136 + src/mmap_storage.cpp | 945 + src/natpmp.cpp | 929 + src/packet_buffer.cpp | 195 + src/parse_url.cpp | 216 + src/part_file.cpp | 453 + src/path.cpp | 954 + src/pe_crypto.cpp | 420 + src/peer_class.cpp | 126 + src/peer_class_set.cpp | 73 + src/peer_connection.cpp | 6887 ++ src/peer_connection_handle.cpp | 357 + src/peer_info.cpp | 112 + src/peer_list.cpp | 1429 + src/performance_counters.cpp | 160 + src/piece_picker.cpp | 3966 + src/platform_util.cpp | 132 + src/posix_disk_io.cpp | 403 + src/posix_part_file.cpp | 472 + src/posix_storage.cpp | 535 + src/proxy_base.cpp | 54 + src/proxy_settings.cpp | 67 + src/puff.cpp | 844 + src/random.cpp | 198 + src/read_resume_data.cpp | 523 + src/receive_buffer.cpp | 341 + src/request_blocks.cpp | 307 + src/resolve_links.cpp | 188 + src/resolver.cpp | 216 + src/session.cpp | 552 + src/session_call.cpp | 82 + src/session_handle.cpp | 1295 + src/session_impl.cpp | 7370 ++ src/session_params.cpp | 295 + src/session_settings.cpp | 65 + src/session_stats.cpp | 599 + src/settings_pack.cpp | 868 + src/sha1.cpp | 329 + src/sha1_hash.cpp | 179 + src/sha256.cpp | 180 + src/smart_ban.cpp | 329 + src/socket_io.cpp | 167 + src/socket_type.cpp | 269 + src/socks5_stream.cpp | 81 + src/ssl.cpp | 214 + src/stack_allocator.cpp | 143 + src/stat.cpp | 46 + src/stat_cache.cpp | 145 + src/storage_utils.cpp | 769 + src/string_util.cpp | 412 + src/time.cpp | 82 + src/timestamp_history.cpp | 108 + src/torrent.cpp | 12336 +++ src/torrent_handle.cpp | 976 + src/torrent_info.cpp | 1902 + src/torrent_peer.cpp | 299 + src/torrent_peer_allocator.cpp | 117 + src/torrent_status.cpp | 55 + src/tracker_manager.cpp | 496 + src/truncate.cpp | 177 + src/udp_socket.cpp | 1073 + src/udp_tracker_connection.cpp | 780 + src/upnp.cpp | 1681 + src/ut_metadata.cpp | 640 + src/ut_pex.cpp | 644 + src/utf8.cpp | 178 + src/utp_socket_manager.cpp | 344 + src/utp_stream.cpp | 3575 + src/version.cpp | 42 + src/web_connection_base.cpp | 213 + src/web_peer_connection.cpp | 1246 + src/write_resume_data.cpp | 439 + src/xml_parse.cpp | 171 + tags | 11598 +++ test/CMakeLists.txt | 62 + test/Jamfile | 320 + test/bittorrent_peer.cpp | 563 + test/bittorrent_peer.hpp | 110 + test/broadcast_socket.cpp | 249 + test/broadcast_socket.hpp | 143 + test/corrupt.gz | Bin 0 -> 296 bytes test/dht_server.cpp | 183 + test/dht_server.hpp | 42 + test/enum_if.cpp | 134 + test/http_proxy.py | 550 + test/invalid1.gz | Bin 0 -> 27 bytes test/main.cpp | 582 + test/make_torrent.cpp | 214 + test/make_torrent.hpp | 69 + test/mutable_test_torrents/test1.torrent | 3 + .../test1_pad_files.torrent | 2 + .../test1_single.torrent | 2 + .../test1_single_padded.torrent | 2 + test/mutable_test_torrents/test2.torrent | 1 + .../test2_pad_files.torrent | Bin 0 -> 499 bytes test/mutable_test_torrents/test3.torrent | Bin 0 -> 366 bytes .../test3_pad_files.torrent | 4 + test/peer_server.cpp | 169 + test/peer_server.hpp | 47 + test/print_alerts.cpp | 72 + test/print_alerts.hpp | 43 + test/root1.xml | 135 + test/root2.xml | 1 + test/root3.xml | 1 + test/settings.cpp | 93 + test/settings.hpp | 37 + test/setup_transfer.cpp | 1240 + test/setup_transfer.hpp | 129 + test/socks.py | 317 + test/ssl/dhparams.pem | 13 + test/ssl/invalid_peer_certificate.pem | 79 + test/ssl/invalid_peer_private_key.pem | 30 + test/ssl/peer_certificate.pem | 79 + test/ssl/peer_private_key.pem | 30 + test/ssl/regenerate_test_certificate.sh | 37 + test/ssl/root_ca_cert.pem | 79 + test/ssl/root_ca_private.pem | 30 + test/ssl/server.pem | 84 + test/swarm_suite.cpp | 245 + test/swarm_suite.hpp | 52 + test/test.cpp | 102 + test/test.hpp | 201 + test/test_add_torrent.cpp | 305 + test/test_alert_manager.cpp | 364 + test/test_alert_types.cpp | 361 + test/test_alloca.cpp | 82 + test/test_apply_pad.cpp | 175 + test/test_auto_unchoke.cpp | 165 + test/test_bandwidth_limiter.cpp | 519 + test/test_bdecode.cpp | 1367 + test/test_bencoding.cpp | 274 + test/test_bitfield.cpp | 431 + test/test_bloom_filter.cpp | 137 + test/test_buffer.cpp | 311 + test/test_checking.cpp | 435 + test/test_copy_file.cpp | 178 + test/test_crc32.cpp | 91 + test/test_create_torrent.cpp | 880 + test/test_dht.cpp | 4090 + test/test_dht_storage.cpp | 511 + test/test_direct_dht.cpp | 150 + test/test_dos_blocker.cpp | 105 + test/test_ed25519.cpp | 290 + test/test_enum_net.cpp | 282 + test/test_fast_extension.cpp | 1136 + test/test_fence.cpp | 233 + test/test_ffs.cpp | 139 + test/test_file.cpp | 618 + test/test_file_progress.cpp | 180 + test/test_file_storage.cpp | 1209 + test/test_flags.cpp | 235 + test/test_generate_peer_id.cpp | 56 + test/test_gzip.cpp | 101 + test/test_hash_picker.cpp | 638 + test/test_hasher.cpp | 146 + test/test_hasher512.cpp | 92 + test/test_heterogeneous_queue.cpp | 339 + test/test_http_connection.cpp | 250 + test/test_http_parser.cpp | 914 + test/test_identify_client.cpp | 48 + test/test_info_hash.cpp | 164 + test/test_io.cpp | 291 + test/test_ip_filter.cpp | 260 + test/test_ip_voter.cpp | 223 + test/test_listen_socket.cpp | 538 + test/test_lsd.cpp | 131 + test/test_magnet.cpp | 843 + test/test_merkle.cpp | 1317 + test/test_merkle_tree.cpp | 941 + test/test_mmap.cpp | 122 + test/test_natpmp.cpp | 169 + test/test_packet_buffer.cpp | 186 + test/test_part_file.cpp | 254 + test/test_pe_crypto.cpp | 163 + test/test_peer_classes.cpp | 151 + test/test_peer_list.cpp | 1249 + test/test_peer_priority.cpp | 140 + test/test_piece_picker.cpp | 2871 + test/test_primitives.cpp | 256 + test/test_priority.cpp | 713 + test/test_privacy.cpp | 341 + test/test_read_piece.cpp | 161 + test/test_read_resume.cpp | 540 + test/test_receive_buffer.cpp | 258 + test/test_recheck.cpp | 121 + test/test_remap_files.cpp | 220 + test/test_remove_torrent.cpp | 239 + test/test_resolve_links.cpp | 234 + test/test_resume.cpp | 1857 + test/test_session.cpp | 599 + test/test_session_params.cpp | 287 + test/test_settings_pack.cpp | 327 + test/test_sha1_hash.cpp | 147 + test/test_similar_torrent.cpp | 347 + test/test_sliding_average.cpp | 118 + test/test_socket_io.cpp | 218 + test/test_span.cpp | 153 + test/test_ssl.cpp | 643 + test/test_stack_allocator.cpp | 142 + test/test_stat_cache.cpp | 88 + test/test_storage.cpp | 1880 + test/test_store_buffer.cpp | 186 + test/test_string.cpp | 602 + test/test_tailqueue.cpp | 170 + test/test_threads.cpp | 129 + test/test_time.cpp | 147 + test/test_time_critical.cpp | 61 + test/test_timestamp_history.cpp | 57 + test/test_torrent.cpp | 884 + test/test_torrent_info.cpp | 1530 + test/test_torrent_list.cpp | 249 + test/test_torrents/absolute_filename.torrent | 1 + test/test_torrents/backslash_path.torrent | 1 + test/test_torrents/bad_name.torrent | Bin 0 -> 374 bytes test/test_torrents/base.torrent | 1 + test/test_torrents/collection.torrent | 1 + test/test_torrents/collection2.torrent | 1 + test/test_torrents/creation_date.torrent | 1 + test/test_torrents/dht_nodes.torrent | 1 + test/test_torrents/duplicate_files.torrent | 1 + .../test_torrents/duplicate_web_seeds.torrent | 1 + test/test_torrents/empty-files-1.torrent | Bin 0 -> 927 bytes test/test_torrents/empty-files-2.torrent | Bin 0 -> 927 bytes test/test_torrents/empty-files-3.torrent | Bin 0 -> 927 bytes test/test_torrents/empty-files-4.torrent | Bin 0 -> 927 bytes test/test_torrents/empty-files-5.torrent | Bin 0 -> 927 bytes test/test_torrents/empty_httpseed.torrent | 1 + test/test_torrents/empty_path.torrent | 1 + test/test_torrents/empty_path_multi.torrent | 1 + test/test_torrents/hidden_parent_path.torrent | 1 + test/test_torrents/httpseed.torrent | 1 + test/test_torrents/invalid_file_size.torrent | 1 + test/test_torrents/invalid_filename.torrent | 1 + test/test_torrents/invalid_filename2.torrent | 1 + test/test_torrents/invalid_info.torrent | 1 + test/test_torrents/invalid_name.torrent | 1 + test/test_torrents/invalid_name2.torrent | 1 + test/test_torrents/invalid_name3.torrent | 1 + test/test_torrents/invalid_path_list.torrent | 1 + test/test_torrents/invalid_piece_len.torrent | 1 + test/test_torrents/invalid_pieces.torrent | 1 + test/test_torrents/invalid_symlink.torrent | 1 + test/test_torrents/large.torrent | Bin 0 -> 100144 bytes test/test_torrents/large_piece_size.torrent | 1 + test/test_torrents/long_name.torrent | 1 + test/test_torrents/many_pieces.torrent | 1 + test/test_torrents/missing_path_list.torrent | 1 + test/test_torrents/missing_piece_len.torrent | 1 + test/test_torrents/negative_file_size.torrent | 1 + test/test_torrents/negative_piece_len.torrent | 1 + test/test_torrents/negative_size.torrent | 1 + test/test_torrents/no_creation_date.torrent | 1 + test/test_torrents/no_files.torrent | 1 + test/test_torrents/no_name.torrent | 1 + .../overlapping_symlinks.torrent | Bin 0 -> 6119 bytes test/test_torrents/pad_file.torrent | 1 + test/test_torrents/pad_file_no_path.torrent | 1 + test/test_torrents/parent_path.torrent | 1 + test/test_torrents/sample.torrent | Bin 0 -> 504 bytes test/test_torrents/similar.torrent | 1 + test/test_torrents/similar2.torrent | 1 + test/test_torrents/single_multi_file.torrent | 1 + test/test_torrents/slash_path.torrent | 1 + test/test_torrents/slash_path2.torrent | 1 + test/test_torrents/slash_path3.torrent | 1 + test/test_torrents/string.torrent | 1 + test/test_torrents/symlink1.torrent | 1 + test/test_torrents/symlink2.torrent | 1 + test/test_torrents/symlink_zero_size.torrent | 1 + test/test_torrents/unaligned_pieces.torrent | 1 + test/test_torrents/unordered.torrent | 1 + test/test_torrents/url_list.torrent | 1 + test/test_torrents/url_list2.torrent | 1 + test/test_torrents/url_list3.torrent | 1 + test/test_torrents/url_seed.torrent | 1 + test/test_torrents/url_seed_multi.torrent | 1 + .../url_seed_multi_single_file.torrent | 1 + .../url_seed_multi_space.torrent | 1 + .../url_seed_multi_space_nolist.torrent | 1 + test/test_torrents/v2.torrent | 2 + .../v2_bad_file_alignment.torrent | Bin 0 -> 156905 bytes test/test_torrents/v2_deep_recursion.torrent | 1 + test/test_torrents/v2_empty_file.torrent | Bin 0 -> 13529 bytes .../v2_hybrid-missing-tailpad.torrent | Bin 0 -> 91581 bytes test/test_torrents/v2_hybrid.torrent | Bin 0 -> 91576 bytes .../v2_incomplete_piece_layer.torrent | Bin 0 -> 64532 bytes test/test_torrents/v2_invalid_file.torrent | 2 + .../test_torrents/v2_invalid_filename.torrent | 17 + .../test_torrents/v2_invalid_pad_file.torrent | 17 + .../v2_invalid_piece_layer.torrent | 1 + .../v2_invalid_piece_layer_root.torrent | 17 + .../v2_invalid_piece_layer_size.torrent | 33 + .../v2_invalid_root_hash.torrent | Bin 0 -> 3134 bytes test/test_torrents/v2_large_file.torrent | 17 + test/test_torrents/v2_large_offset.torrent | 17 + .../v2_mismatching_metadata.torrent | 17 + ..._missing_file_root_invalid_symlink.torrent | Bin 0 -> 96516 bytes test/test_torrents/v2_multipiece_file.torrent | 17 + test/test_torrents/v2_multiple_files.torrent | Bin 0 -> 96605 bytes test/test_torrents/v2_no_piece_layers.torrent | 1 + test/test_torrents/v2_no_power2_piece.torrent | 1 + .../v2_non_multiple_piece_layer.torrent | 17 + test/test_torrents/v2_only.torrent | 17 + .../test_torrents/v2_overlong_integer.torrent | 2 + .../v2_piece_layer_invalid_file_hash.torrent | 17 + test/test_torrents/v2_piece_size.torrent | 1 + test/test_torrents/v2_symlinks.torrent | Bin 0 -> 3290 bytes .../v2_unknown_piece_layer_entry.torrent | 33 + test/test_torrents/v2_unordered_files.torrent | 2 + test/test_torrents/v2_zero_root.torrent | Bin 0 -> 767 bytes test/test_torrents/v2_zero_root_small.torrent | Bin 0 -> 214 bytes test/test_torrents/whitespace_url.torrent | 1 + test/test_torrents/zero.torrent | 1 + test/test_torrents/zero2.torrent | 1 + test/test_tracker.cpp | 734 + test/test_transfer.cpp | 433 + test/test_truncate.cpp | 107 + test/test_upnp.cpp | 365 + test/test_url_seed.cpp | 67 + test/test_utf8.cpp | 148 + test/test_utils.cpp | 244 + test/test_utils.hpp | 92 + test/test_utp.cpp | 169 + test/test_web_seed.cpp | 52 + test/test_web_seed_ban.cpp | 62 + test/test_web_seed_chunked.cpp | 62 + test/test_web_seed_http.cpp | 62 + test/test_web_seed_http_pw.cpp | 62 + test/test_web_seed_redirect.cpp | 99 + test/test_web_seed_socks4.cpp | 62 + test/test_web_seed_socks5.cpp | 62 + test/test_web_seed_socks5_no_peers.cpp | 52 + test/test_web_seed_socks5_pw.cpp | 62 + test/test_xml.cpp | 493 + test/udp_tracker.cpp | 260 + test/udp_tracker.hpp | 44 + test/utf8_test.txt | 150 + test/valgrind_suppressions.txt | 8 + test/web_seed_suite.cpp | 403 + test/web_seed_suite.hpp | 43 + test/web_server.py | 228 + test/zeroes.gz | Bin 0 -> 538 bytes tools/CMakeLists.txt | 8 + tools/Jamfile | 48 + tools/benchmark_checking.py | 129 + tools/checking_benchmark.cpp | 198 + tools/cibuildwheel/manylinux/build-openssl.sh | 51 + tools/cibuildwheel/manylinux/build_utils.sh | 67 + .../cibuildwheel/manylinux/openssl-version.sh | 5 + .../cibuildwheel/manylinux/update-scripts.sh | 16 + tools/cibuildwheel/setup_boost.sh | 28 + .../cibuildwheel/setup_ccache_on_manylinux.sh | 23 + tools/cibuildwheel/setup_openssl.sh | 17 + tools/clean.py | 88 + tools/copyright.py | 130 + tools/dht_flood.py | 78 + tools/dht_put.cpp | 430 + tools/dht_sample.cpp | 201 + tools/disk_io_stress_test.cpp | 511 + tools/gen_convenience_header.py | 22 + tools/gen_fwd.py | 142 + tools/libtorrent_lldb.py | 184 + tools/parse_dht_log.py | 341 + tools/parse_dht_rtt.py | 55 + tools/parse_dht_stats.py | 64 + tools/parse_lookup_log.py | 131 + tools/parse_peer_log.py | 75 + tools/parse_sample.py | 162 + tools/parse_session_stats.py | 658 + tools/parse_utp_log.py | 417 + tools/run_benchmark.py | 149 + tools/run_tests.sh | 21 + tools/sanitizer-blacklist.txt | 4 + tools/session_log_alerts.cpp | 72 + tools/set_version.py | 121 + tools/test_coverage.sh | 137 + tools/update_copyright.py | 56 + tools/vmstat.py | 366 + 1206 files changed, 438091 insertions(+) create mode 100644 .cirrus.yml create mode 100644 .pre-commit-config.yaml create mode 100644 AUTHORS create mode 100644 CMakeLists.txt create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 Jamfile create mode 100644 Jamroot.jam create mode 100644 LICENSE create mode 100644 LibtorrentRasterbarConfig.cmake.in create mode 100644 Makefile create mode 100644 NEWS create mode 100644 README.rst create mode 100644 appveyor.yml create mode 100644 bindings/CMakeLists.txt create mode 100644 bindings/Makefile.am create mode 100644 bindings/README.txt create mode 100644 bindings/c/Jamfile create mode 100644 bindings/c/library.cpp create mode 100644 bindings/c/libtorrent.h create mode 100644 bindings/c/simple_client.c create mode 100644 bindings/python/CMakeLists.txt create mode 100644 bindings/python/Jamfile create mode 100755 bindings/python/client.py create mode 100644 bindings/python/dummy_data.py create mode 100755 bindings/python/make_torrent.py create mode 100644 bindings/python/setup.cfg create mode 100644 bindings/python/setup.py create mode 100644 bindings/python/setup.py.cmake.in create mode 100755 bindings/python/simple_client.py create mode 100644 bindings/python/src/alert.cpp create mode 100644 bindings/python/src/boost_python.hpp create mode 100644 bindings/python/src/bytes.hpp create mode 100644 bindings/python/src/converters.cpp create mode 100644 bindings/python/src/create_torrent.cpp create mode 100644 bindings/python/src/datetime.cpp create mode 100644 bindings/python/src/entry.cpp create mode 100644 bindings/python/src/error_code.cpp create mode 100644 bindings/python/src/fingerprint.cpp create mode 100644 bindings/python/src/gil.hpp create mode 100644 bindings/python/src/info_hash.cpp create mode 100644 bindings/python/src/ip_filter.cpp create mode 100644 bindings/python/src/load_torrent.cpp create mode 100644 bindings/python/src/magnet_uri.cpp create mode 100644 bindings/python/src/module.cpp create mode 100644 bindings/python/src/optional.hpp create mode 100644 bindings/python/src/peer_info.cpp create mode 100644 bindings/python/src/session.cpp create mode 100644 bindings/python/src/session_settings.cpp create mode 100644 bindings/python/src/sha1_hash.cpp create mode 100644 bindings/python/src/sha256_hash.cpp create mode 100644 bindings/python/src/string.cpp create mode 100644 bindings/python/src/torrent_handle.cpp create mode 100644 bindings/python/src/torrent_info.cpp create mode 100644 bindings/python/src/torrent_status.cpp create mode 100644 bindings/python/src/utility.cpp create mode 100644 bindings/python/src/version.cpp create mode 100644 bindings/python/test.py create mode 100644 clang_tidy.jam create mode 100644 cmake/Modules/FindLibGcrypt.cmake create mode 100644 cmake/Modules/GeneratePkgConfig.cmake create mode 100644 cmake/Modules/GeneratePkgConfig/generate-pkg-config.cmake.in create mode 100644 cmake/Modules/GeneratePkgConfig/pkg-config.cmake.in create mode 100644 cmake/Modules/GeneratePkgConfig/target-compile-settings.cmake.in create mode 100644 cmake/Modules/LibtorrentMacros.cmake create mode 100644 cmake/Modules/ucm_flags.cmake create mode 100644 deps/try_signal/.travis.yml create mode 100644 deps/try_signal/CMakeLists.txt create mode 100644 deps/try_signal/Jamfile create mode 100644 deps/try_signal/LICENSE create mode 100644 deps/try_signal/README.rst create mode 100644 deps/try_signal/appveyor.yml create mode 100644 deps/try_signal/example.cpp create mode 100644 deps/try_signal/project-root.jam create mode 100644 deps/try_signal/signal_error_code.cpp create mode 100644 deps/try_signal/signal_error_code.hpp create mode 100644 deps/try_signal/test.cpp create mode 100644 deps/try_signal/try_signal.cpp create mode 100644 deps/try_signal/try_signal.hpp create mode 100644 deps/try_signal/try_signal_mingw.hpp create mode 100644 deps/try_signal/try_signal_msvc.hpp create mode 100644 deps/try_signal/try_signal_posix.hpp create mode 100644 docs/2020 Q4 Mozilla Libtorrent Report Public Report.pdf create mode 100755 docs/build_version.sh create mode 100644 docs/building.rst create mode 100644 docs/client_test.rst create mode 100644 docs/contributing.rst create mode 100644 docs/dht_extensions.rst create mode 100644 docs/dht_rss.rst create mode 100644 docs/dht_sec.rst create mode 100644 docs/dht_store.rst create mode 100644 docs/examples.rst create mode 100644 docs/extension_protocol.rst create mode 100644 docs/features.rst create mode 100644 docs/filter-rst.py create mode 100644 docs/fuzzing.rst create mode 100644 docs/gen_reference_doc.py create mode 100755 docs/gen_settings_doc.py create mode 100755 docs/gen_stats_doc.py create mode 100755 docs/gen_todo.py create mode 100644 docs/hacking.rst create mode 100644 docs/header.rst create mode 100644 docs/hunspell/en_US.aff create mode 100644 docs/hunspell/en_US.dic create mode 100644 docs/hunspell/libtorrent.dic create mode 100644 docs/img/bitcoin.png create mode 100644 docs/img/complete_bit_prefixes.png create mode 100644 docs/img/cwnd.png create mode 100644 docs/img/delays.png create mode 100644 docs/img/hacking.diagram create mode 100644 docs/img/hash_distribution.png create mode 100644 docs/img/ip_id_v4.png create mode 100644 docs/img/ip_id_v6.png create mode 100644 docs/img/logo-bw.svg create mode 100644 docs/img/logo-color-text-vertical.svg create mode 100644 docs/img/logo-color-text.png create mode 100644 docs/img/logo-color-text.svg create mode 100644 docs/img/logo-color.svg create mode 100644 docs/img/logo.svg create mode 100644 docs/img/our_delay_base.png create mode 100644 docs/img/pp-acceptance-medium.png create mode 100644 docs/img/read_disk_buffers.diagram create mode 100644 docs/img/screenshot.png create mode 100644 docs/img/storage.diagram create mode 100644 docs/img/troubleshooting.dot create mode 100644 docs/img/utp_stack.diagram create mode 100644 docs/img/write_disk_buffers.diagram create mode 100644 docs/include/libtorrent/add_torrent_params.hpp create mode 100644 docs/include/libtorrent/address.hpp create mode 100644 docs/include/libtorrent/alert.hpp create mode 100644 docs/include/libtorrent/alert_types.hpp create mode 100644 docs/include/libtorrent/announce_entry.hpp create mode 100644 docs/include/libtorrent/assert.hpp create mode 100644 docs/include/libtorrent/bdecode.hpp create mode 100644 docs/include/libtorrent/bencode.hpp create mode 100644 docs/include/libtorrent/bitfield.hpp create mode 100644 docs/include/libtorrent/bloom_filter.hpp create mode 100644 docs/include/libtorrent/bt_peer_connection.hpp create mode 100644 docs/include/libtorrent/choker.hpp create mode 100644 docs/include/libtorrent/client_data.hpp create mode 100644 docs/include/libtorrent/close_reason.hpp create mode 100644 docs/include/libtorrent/config.hpp create mode 100644 docs/include/libtorrent/copy_ptr.hpp create mode 100644 docs/include/libtorrent/crc32c.hpp create mode 100644 docs/include/libtorrent/create_torrent.hpp create mode 100644 docs/include/libtorrent/deadline_timer.hpp create mode 100644 docs/include/libtorrent/debug.hpp create mode 100644 docs/include/libtorrent/disabled_disk_io.hpp create mode 100644 docs/include/libtorrent/disk_buffer_holder.hpp create mode 100644 docs/include/libtorrent/disk_interface.hpp create mode 100644 docs/include/libtorrent/disk_observer.hpp create mode 100644 docs/include/libtorrent/download_priority.hpp create mode 100644 docs/include/libtorrent/entry.hpp create mode 100644 docs/include/libtorrent/enum_net.hpp create mode 100644 docs/include/libtorrent/error.hpp create mode 100644 docs/include/libtorrent/error_code.hpp create mode 100644 docs/include/libtorrent/extensions.hpp create mode 100644 docs/include/libtorrent/file.hpp create mode 100644 docs/include/libtorrent/file_layout.hpp create mode 100644 docs/include/libtorrent/file_storage.hpp create mode 100644 docs/include/libtorrent/fingerprint.hpp create mode 100644 docs/include/libtorrent/flags.hpp create mode 100644 docs/include/libtorrent/fwd.hpp create mode 100644 docs/include/libtorrent/gzip.hpp create mode 100644 docs/include/libtorrent/hash_picker.hpp create mode 100644 docs/include/libtorrent/hasher.hpp create mode 100644 docs/include/libtorrent/hex.hpp create mode 100644 docs/include/libtorrent/http_connection.hpp create mode 100644 docs/include/libtorrent/http_parser.hpp create mode 100644 docs/include/libtorrent/http_seed_connection.hpp create mode 100644 docs/include/libtorrent/http_stream.hpp create mode 100644 docs/include/libtorrent/http_tracker_connection.hpp create mode 100644 docs/include/libtorrent/i2p_stream.hpp create mode 100644 docs/include/libtorrent/identify_client.hpp create mode 100644 docs/include/libtorrent/index_range.hpp create mode 100644 docs/include/libtorrent/info_hash.hpp create mode 100644 docs/include/libtorrent/io.hpp create mode 100644 docs/include/libtorrent/io_context.hpp create mode 100644 docs/include/libtorrent/io_service.hpp create mode 100644 docs/include/libtorrent/ip_filter.hpp create mode 100644 docs/include/libtorrent/ip_voter.hpp create mode 100644 docs/include/libtorrent/libtorrent.hpp create mode 100644 docs/include/libtorrent/link.hpp create mode 100644 docs/include/libtorrent/load_torrent.hpp create mode 100644 docs/include/libtorrent/lsd.hpp create mode 100644 docs/include/libtorrent/magnet_uri.hpp create mode 100644 docs/include/libtorrent/mmap_disk_io.hpp create mode 100644 docs/include/libtorrent/mmap_storage.hpp create mode 100644 docs/include/libtorrent/natpmp.hpp create mode 100644 docs/include/libtorrent/netlink.hpp create mode 100644 docs/include/libtorrent/operations.hpp create mode 100644 docs/include/libtorrent/optional.hpp create mode 100644 docs/include/libtorrent/parse_url.hpp create mode 100644 docs/include/libtorrent/part_file.hpp create mode 100644 docs/include/libtorrent/pe_crypto.hpp create mode 100644 docs/include/libtorrent/peer.hpp create mode 100644 docs/include/libtorrent/peer_class.hpp create mode 100644 docs/include/libtorrent/peer_class_set.hpp create mode 100644 docs/include/libtorrent/peer_class_type_filter.hpp create mode 100644 docs/include/libtorrent/peer_connection.hpp create mode 100644 docs/include/libtorrent/peer_connection_handle.hpp create mode 100644 docs/include/libtorrent/peer_connection_interface.hpp create mode 100644 docs/include/libtorrent/peer_id.hpp create mode 100644 docs/include/libtorrent/peer_info.hpp create mode 100644 docs/include/libtorrent/peer_list.hpp create mode 100644 docs/include/libtorrent/peer_request.hpp create mode 100644 docs/include/libtorrent/performance_counters.hpp create mode 100644 docs/include/libtorrent/pex_flags.hpp create mode 100644 docs/include/libtorrent/piece_block.hpp create mode 100644 docs/include/libtorrent/piece_block_progress.hpp create mode 100644 docs/include/libtorrent/piece_picker.hpp create mode 100644 docs/include/libtorrent/platform_util.hpp create mode 100644 docs/include/libtorrent/portmap.hpp create mode 100644 docs/include/libtorrent/posix_disk_io.hpp create mode 100644 docs/include/libtorrent/proxy_base.hpp create mode 100644 docs/include/libtorrent/puff.hpp create mode 100644 docs/include/libtorrent/random.hpp create mode 100644 docs/include/libtorrent/read_resume_data.hpp create mode 100644 docs/include/libtorrent/request_blocks.hpp create mode 100644 docs/include/libtorrent/resolve_links.hpp create mode 100644 docs/include/libtorrent/session.hpp create mode 100644 docs/include/libtorrent/session_handle.hpp create mode 100644 docs/include/libtorrent/session_params.hpp create mode 100644 docs/include/libtorrent/session_settings.hpp create mode 100644 docs/include/libtorrent/session_stats.hpp create mode 100644 docs/include/libtorrent/session_status.hpp create mode 100644 docs/include/libtorrent/session_types.hpp create mode 100644 docs/include/libtorrent/settings_pack.hpp create mode 100644 docs/include/libtorrent/sha1.hpp create mode 100644 docs/include/libtorrent/sha1_hash.hpp create mode 100644 docs/include/libtorrent/sha256.hpp create mode 100644 docs/include/libtorrent/sliding_average.hpp create mode 100644 docs/include/libtorrent/socket.hpp create mode 100644 docs/include/libtorrent/socket_io.hpp create mode 100644 docs/include/libtorrent/socket_type.hpp create mode 100644 docs/include/libtorrent/socks5_stream.hpp create mode 100644 docs/include/libtorrent/span.hpp create mode 100644 docs/include/libtorrent/ssl.hpp create mode 100644 docs/include/libtorrent/ssl_stream.hpp create mode 100644 docs/include/libtorrent/stack_allocator.hpp create mode 100644 docs/include/libtorrent/stat.hpp create mode 100644 docs/include/libtorrent/stat_cache.hpp create mode 100644 docs/include/libtorrent/storage.hpp create mode 100644 docs/include/libtorrent/storage_defs.hpp create mode 100644 docs/include/libtorrent/string_util.hpp create mode 100644 docs/include/libtorrent/string_view.hpp create mode 100644 docs/include/libtorrent/tailqueue.hpp create mode 100644 docs/include/libtorrent/time.hpp create mode 100644 docs/include/libtorrent/torrent.hpp create mode 100644 docs/include/libtorrent/torrent_flags.hpp create mode 100644 docs/include/libtorrent/torrent_handle.hpp create mode 100644 docs/include/libtorrent/torrent_info.hpp create mode 100644 docs/include/libtorrent/torrent_peer.hpp create mode 100644 docs/include/libtorrent/torrent_peer_allocator.hpp create mode 100644 docs/include/libtorrent/torrent_status.hpp create mode 100644 docs/include/libtorrent/tracker_manager.hpp create mode 100644 docs/include/libtorrent/truncate.hpp create mode 100644 docs/include/libtorrent/udp_socket.hpp create mode 100644 docs/include/libtorrent/udp_tracker_connection.hpp create mode 100644 docs/include/libtorrent/union_endpoint.hpp create mode 100644 docs/include/libtorrent/units.hpp create mode 100644 docs/include/libtorrent/upnp.hpp create mode 100644 docs/include/libtorrent/utf8.hpp create mode 100644 docs/include/libtorrent/vector_utils.hpp create mode 100644 docs/include/libtorrent/version.hpp create mode 100644 docs/include/libtorrent/web_connection_base.hpp create mode 100644 docs/include/libtorrent/web_peer_connection.hpp create mode 100644 docs/include/libtorrent/write_resume_data.hpp create mode 100644 docs/include/libtorrent/xml_parse.hpp create mode 100644 docs/index.rst create mode 100644 docs/makefile create mode 100644 docs/manual.rst create mode 100644 docs/plain_text_out.txt create mode 100644 docs/projects.rst create mode 100644 docs/python_binding.rst create mode 100644 docs/security-audit.rst create mode 100644 docs/settings-ref.rst create mode 100644 docs/streaming.rst create mode 100644 docs/style.css create mode 100644 docs/stylesheet create mode 100644 docs/template.txt create mode 100644 docs/template2.txt create mode 100644 docs/troubleshooting.rst create mode 100644 docs/troubleshooting_thumb.png create mode 100644 docs/tuning.rst create mode 100644 docs/tutorial.rst create mode 100644 docs/udp_tracker_protocol.rst create mode 100644 docs/upgrade_to_1.2.rst create mode 100644 docs/upgrade_to_2.0.rst create mode 100644 docs/utp.rst create mode 100644 examples/CMakeLists.txt create mode 100644 examples/Jamfile create mode 100644 examples/Makefile.am create mode 100644 examples/bt-get.cpp create mode 100644 examples/bt-get2.cpp create mode 100644 examples/bt-get3.cpp create mode 100644 examples/check_files.cpp create mode 100644 examples/client_test.cpp create mode 100644 examples/cmake/FindLibtorrentRasterbar.cmake create mode 100644 examples/connection_tester.cpp create mode 100644 examples/custom_storage.cpp create mode 100644 examples/dump_bdecode.cpp create mode 100644 examples/dump_torrent.cpp create mode 100644 examples/magnet2torrent.cpp create mode 100644 examples/make_torrent.cpp create mode 100644 examples/print.cpp create mode 100644 examples/print.hpp create mode 100755 examples/run_benchmarks.py create mode 100644 examples/session_view.cpp create mode 100644 examples/session_view.hpp create mode 100644 examples/simple_client.cpp create mode 100644 examples/stats_counters.cpp create mode 100644 examples/torrent2magnet.cpp create mode 100644 examples/torrent_view.cpp create mode 100644 examples/torrent_view.hpp create mode 100644 examples/upnp_test.cpp create mode 100644 fuzzers/Jamfile create mode 100644 fuzzers/LICENSE create mode 100644 fuzzers/README.rst create mode 100644 fuzzers/main.cpp create mode 100755 fuzzers/minimize.sh create mode 100755 fuzzers/run.sh create mode 100644 fuzzers/src/add_torrent.cpp create mode 100644 fuzzers/src/base32decode.cpp create mode 100644 fuzzers/src/base32encode.cpp create mode 100644 fuzzers/src/base64encode.cpp create mode 100644 fuzzers/src/bdecode_node.cpp create mode 100644 fuzzers/src/convert_from_native.cpp create mode 100644 fuzzers/src/convert_to_native.cpp create mode 100644 fuzzers/src/dht_node.cpp create mode 100644 fuzzers/src/escape_path.cpp create mode 100644 fuzzers/src/escape_string.cpp create mode 100644 fuzzers/src/file_storage_add_file.cpp create mode 100644 fuzzers/src/gzip.cpp create mode 100644 fuzzers/src/http_parser.cpp create mode 100644 fuzzers/src/http_tracker.cpp create mode 100644 fuzzers/src/idna.cpp create mode 100644 fuzzers/src/parse_int.cpp create mode 100644 fuzzers/src/parse_magnet_uri.cpp create mode 100644 fuzzers/src/parse_url.cpp create mode 100644 fuzzers/src/peer_conn.cpp create mode 100644 fuzzers/src/read_bits.hpp create mode 100644 fuzzers/src/resume_data.cpp create mode 100644 fuzzers/src/sanitize_path.cpp create mode 100644 fuzzers/src/session_params.cpp create mode 100644 fuzzers/src/torrent_info.cpp create mode 100644 fuzzers/src/upnp.cpp create mode 100644 fuzzers/src/utf8_codepoint.cpp create mode 100644 fuzzers/src/utp.cpp create mode 100644 fuzzers/src/verify_encoding.cpp create mode 100644 fuzzers/tools/generate_initial_corpus.py create mode 100644 fuzzers/tools/unify_corpus_names.py create mode 100644 include/libtorrent/add_torrent_params.hpp create mode 100644 include/libtorrent/address.hpp create mode 100644 include/libtorrent/alert.hpp create mode 100644 include/libtorrent/alert_types.hpp create mode 100644 include/libtorrent/announce_entry.hpp create mode 100644 include/libtorrent/assert.hpp create mode 100644 include/libtorrent/aux_/alert_manager.hpp create mode 100644 include/libtorrent/aux_/aligned_union.hpp create mode 100644 include/libtorrent/aux_/alloca.hpp create mode 100644 include/libtorrent/aux_/allocating_handler.hpp create mode 100644 include/libtorrent/aux_/announce_entry.hpp create mode 100644 include/libtorrent/aux_/apply_pad_files.hpp create mode 100644 include/libtorrent/aux_/array.hpp create mode 100644 include/libtorrent/aux_/bandwidth_limit.hpp create mode 100644 include/libtorrent/aux_/bandwidth_manager.hpp create mode 100644 include/libtorrent/aux_/bandwidth_queue_entry.hpp create mode 100644 include/libtorrent/aux_/bandwidth_socket.hpp create mode 100644 include/libtorrent/aux_/bind_to_device.hpp create mode 100644 include/libtorrent/aux_/buffer.hpp create mode 100644 include/libtorrent/aux_/byteswap.hpp create mode 100644 include/libtorrent/aux_/chained_buffer.hpp create mode 100644 include/libtorrent/aux_/container_wrapper.hpp create mode 100644 include/libtorrent/aux_/cpuid.hpp create mode 100644 include/libtorrent/aux_/deferred_handler.hpp create mode 100644 include/libtorrent/aux_/deprecated.hpp create mode 100644 include/libtorrent/aux_/deque.hpp create mode 100644 include/libtorrent/aux_/dev_random.hpp create mode 100644 include/libtorrent/aux_/directory.hpp create mode 100644 include/libtorrent/aux_/disable_deprecation_warnings_push.hpp create mode 100644 include/libtorrent/aux_/disable_warnings_pop.hpp create mode 100644 include/libtorrent/aux_/disable_warnings_push.hpp create mode 100644 include/libtorrent/aux_/disk_buffer_pool.hpp create mode 100644 include/libtorrent/aux_/disk_io_thread_pool.hpp create mode 100644 include/libtorrent/aux_/disk_job_fence.hpp create mode 100644 include/libtorrent/aux_/disk_job_pool.hpp create mode 100644 include/libtorrent/aux_/drive_info.hpp create mode 100644 include/libtorrent/aux_/ed25519.hpp create mode 100644 include/libtorrent/aux_/escape_string.hpp create mode 100644 include/libtorrent/aux_/export.hpp create mode 100644 include/libtorrent/aux_/ffs.hpp create mode 100644 include/libtorrent/aux_/file_descriptor.hpp create mode 100644 include/libtorrent/aux_/file_pointer.hpp create mode 100644 include/libtorrent/aux_/file_progress.hpp create mode 100644 include/libtorrent/aux_/file_view_pool.hpp create mode 100644 include/libtorrent/aux_/generate_peer_id.hpp create mode 100644 include/libtorrent/aux_/has_block.hpp create mode 100644 include/libtorrent/aux_/hasher512.hpp create mode 100644 include/libtorrent/aux_/heterogeneous_queue.hpp create mode 100644 include/libtorrent/aux_/instantiate_connection.hpp create mode 100644 include/libtorrent/aux_/invariant_check.hpp create mode 100644 include/libtorrent/aux_/io.hpp create mode 100644 include/libtorrent/aux_/ip_helpers.hpp create mode 100644 include/libtorrent/aux_/ip_notifier.hpp create mode 100644 include/libtorrent/aux_/keepalive.hpp create mode 100644 include/libtorrent/aux_/listen_socket_handle.hpp create mode 100644 include/libtorrent/aux_/lsd.hpp create mode 100644 include/libtorrent/aux_/merkle.hpp create mode 100644 include/libtorrent/aux_/merkle_tree.hpp create mode 100644 include/libtorrent/aux_/mmap.hpp create mode 100644 include/libtorrent/aux_/mmap_disk_job.hpp create mode 100644 include/libtorrent/aux_/netlink_utils.hpp create mode 100644 include/libtorrent/aux_/noexcept_movable.hpp create mode 100644 include/libtorrent/aux_/numeric_cast.hpp create mode 100644 include/libtorrent/aux_/open_mode.hpp create mode 100644 include/libtorrent/aux_/packet_buffer.hpp create mode 100644 include/libtorrent/aux_/packet_pool.hpp create mode 100644 include/libtorrent/aux_/path.hpp create mode 100644 include/libtorrent/aux_/polymorphic_socket.hpp create mode 100644 include/libtorrent/aux_/pool.hpp create mode 100644 include/libtorrent/aux_/portmap.hpp create mode 100644 include/libtorrent/aux_/posix_part_file.hpp create mode 100644 include/libtorrent/aux_/posix_storage.hpp create mode 100644 include/libtorrent/aux_/proxy_settings.hpp create mode 100644 include/libtorrent/aux_/range.hpp create mode 100644 include/libtorrent/aux_/receive_buffer.hpp create mode 100644 include/libtorrent/aux_/resolver.hpp create mode 100644 include/libtorrent/aux_/resolver_interface.hpp create mode 100644 include/libtorrent/aux_/route.h create mode 100644 include/libtorrent/aux_/scope_end.hpp create mode 100644 include/libtorrent/aux_/session_call.hpp create mode 100644 include/libtorrent/aux_/session_impl.hpp create mode 100644 include/libtorrent/aux_/session_interface.hpp create mode 100644 include/libtorrent/aux_/session_settings.hpp create mode 100644 include/libtorrent/aux_/session_udp_sockets.hpp create mode 100644 include/libtorrent/aux_/set_socket_buffer.hpp create mode 100644 include/libtorrent/aux_/set_traffic_class.hpp create mode 100644 include/libtorrent/aux_/sha512.hpp create mode 100644 include/libtorrent/aux_/socket_type.hpp create mode 100644 include/libtorrent/aux_/storage_free_list.hpp create mode 100644 include/libtorrent/aux_/storage_utils.hpp create mode 100644 include/libtorrent/aux_/store_buffer.hpp create mode 100644 include/libtorrent/aux_/string_ptr.hpp create mode 100644 include/libtorrent/aux_/strview_less.hpp create mode 100644 include/libtorrent/aux_/suggest_piece.hpp create mode 100644 include/libtorrent/aux_/throw.hpp create mode 100644 include/libtorrent/aux_/time.hpp create mode 100644 include/libtorrent/aux_/timestamp_history.hpp create mode 100644 include/libtorrent/aux_/torrent_impl.hpp create mode 100644 include/libtorrent/aux_/torrent_list.hpp create mode 100644 include/libtorrent/aux_/unique_ptr.hpp create mode 100644 include/libtorrent/aux_/utp_socket_manager.hpp create mode 100644 include/libtorrent/aux_/utp_stream.hpp create mode 100644 include/libtorrent/aux_/vector.hpp create mode 100644 include/libtorrent/aux_/win_cng.hpp create mode 100644 include/libtorrent/aux_/win_crypto_provider.hpp create mode 100644 include/libtorrent/aux_/win_file_handle.hpp create mode 100644 include/libtorrent/aux_/win_util.hpp create mode 100644 include/libtorrent/aux_/windows.hpp create mode 100644 include/libtorrent/bdecode.hpp create mode 100644 include/libtorrent/bencode.hpp create mode 100644 include/libtorrent/bitfield.hpp create mode 100644 include/libtorrent/bloom_filter.hpp create mode 100644 include/libtorrent/bt_peer_connection.hpp create mode 100644 include/libtorrent/choker.hpp create mode 100644 include/libtorrent/client_data.hpp create mode 100644 include/libtorrent/close_reason.hpp create mode 100644 include/libtorrent/config.hpp create mode 100644 include/libtorrent/copy_ptr.hpp create mode 100644 include/libtorrent/crc32c.hpp create mode 100644 include/libtorrent/create_torrent.hpp create mode 100644 include/libtorrent/deadline_timer.hpp create mode 100644 include/libtorrent/debug.hpp create mode 100644 include/libtorrent/disabled_disk_io.hpp create mode 100644 include/libtorrent/disk_buffer_holder.hpp create mode 100644 include/libtorrent/disk_interface.hpp create mode 100644 include/libtorrent/disk_observer.hpp create mode 100644 include/libtorrent/download_priority.hpp create mode 100644 include/libtorrent/entry.hpp create mode 100644 include/libtorrent/enum_net.hpp create mode 100644 include/libtorrent/error.hpp create mode 100644 include/libtorrent/error_code.hpp create mode 100644 include/libtorrent/extensions.hpp create mode 100644 include/libtorrent/extensions/smart_ban.hpp create mode 100644 include/libtorrent/extensions/ut_metadata.hpp create mode 100644 include/libtorrent/extensions/ut_pex.hpp create mode 100644 include/libtorrent/file.hpp create mode 100644 include/libtorrent/file_layout.hpp create mode 100644 include/libtorrent/file_storage.hpp create mode 100644 include/libtorrent/fingerprint.hpp create mode 100644 include/libtorrent/flags.hpp create mode 100644 include/libtorrent/fwd.hpp create mode 100644 include/libtorrent/gzip.hpp create mode 100644 include/libtorrent/hash_picker.hpp create mode 100644 include/libtorrent/hasher.hpp create mode 100644 include/libtorrent/hex.hpp create mode 100644 include/libtorrent/http_connection.hpp create mode 100644 include/libtorrent/http_parser.hpp create mode 100644 include/libtorrent/http_seed_connection.hpp create mode 100644 include/libtorrent/http_stream.hpp create mode 100644 include/libtorrent/http_tracker_connection.hpp create mode 100644 include/libtorrent/i2p_stream.hpp create mode 100644 include/libtorrent/identify_client.hpp create mode 100644 include/libtorrent/index_range.hpp create mode 100644 include/libtorrent/info_hash.hpp create mode 100644 include/libtorrent/io.hpp create mode 100644 include/libtorrent/io_context.hpp create mode 100644 include/libtorrent/io_service.hpp create mode 100644 include/libtorrent/ip_filter.hpp create mode 100644 include/libtorrent/ip_voter.hpp create mode 100644 include/libtorrent/kademlia/announce_flags.hpp create mode 100644 include/libtorrent/kademlia/dht_observer.hpp create mode 100644 include/libtorrent/kademlia/dht_settings.hpp create mode 100644 include/libtorrent/kademlia/dht_state.hpp create mode 100644 include/libtorrent/kademlia/dht_storage.hpp create mode 100644 include/libtorrent/kademlia/dht_tracker.hpp create mode 100644 include/libtorrent/kademlia/direct_request.hpp create mode 100644 include/libtorrent/kademlia/dos_blocker.hpp create mode 100644 include/libtorrent/kademlia/ed25519.hpp create mode 100644 include/libtorrent/kademlia/find_data.hpp create mode 100644 include/libtorrent/kademlia/get_item.hpp create mode 100644 include/libtorrent/kademlia/get_peers.hpp create mode 100644 include/libtorrent/kademlia/io.hpp create mode 100644 include/libtorrent/kademlia/item.hpp create mode 100644 include/libtorrent/kademlia/msg.hpp create mode 100644 include/libtorrent/kademlia/node.hpp create mode 100644 include/libtorrent/kademlia/node_entry.hpp create mode 100644 include/libtorrent/kademlia/node_id.hpp create mode 100644 include/libtorrent/kademlia/observer.hpp create mode 100644 include/libtorrent/kademlia/put_data.hpp create mode 100644 include/libtorrent/kademlia/refresh.hpp create mode 100644 include/libtorrent/kademlia/routing_table.hpp create mode 100644 include/libtorrent/kademlia/rpc_manager.hpp create mode 100644 include/libtorrent/kademlia/sample_infohashes.hpp create mode 100644 include/libtorrent/kademlia/traversal_algorithm.hpp create mode 100644 include/libtorrent/kademlia/types.hpp create mode 100644 include/libtorrent/libtorrent.hpp create mode 100644 include/libtorrent/link.hpp create mode 100644 include/libtorrent/load_torrent.hpp create mode 100644 include/libtorrent/lsd.hpp create mode 100644 include/libtorrent/magnet_uri.hpp create mode 100644 include/libtorrent/mmap_disk_io.hpp create mode 100644 include/libtorrent/mmap_storage.hpp create mode 100644 include/libtorrent/natpmp.hpp create mode 100644 include/libtorrent/netlink.hpp create mode 100644 include/libtorrent/operations.hpp create mode 100644 include/libtorrent/optional.hpp create mode 100644 include/libtorrent/parse_url.hpp create mode 100644 include/libtorrent/part_file.hpp create mode 100644 include/libtorrent/pe_crypto.hpp create mode 100644 include/libtorrent/peer.hpp create mode 100644 include/libtorrent/peer_class.hpp create mode 100644 include/libtorrent/peer_class_set.hpp create mode 100644 include/libtorrent/peer_class_type_filter.hpp create mode 100644 include/libtorrent/peer_connection.hpp create mode 100644 include/libtorrent/peer_connection_handle.hpp create mode 100644 include/libtorrent/peer_connection_interface.hpp create mode 100644 include/libtorrent/peer_id.hpp create mode 100644 include/libtorrent/peer_info.hpp create mode 100644 include/libtorrent/peer_list.hpp create mode 100644 include/libtorrent/peer_request.hpp create mode 100644 include/libtorrent/performance_counters.hpp create mode 100644 include/libtorrent/pex_flags.hpp create mode 100644 include/libtorrent/piece_block.hpp create mode 100644 include/libtorrent/piece_block_progress.hpp create mode 100644 include/libtorrent/piece_picker.hpp create mode 100644 include/libtorrent/platform_util.hpp create mode 100644 include/libtorrent/portmap.hpp create mode 100644 include/libtorrent/posix_disk_io.hpp create mode 100644 include/libtorrent/proxy_base.hpp create mode 100644 include/libtorrent/puff.hpp create mode 100644 include/libtorrent/random.hpp create mode 100644 include/libtorrent/read_resume_data.hpp create mode 100644 include/libtorrent/request_blocks.hpp create mode 100644 include/libtorrent/resolve_links.hpp create mode 100644 include/libtorrent/session.hpp create mode 100644 include/libtorrent/session_handle.hpp create mode 100644 include/libtorrent/session_params.hpp create mode 100644 include/libtorrent/session_settings.hpp create mode 100644 include/libtorrent/session_stats.hpp create mode 100644 include/libtorrent/session_status.hpp create mode 100644 include/libtorrent/session_types.hpp create mode 100644 include/libtorrent/settings_pack.hpp create mode 100644 include/libtorrent/sha1.hpp create mode 100644 include/libtorrent/sha1_hash.hpp create mode 100644 include/libtorrent/sha256.hpp create mode 100644 include/libtorrent/sliding_average.hpp create mode 100644 include/libtorrent/socket.hpp create mode 100644 include/libtorrent/socket_io.hpp create mode 100644 include/libtorrent/socket_type.hpp create mode 100644 include/libtorrent/socks5_stream.hpp create mode 100644 include/libtorrent/span.hpp create mode 100644 include/libtorrent/ssl.hpp create mode 100644 include/libtorrent/ssl_stream.hpp create mode 100644 include/libtorrent/stack_allocator.hpp create mode 100644 include/libtorrent/stat.hpp create mode 100644 include/libtorrent/stat_cache.hpp create mode 100644 include/libtorrent/storage.hpp create mode 100644 include/libtorrent/storage_defs.hpp create mode 100644 include/libtorrent/string_util.hpp create mode 100644 include/libtorrent/string_view.hpp create mode 100644 include/libtorrent/tailqueue.hpp create mode 100644 include/libtorrent/time.hpp create mode 100644 include/libtorrent/torrent.hpp create mode 100644 include/libtorrent/torrent_flags.hpp create mode 100644 include/libtorrent/torrent_handle.hpp create mode 100644 include/libtorrent/torrent_info.hpp create mode 100644 include/libtorrent/torrent_peer.hpp create mode 100644 include/libtorrent/torrent_peer_allocator.hpp create mode 100644 include/libtorrent/torrent_status.hpp create mode 100644 include/libtorrent/tracker_manager.hpp create mode 100644 include/libtorrent/truncate.hpp create mode 100644 include/libtorrent/udp_socket.hpp create mode 100644 include/libtorrent/udp_tracker_connection.hpp create mode 100644 include/libtorrent/union_endpoint.hpp create mode 100644 include/libtorrent/units.hpp create mode 100644 include/libtorrent/upnp.hpp create mode 100644 include/libtorrent/utf8.hpp create mode 100644 include/libtorrent/vector_utils.hpp create mode 100644 include/libtorrent/version.hpp create mode 100644 include/libtorrent/web_connection_base.hpp create mode 100644 include/libtorrent/web_peer_connection.hpp create mode 100644 include/libtorrent/write_resume_data.hpp create mode 100644 include/libtorrent/xml_parse.hpp create mode 100644 project-config.jam create mode 100644 pyproject.toml create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 simulation/Jamfile create mode 100644 simulation/create_torrent.cpp create mode 100644 simulation/create_torrent.hpp create mode 100644 simulation/disk_io.cpp create mode 100644 simulation/disk_io.hpp create mode 100644 simulation/fake_peer.hpp create mode 100644 simulation/libsimulator/.travis.yml create mode 100644 simulation/libsimulator/CMakeLists.txt create mode 100644 simulation/libsimulator/Jamfile create mode 100644 simulation/libsimulator/Jamroot.jam create mode 100644 simulation/libsimulator/LICENSE create mode 100644 simulation/libsimulator/README.rst create mode 100644 simulation/libsimulator/appveyor.yml create mode 100644 simulation/libsimulator/include/simulator/chrono.hpp create mode 100644 simulation/libsimulator/include/simulator/config.hpp create mode 100644 simulation/libsimulator/include/simulator/function.hpp create mode 100644 simulation/libsimulator/include/simulator/handler_allocator.hpp create mode 100644 simulation/libsimulator/include/simulator/http_proxy.hpp create mode 100644 simulation/libsimulator/include/simulator/http_server.hpp create mode 100644 simulation/libsimulator/include/simulator/mallocator.hpp create mode 100644 simulation/libsimulator/include/simulator/nat.hpp create mode 100644 simulation/libsimulator/include/simulator/noexcept_movable.hpp create mode 100644 simulation/libsimulator/include/simulator/packet.hpp create mode 100644 simulation/libsimulator/include/simulator/pcap.hpp create mode 100644 simulation/libsimulator/include/simulator/pop_warnings.hpp create mode 100644 simulation/libsimulator/include/simulator/push_warnings.hpp create mode 100644 simulation/libsimulator/include/simulator/queue.hpp create mode 100644 simulation/libsimulator/include/simulator/simulator.hpp create mode 100644 simulation/libsimulator/include/simulator/sink.hpp create mode 100644 simulation/libsimulator/include/simulator/sink_forwarder.hpp create mode 100644 simulation/libsimulator/include/simulator/socks_server.hpp create mode 100644 simulation/libsimulator/include/simulator/utils.hpp create mode 100644 simulation/libsimulator/src/acceptor.cpp create mode 100644 simulation/libsimulator/src/default_config.cpp create mode 100644 simulation/libsimulator/src/high_resolution_clock.cpp create mode 100644 simulation/libsimulator/src/high_resolution_timer.cpp create mode 100644 simulation/libsimulator/src/http_proxy.cpp create mode 100644 simulation/libsimulator/src/http_server.cpp create mode 100644 simulation/libsimulator/src/io_service.cpp create mode 100644 simulation/libsimulator/src/nat.cpp create mode 100644 simulation/libsimulator/src/pcap.cpp create mode 100644 simulation/libsimulator/src/queue.cpp create mode 100644 simulation/libsimulator/src/resolver.cpp create mode 100644 simulation/libsimulator/src/simulation.cpp create mode 100644 simulation/libsimulator/src/simulator.cpp create mode 100644 simulation/libsimulator/src/sink_forwarder.cpp create mode 100644 simulation/libsimulator/src/socks_server.cpp create mode 100644 simulation/libsimulator/src/tcp_socket.cpp create mode 100644 simulation/libsimulator/src/udp_socket.cpp create mode 100644 simulation/libsimulator/test/acceptor.cpp create mode 100644 simulation/libsimulator/test/catch.hpp create mode 100644 simulation/libsimulator/test/main.cpp create mode 100644 simulation/libsimulator/test/multi_accept.cpp create mode 100644 simulation/libsimulator/test/multi_homed.cpp create mode 100644 simulation/libsimulator/test/null_buffers.cpp create mode 100644 simulation/libsimulator/test/parse_request.cpp create mode 100644 simulation/libsimulator/test/resolver.cpp create mode 100644 simulation/libsimulator/test/timer.cpp create mode 100644 simulation/libsimulator/test/udp_socket.cpp create mode 100644 simulation/libsimulator/user-config.jam create mode 100644 simulation/make_proxy_settings.hpp create mode 100644 simulation/setup_dht.cpp create mode 100644 simulation/setup_dht.hpp create mode 100644 simulation/setup_swarm.cpp create mode 100644 simulation/setup_swarm.hpp create mode 100644 simulation/test_auto_manage.cpp create mode 100644 simulation/test_checking.cpp create mode 100644 simulation/test_dht.cpp create mode 100644 simulation/test_dht_bootstrap.cpp create mode 100644 simulation/test_dht_rate_limit.cpp create mode 100644 simulation/test_dht_storage.cpp create mode 100644 simulation/test_error_handling.cpp create mode 100644 simulation/test_fast_extensions.cpp create mode 100644 simulation/test_file_pool.cpp create mode 100644 simulation/test_http_connection.cpp create mode 100644 simulation/test_ip_filter.cpp create mode 100644 simulation/test_metadata_extension.cpp create mode 100644 simulation/test_optimistic_unchoke.cpp create mode 100644 simulation/test_pause.cpp create mode 100644 simulation/test_pe_crypto.cpp create mode 100644 simulation/test_peer_connection.cpp create mode 100644 simulation/test_save_resume.cpp create mode 100644 simulation/test_session.cpp create mode 100644 simulation/test_socks5.cpp create mode 100644 simulation/test_super_seeding.cpp create mode 100644 simulation/test_swarm.cpp create mode 100644 simulation/test_thread_pool.cpp create mode 100644 simulation/test_timeout.cpp create mode 100644 simulation/test_torrent_status.cpp create mode 100644 simulation/test_tracker.cpp create mode 100644 simulation/test_transfer.cpp create mode 100644 simulation/test_transfer_full_invalid_files.cpp create mode 100644 simulation/test_transfer_no_files.cpp create mode 100644 simulation/test_transfer_partial_valid_files.cpp create mode 100644 simulation/test_utp.cpp create mode 100644 simulation/test_v2.cpp create mode 100644 simulation/test_web_seed.cpp create mode 100644 simulation/transfer_sim.cpp create mode 100644 simulation/transfer_sim.hpp create mode 100644 simulation/utils.cpp create mode 100644 simulation/utils.hpp create mode 100644 src/add_torrent_params.cpp create mode 100644 src/alert.cpp create mode 100644 src/alert_manager.cpp create mode 100644 src/announce_entry.cpp create mode 100644 src/assert.cpp create mode 100644 src/bandwidth_limit.cpp create mode 100644 src/bandwidth_manager.cpp create mode 100644 src/bandwidth_queue_entry.cpp create mode 100644 src/bdecode.cpp create mode 100644 src/bitfield.cpp create mode 100644 src/bloom_filter.cpp create mode 100644 src/bt_peer_connection.cpp create mode 100644 src/chained_buffer.cpp create mode 100644 src/choker.cpp create mode 100644 src/close_reason.cpp create mode 100644 src/copy_file.cpp create mode 100644 src/cpuid.cpp create mode 100644 src/crc32c.cpp create mode 100644 src/create_torrent.cpp create mode 100644 src/directory.cpp create mode 100644 src/disabled_disk_io.cpp create mode 100644 src/disk_buffer_holder.cpp create mode 100644 src/disk_buffer_pool.cpp create mode 100644 src/disk_interface.cpp create mode 100644 src/disk_io_thread_pool.cpp create mode 100644 src/disk_job_fence.cpp create mode 100644 src/disk_job_pool.cpp create mode 100644 src/drive_info.cpp create mode 100644 src/ed25519/LICENSE create mode 100644 src/ed25519/add_scalar.cpp create mode 100644 src/ed25519/fe.cpp create mode 100644 src/ed25519/fe.h create mode 100644 src/ed25519/fixedint.h create mode 100644 src/ed25519/ge.cpp create mode 100644 src/ed25519/ge.h create mode 100644 src/ed25519/hasher512.cpp create mode 100644 src/ed25519/key_exchange.cpp create mode 100644 src/ed25519/keypair.cpp create mode 100644 src/ed25519/precomp_data.h create mode 100644 src/ed25519/sc.cpp create mode 100644 src/ed25519/sc.h create mode 100644 src/ed25519/sha512.cpp create mode 100644 src/ed25519/sign.cpp create mode 100644 src/ed25519/verify.cpp create mode 100644 src/entry.cpp create mode 100644 src/enum_net.cpp create mode 100644 src/error_code.cpp create mode 100644 src/escape_string.cpp create mode 100644 src/ffs.cpp create mode 100644 src/file.cpp create mode 100644 src/file_progress.cpp create mode 100644 src/file_storage.cpp create mode 100644 src/file_view_pool.cpp create mode 100644 src/fingerprint.cpp create mode 100644 src/generate_peer_id.cpp create mode 100644 src/gzip.cpp create mode 100644 src/hash_picker.cpp create mode 100644 src/hasher.cpp create mode 100644 src/hex.cpp create mode 100644 src/http_connection.cpp create mode 100644 src/http_parser.cpp create mode 100644 src/http_seed_connection.cpp create mode 100644 src/http_tracker_connection.cpp create mode 100644 src/i2p_stream.cpp create mode 100644 src/identify_client.cpp create mode 100644 src/instantiate_connection.cpp create mode 100644 src/ip_filter.cpp create mode 100644 src/ip_helpers.cpp create mode 100644 src/ip_notifier.cpp create mode 100644 src/ip_voter.cpp create mode 100644 src/kademlia/dht_settings.cpp create mode 100644 src/kademlia/dht_state.cpp create mode 100644 src/kademlia/dht_storage.cpp create mode 100644 src/kademlia/dht_tracker.cpp create mode 100644 src/kademlia/dos_blocker.cpp create mode 100644 src/kademlia/ed25519.cpp create mode 100644 src/kademlia/find_data.cpp create mode 100644 src/kademlia/get_item.cpp create mode 100644 src/kademlia/get_peers.cpp create mode 100644 src/kademlia/item.cpp create mode 100644 src/kademlia/msg.cpp create mode 100644 src/kademlia/node.cpp create mode 100644 src/kademlia/node_entry.cpp create mode 100644 src/kademlia/node_id.cpp create mode 100644 src/kademlia/put_data.cpp create mode 100644 src/kademlia/refresh.cpp create mode 100644 src/kademlia/routing_table.cpp create mode 100644 src/kademlia/rpc_manager.cpp create mode 100644 src/kademlia/sample_infohashes.cpp create mode 100644 src/kademlia/traversal_algorithm.cpp create mode 100644 src/listen_socket_handle.cpp create mode 100644 src/load_torrent.cpp create mode 100644 src/lsd.cpp create mode 100644 src/magnet_uri.cpp create mode 100644 src/merkle.cpp create mode 100644 src/merkle_tree.cpp create mode 100644 src/mmap.cpp create mode 100644 src/mmap_disk_io.cpp create mode 100644 src/mmap_disk_job.cpp create mode 100644 src/mmap_storage.cpp create mode 100644 src/natpmp.cpp create mode 100644 src/packet_buffer.cpp create mode 100644 src/parse_url.cpp create mode 100644 src/part_file.cpp create mode 100644 src/path.cpp create mode 100644 src/pe_crypto.cpp create mode 100644 src/peer_class.cpp create mode 100644 src/peer_class_set.cpp create mode 100644 src/peer_connection.cpp create mode 100644 src/peer_connection_handle.cpp create mode 100644 src/peer_info.cpp create mode 100644 src/peer_list.cpp create mode 100644 src/performance_counters.cpp create mode 100644 src/piece_picker.cpp create mode 100644 src/platform_util.cpp create mode 100644 src/posix_disk_io.cpp create mode 100644 src/posix_part_file.cpp create mode 100644 src/posix_storage.cpp create mode 100644 src/proxy_base.cpp create mode 100644 src/proxy_settings.cpp create mode 100644 src/puff.cpp create mode 100644 src/random.cpp create mode 100644 src/read_resume_data.cpp create mode 100644 src/receive_buffer.cpp create mode 100644 src/request_blocks.cpp create mode 100644 src/resolve_links.cpp create mode 100644 src/resolver.cpp create mode 100644 src/session.cpp create mode 100644 src/session_call.cpp create mode 100644 src/session_handle.cpp create mode 100644 src/session_impl.cpp create mode 100644 src/session_params.cpp create mode 100644 src/session_settings.cpp create mode 100644 src/session_stats.cpp create mode 100644 src/settings_pack.cpp create mode 100644 src/sha1.cpp create mode 100644 src/sha1_hash.cpp create mode 100644 src/sha256.cpp create mode 100644 src/smart_ban.cpp create mode 100644 src/socket_io.cpp create mode 100644 src/socket_type.cpp create mode 100644 src/socks5_stream.cpp create mode 100644 src/ssl.cpp create mode 100644 src/stack_allocator.cpp create mode 100644 src/stat.cpp create mode 100644 src/stat_cache.cpp create mode 100644 src/storage_utils.cpp create mode 100644 src/string_util.cpp create mode 100644 src/time.cpp create mode 100644 src/timestamp_history.cpp create mode 100644 src/torrent.cpp create mode 100644 src/torrent_handle.cpp create mode 100644 src/torrent_info.cpp create mode 100644 src/torrent_peer.cpp create mode 100644 src/torrent_peer_allocator.cpp create mode 100644 src/torrent_status.cpp create mode 100644 src/tracker_manager.cpp create mode 100644 src/truncate.cpp create mode 100644 src/udp_socket.cpp create mode 100644 src/udp_tracker_connection.cpp create mode 100644 src/upnp.cpp create mode 100644 src/ut_metadata.cpp create mode 100644 src/ut_pex.cpp create mode 100644 src/utf8.cpp create mode 100644 src/utp_socket_manager.cpp create mode 100644 src/utp_stream.cpp create mode 100644 src/version.cpp create mode 100644 src/web_connection_base.cpp create mode 100644 src/web_peer_connection.cpp create mode 100644 src/write_resume_data.cpp create mode 100644 src/xml_parse.cpp create mode 100644 tags create mode 100644 test/CMakeLists.txt create mode 100644 test/Jamfile create mode 100644 test/bittorrent_peer.cpp create mode 100644 test/bittorrent_peer.hpp create mode 100644 test/broadcast_socket.cpp create mode 100644 test/broadcast_socket.hpp create mode 100644 test/corrupt.gz create mode 100644 test/dht_server.cpp create mode 100644 test/dht_server.hpp create mode 100644 test/enum_if.cpp create mode 100755 test/http_proxy.py create mode 100644 test/invalid1.gz create mode 100644 test/main.cpp create mode 100644 test/make_torrent.cpp create mode 100644 test/make_torrent.hpp create mode 100644 test/mutable_test_torrents/test1.torrent create mode 100644 test/mutable_test_torrents/test1_pad_files.torrent create mode 100644 test/mutable_test_torrents/test1_single.torrent create mode 100644 test/mutable_test_torrents/test1_single_padded.torrent create mode 100644 test/mutable_test_torrents/test2.torrent create mode 100644 test/mutable_test_torrents/test2_pad_files.torrent create mode 100644 test/mutable_test_torrents/test3.torrent create mode 100644 test/mutable_test_torrents/test3_pad_files.torrent create mode 100644 test/peer_server.cpp create mode 100644 test/peer_server.hpp create mode 100644 test/print_alerts.cpp create mode 100644 test/print_alerts.hpp create mode 100644 test/root1.xml create mode 100644 test/root2.xml create mode 100644 test/root3.xml create mode 100644 test/settings.cpp create mode 100644 test/settings.hpp create mode 100644 test/setup_transfer.cpp create mode 100644 test/setup_transfer.hpp create mode 100755 test/socks.py create mode 100644 test/ssl/dhparams.pem create mode 100644 test/ssl/invalid_peer_certificate.pem create mode 100644 test/ssl/invalid_peer_private_key.pem create mode 100644 test/ssl/peer_certificate.pem create mode 100644 test/ssl/peer_private_key.pem create mode 100755 test/ssl/regenerate_test_certificate.sh create mode 100644 test/ssl/root_ca_cert.pem create mode 100644 test/ssl/root_ca_private.pem create mode 100644 test/ssl/server.pem create mode 100644 test/swarm_suite.cpp create mode 100644 test/swarm_suite.hpp create mode 100644 test/test.cpp create mode 100644 test/test.hpp create mode 100644 test/test_add_torrent.cpp create mode 100644 test/test_alert_manager.cpp create mode 100644 test/test_alert_types.cpp create mode 100644 test/test_alloca.cpp create mode 100644 test/test_apply_pad.cpp create mode 100644 test/test_auto_unchoke.cpp create mode 100644 test/test_bandwidth_limiter.cpp create mode 100644 test/test_bdecode.cpp create mode 100644 test/test_bencoding.cpp create mode 100644 test/test_bitfield.cpp create mode 100644 test/test_bloom_filter.cpp create mode 100644 test/test_buffer.cpp create mode 100644 test/test_checking.cpp create mode 100644 test/test_copy_file.cpp create mode 100644 test/test_crc32.cpp create mode 100644 test/test_create_torrent.cpp create mode 100644 test/test_dht.cpp create mode 100644 test/test_dht_storage.cpp create mode 100644 test/test_direct_dht.cpp create mode 100644 test/test_dos_blocker.cpp create mode 100644 test/test_ed25519.cpp create mode 100644 test/test_enum_net.cpp create mode 100644 test/test_fast_extension.cpp create mode 100644 test/test_fence.cpp create mode 100644 test/test_ffs.cpp create mode 100644 test/test_file.cpp create mode 100644 test/test_file_progress.cpp create mode 100644 test/test_file_storage.cpp create mode 100644 test/test_flags.cpp create mode 100644 test/test_generate_peer_id.cpp create mode 100644 test/test_gzip.cpp create mode 100644 test/test_hash_picker.cpp create mode 100644 test/test_hasher.cpp create mode 100644 test/test_hasher512.cpp create mode 100644 test/test_heterogeneous_queue.cpp create mode 100644 test/test_http_connection.cpp create mode 100644 test/test_http_parser.cpp create mode 100644 test/test_identify_client.cpp create mode 100644 test/test_info_hash.cpp create mode 100644 test/test_io.cpp create mode 100644 test/test_ip_filter.cpp create mode 100644 test/test_ip_voter.cpp create mode 100644 test/test_listen_socket.cpp create mode 100644 test/test_lsd.cpp create mode 100644 test/test_magnet.cpp create mode 100644 test/test_merkle.cpp create mode 100644 test/test_merkle_tree.cpp create mode 100644 test/test_mmap.cpp create mode 100644 test/test_natpmp.cpp create mode 100644 test/test_packet_buffer.cpp create mode 100644 test/test_part_file.cpp create mode 100644 test/test_pe_crypto.cpp create mode 100644 test/test_peer_classes.cpp create mode 100644 test/test_peer_list.cpp create mode 100644 test/test_peer_priority.cpp create mode 100644 test/test_piece_picker.cpp create mode 100644 test/test_primitives.cpp create mode 100644 test/test_priority.cpp create mode 100644 test/test_privacy.cpp create mode 100644 test/test_read_piece.cpp create mode 100644 test/test_read_resume.cpp create mode 100644 test/test_receive_buffer.cpp create mode 100644 test/test_recheck.cpp create mode 100644 test/test_remap_files.cpp create mode 100644 test/test_remove_torrent.cpp create mode 100644 test/test_resolve_links.cpp create mode 100644 test/test_resume.cpp create mode 100644 test/test_session.cpp create mode 100644 test/test_session_params.cpp create mode 100644 test/test_settings_pack.cpp create mode 100644 test/test_sha1_hash.cpp create mode 100644 test/test_similar_torrent.cpp create mode 100644 test/test_sliding_average.cpp create mode 100644 test/test_socket_io.cpp create mode 100644 test/test_span.cpp create mode 100644 test/test_ssl.cpp create mode 100644 test/test_stack_allocator.cpp create mode 100644 test/test_stat_cache.cpp create mode 100644 test/test_storage.cpp create mode 100644 test/test_store_buffer.cpp create mode 100644 test/test_string.cpp create mode 100644 test/test_tailqueue.cpp create mode 100644 test/test_threads.cpp create mode 100644 test/test_time.cpp create mode 100644 test/test_time_critical.cpp create mode 100644 test/test_timestamp_history.cpp create mode 100644 test/test_torrent.cpp create mode 100644 test/test_torrent_info.cpp create mode 100644 test/test_torrent_list.cpp create mode 100644 test/test_torrents/absolute_filename.torrent create mode 100644 test/test_torrents/backslash_path.torrent create mode 100644 test/test_torrents/bad_name.torrent create mode 100644 test/test_torrents/base.torrent create mode 100644 test/test_torrents/collection.torrent create mode 100644 test/test_torrents/collection2.torrent create mode 100644 test/test_torrents/creation_date.torrent create mode 100644 test/test_torrents/dht_nodes.torrent create mode 100644 test/test_torrents/duplicate_files.torrent create mode 100644 test/test_torrents/duplicate_web_seeds.torrent create mode 100644 test/test_torrents/empty-files-1.torrent create mode 100644 test/test_torrents/empty-files-2.torrent create mode 100644 test/test_torrents/empty-files-3.torrent create mode 100644 test/test_torrents/empty-files-4.torrent create mode 100644 test/test_torrents/empty-files-5.torrent create mode 100644 test/test_torrents/empty_httpseed.torrent create mode 100644 test/test_torrents/empty_path.torrent create mode 100644 test/test_torrents/empty_path_multi.torrent create mode 100644 test/test_torrents/hidden_parent_path.torrent create mode 100644 test/test_torrents/httpseed.torrent create mode 100644 test/test_torrents/invalid_file_size.torrent create mode 100644 test/test_torrents/invalid_filename.torrent create mode 100644 test/test_torrents/invalid_filename2.torrent create mode 100644 test/test_torrents/invalid_info.torrent create mode 100644 test/test_torrents/invalid_name.torrent create mode 100644 test/test_torrents/invalid_name2.torrent create mode 100644 test/test_torrents/invalid_name3.torrent create mode 100644 test/test_torrents/invalid_path_list.torrent create mode 100644 test/test_torrents/invalid_piece_len.torrent create mode 100644 test/test_torrents/invalid_pieces.torrent create mode 100644 test/test_torrents/invalid_symlink.torrent create mode 100644 test/test_torrents/large.torrent create mode 100644 test/test_torrents/large_piece_size.torrent create mode 100644 test/test_torrents/long_name.torrent create mode 100644 test/test_torrents/many_pieces.torrent create mode 100644 test/test_torrents/missing_path_list.torrent create mode 100644 test/test_torrents/missing_piece_len.torrent create mode 100644 test/test_torrents/negative_file_size.torrent create mode 100644 test/test_torrents/negative_piece_len.torrent create mode 100644 test/test_torrents/negative_size.torrent create mode 100644 test/test_torrents/no_creation_date.torrent create mode 100644 test/test_torrents/no_files.torrent create mode 100644 test/test_torrents/no_name.torrent create mode 100644 test/test_torrents/overlapping_symlinks.torrent create mode 100644 test/test_torrents/pad_file.torrent create mode 100644 test/test_torrents/pad_file_no_path.torrent create mode 100644 test/test_torrents/parent_path.torrent create mode 100644 test/test_torrents/sample.torrent create mode 100644 test/test_torrents/similar.torrent create mode 100644 test/test_torrents/similar2.torrent create mode 100644 test/test_torrents/single_multi_file.torrent create mode 100644 test/test_torrents/slash_path.torrent create mode 100644 test/test_torrents/slash_path2.torrent create mode 100644 test/test_torrents/slash_path3.torrent create mode 100644 test/test_torrents/string.torrent create mode 100644 test/test_torrents/symlink1.torrent create mode 100755 test/test_torrents/symlink2.torrent create mode 100644 test/test_torrents/symlink_zero_size.torrent create mode 100644 test/test_torrents/unaligned_pieces.torrent create mode 100644 test/test_torrents/unordered.torrent create mode 100644 test/test_torrents/url_list.torrent create mode 100644 test/test_torrents/url_list2.torrent create mode 100644 test/test_torrents/url_list3.torrent create mode 100644 test/test_torrents/url_seed.torrent create mode 100644 test/test_torrents/url_seed_multi.torrent create mode 100644 test/test_torrents/url_seed_multi_single_file.torrent create mode 100644 test/test_torrents/url_seed_multi_space.torrent create mode 100644 test/test_torrents/url_seed_multi_space_nolist.torrent create mode 100644 test/test_torrents/v2.torrent create mode 100644 test/test_torrents/v2_bad_file_alignment.torrent create mode 100644 test/test_torrents/v2_deep_recursion.torrent create mode 100644 test/test_torrents/v2_empty_file.torrent create mode 100644 test/test_torrents/v2_hybrid-missing-tailpad.torrent create mode 100644 test/test_torrents/v2_hybrid.torrent create mode 100644 test/test_torrents/v2_incomplete_piece_layer.torrent create mode 100644 test/test_torrents/v2_invalid_file.torrent create mode 100644 test/test_torrents/v2_invalid_filename.torrent create mode 100644 test/test_torrents/v2_invalid_pad_file.torrent create mode 100644 test/test_torrents/v2_invalid_piece_layer.torrent create mode 100644 test/test_torrents/v2_invalid_piece_layer_root.torrent create mode 100644 test/test_torrents/v2_invalid_piece_layer_size.torrent create mode 100755 test/test_torrents/v2_invalid_root_hash.torrent create mode 100644 test/test_torrents/v2_large_file.torrent create mode 100644 test/test_torrents/v2_large_offset.torrent create mode 100644 test/test_torrents/v2_mismatching_metadata.torrent create mode 100644 test/test_torrents/v2_missing_file_root_invalid_symlink.torrent create mode 100644 test/test_torrents/v2_multipiece_file.torrent create mode 100644 test/test_torrents/v2_multiple_files.torrent create mode 100644 test/test_torrents/v2_no_piece_layers.torrent create mode 100644 test/test_torrents/v2_no_power2_piece.torrent create mode 100644 test/test_torrents/v2_non_multiple_piece_layer.torrent create mode 100644 test/test_torrents/v2_only.torrent create mode 100644 test/test_torrents/v2_overlong_integer.torrent create mode 100644 test/test_torrents/v2_piece_layer_invalid_file_hash.torrent create mode 100644 test/test_torrents/v2_piece_size.torrent create mode 100644 test/test_torrents/v2_symlinks.torrent create mode 100644 test/test_torrents/v2_unknown_piece_layer_entry.torrent create mode 100644 test/test_torrents/v2_unordered_files.torrent create mode 100644 test/test_torrents/v2_zero_root.torrent create mode 100644 test/test_torrents/v2_zero_root_small.torrent create mode 100644 test/test_torrents/whitespace_url.torrent create mode 100644 test/test_torrents/zero.torrent create mode 100644 test/test_torrents/zero2.torrent create mode 100644 test/test_tracker.cpp create mode 100644 test/test_transfer.cpp create mode 100644 test/test_truncate.cpp create mode 100644 test/test_upnp.cpp create mode 100644 test/test_url_seed.cpp create mode 100644 test/test_utf8.cpp create mode 100644 test/test_utils.cpp create mode 100644 test/test_utils.hpp create mode 100644 test/test_utp.cpp create mode 100644 test/test_web_seed.cpp create mode 100644 test/test_web_seed_ban.cpp create mode 100644 test/test_web_seed_chunked.cpp create mode 100644 test/test_web_seed_http.cpp create mode 100644 test/test_web_seed_http_pw.cpp create mode 100644 test/test_web_seed_redirect.cpp create mode 100644 test/test_web_seed_socks4.cpp create mode 100644 test/test_web_seed_socks5.cpp create mode 100644 test/test_web_seed_socks5_no_peers.cpp create mode 100644 test/test_web_seed_socks5_pw.cpp create mode 100644 test/test_xml.cpp create mode 100644 test/udp_tracker.cpp create mode 100644 test/udp_tracker.hpp create mode 100644 test/utf8_test.txt create mode 100644 test/valgrind_suppressions.txt create mode 100644 test/web_seed_suite.cpp create mode 100644 test/web_seed_suite.hpp create mode 100644 test/web_server.py create mode 100644 test/zeroes.gz create mode 100644 tools/CMakeLists.txt create mode 100644 tools/Jamfile create mode 100644 tools/benchmark_checking.py create mode 100644 tools/checking_benchmark.cpp create mode 100755 tools/cibuildwheel/manylinux/build-openssl.sh create mode 100755 tools/cibuildwheel/manylinux/build_utils.sh create mode 100755 tools/cibuildwheel/manylinux/openssl-version.sh create mode 100755 tools/cibuildwheel/manylinux/update-scripts.sh create mode 100755 tools/cibuildwheel/setup_boost.sh create mode 100755 tools/cibuildwheel/setup_ccache_on_manylinux.sh create mode 100755 tools/cibuildwheel/setup_openssl.sh create mode 100755 tools/clean.py create mode 100644 tools/copyright.py create mode 100755 tools/dht_flood.py create mode 100644 tools/dht_put.cpp create mode 100644 tools/dht_sample.cpp create mode 100644 tools/disk_io_stress_test.cpp create mode 100644 tools/gen_convenience_header.py create mode 100644 tools/gen_fwd.py create mode 100644 tools/libtorrent_lldb.py create mode 100755 tools/parse_dht_log.py create mode 100755 tools/parse_dht_rtt.py create mode 100755 tools/parse_dht_stats.py create mode 100755 tools/parse_lookup_log.py create mode 100755 tools/parse_peer_log.py create mode 100755 tools/parse_sample.py create mode 100755 tools/parse_session_stats.py create mode 100755 tools/parse_utp_log.py create mode 100755 tools/run_benchmark.py create mode 100755 tools/run_tests.sh create mode 100644 tools/sanitizer-blacklist.txt create mode 100644 tools/session_log_alerts.cpp create mode 100755 tools/set_version.py create mode 100755 tools/test_coverage.sh create mode 100755 tools/update_copyright.py create mode 100644 tools/vmstat.py diff --git a/.cirrus.yml b/.cirrus.yml new file mode 100644 index 0000000..d3d21dd --- /dev/null +++ b/.cirrus.yml @@ -0,0 +1,35 @@ +# since CirrusCI has limited compute resources for free open-source projects +# we only use it for its unique feature, FreeBSD images + +freebsd_instance: + image_family: freebsd-14-0 + +task: + env: + CIRRUS_CLONE_DEPTH: 1 + CIRRUS_CLONE_SUBMODULES: true + CIBW_BUILD_VERBOSITY: 3 + CIBW_SKIP: pp* cp38-* # cp38-* has problem with x86_64 / arm64 confusion + CIBW_BUILD: cp39-* cp310-* cp311-* cp312-* + install_script: | + pkg install -y boost-build boost-libs openssl cmake ninja py311-pip + echo "using clang ;" > ~/user-config.jam + build_cmake_script: | + cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-Werror" -DCMAKE_CXX_STANDARD=14 -Dbuild_tests=ON -Dbuild_examples=ON -Dbuild_tools=ON -Dpython-bindings=OFF -G Ninja . + cmake --build . + ./test/test_primitives + tests_script: | + cd test + b2 -l250 warnings-as-errors=on warnings=all crypto=openssl deterministic-tests include=/usr/local/include library-path=/usr/local/lib + enum_if_script: | + cd test + b2 -l250 warnings-as-errors=on warnings=all crypto=openssl stage_enum_if stage_dependencies include=/usr/local/include library-path=/usr/local/lib + LD_LIBRARY_PATH=./dependencies ./enum_if +# it appears cibuildwheel does not support FreeBSD (nor CirrusCI) +# install_cibuildwheel_script: +# - python -m pip install cibuildwheel==2.16.5 +# run_cibuildwheel_script: +# - cibuildwheel +# wheels_artifacts: +# path: "wheelhouse/*" + diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..a2630ba --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,213 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks + +exclude: | + (?x)^( + # These files are vendored from elsewhere, don't process them + LICENSE| + docs/hunspell/.*| + src/ed25519/.*| + include/libtorrent/aux_/route\.h| + test/.*\.xml| + test/ssl/.*\.pem + )$ +default_language_version: + python: python3 +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + #- id: trailing-whitespace + #- id: end-of-file-fixer + - id: check-yaml + - id: check-case-conflict + - id: check-executables-have-shebangs + exclude: | + (?x)^( + # Enable these later, avoid bloating this PR + tools/run_tests.sh + ) + - id: check-xml + - id: debug-statements + - id: check-symlinks + - id: check-toml +- repo: https://github.com/pappasam/toml-sort + rev: v0.24.2 + hooks: + - id: toml-sort + args: [--all, --in-place] +- repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.10.0 + hooks: + - id: rst-directive-colons + - id: rst-inline-touching-normal +- repo: https://github.com/PyCQA/isort + rev: 5.13.2 + hooks: + - id: isort + exclude: | + (?x)^( + # Enable these later, avoid bloating this PR + bindings/python/client.py| + bindings/python/dummy_data.py| + bindings/python/make_torrent.py| + bindings/python/simple_client.py| + bindings/python/test.py| + docs/gen_reference_doc.py| + examples/run_benchmarks.py| + fuzzers/tools/generate_initial_corpus.py| + fuzzers/tools/unify_corpus_names.py| + test/socks.py| + test/web_server.py| + tools/clean.py| + tools/copyright.py| + tools/dht_flood.py| + tools/libtorrent_lldb.py| + tools/parse_dht_log.py| + tools/parse_dht_rtt.py| + tools/parse_dht_stats.py| + tools/parse_session_stats.py| + tools/parse_utp_log.py| + tools/run_benchmark.py| + tools/update_copyright.py + )$ +- repo: https://github.com/PyCQA/autoflake + rev: v2.3.1 + hooks: + - id: autoflake + args: [--in-place, --remove-unused-variables, --remove-all-unused-imports, --remove-duplicate-keys] + # Avoiding PR bloat + exclude: | + (?x)^( + bindings/python/test.py| + tools/benchmark_checking.py| + tools/copyright.py| + tools/gen_convenience_header.py| + tools/libtorrent_lldb.py + ) +- repo: https://github.com/psf/black + rev: 24.10.0 + hooks: + - id: black + # Avoiding PR bloat + exclude: | + (?x)^( + bindings/python/client.py| + bindings/python/dummy_data.py| + bindings/python/make_torrent.py| + bindings/python/simple_client.py| + bindings/python/test.py| + docs/filter-rst.py| + docs/gen_settings_doc.py| + docs/gen_reference_doc.py| + docs/gen_stats_doc.py| + docs/gen_todo.py| + examples/run_benchmarks.py| + fuzzers/tools/generate_initial_corpus.py| + fuzzers/tools/unify_corpus_names.py| + test/http_proxy.py| + test/socks.py| + test/web_server.py| + tools/benchmark_checking.py| + tools/clean.py| + tools/copyright.py| + tools/dht_flood.py| + tools/gen_convenience_header.py| + tools/gen_fwd.py| + tools/libtorrent_lldb.py| + tools/parse_dht_log.py| + tools/parse_dht_rtt.py| + tools/parse_dht_stats.py| + tools/parse_lookup_log.py| + tools/parse_peer_log.py| + tools/parse_sample.py| + tools/parse_session_stats.py| + tools/parse_utp_log.py| + tools/run_benchmark.py| + tools/set_version.py| + tools/update_copyright.py + )$ + # black doesn't run on *.pyi files by default, for reasons + - id: black + name: black (pyi) + types: [pyi] +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.13.0 + hooks: + - id: mypy + # Avoiding PR bloat + exclude: | + (?x)^( + bindings/python/client.py| + bindings/python/dummy_data.py| + bindings/python/make_torrent.py| + bindings/python/test.py| + docs/filter-rst.py| + docs/gen_reference_doc.py| + docs/gen_settings_doc.py| + docs/gen_stats_doc.py| + docs/gen_todo.py| + examples/run_benchmarks.py| + fuzzers/tools/generate_initial_corpus.py| + setup.py| + test/http_proxy.py| + test/socks.py| + test/web_server.py| + tools/benchmark_checking.py| + tools/clean.py| + tools/copyright.py| + tools/dht_flood.py| + tools/gen_convenience_header.py| + tools/gen_fwd.py| + tools/libtorrent_lldb.py| + tools/parse_dht_log.py| + tools/parse_dht_stats.py| + tools/parse_lookup_log.py| + tools/parse_peer_log.py| + tools/parse_sample.py| + tools/parse_session_stats.py| + tools/parse_utp_log.py| + tools/run_benchmark.py| + tools/update_copyright.py + )$ +- repo: https://github.com/PyCQA/flake8 + rev: 7.1.1 + hooks: + - id: flake8 + exclude: | + (?x)^( + # Enable these later, avoid bloating this PR + bindings/python/client.py| + bindings/python/make_torrent.py| + bindings/python/test.py| + docs/gen_settings_doc.py| + docs/gen_todo.py| + docs/gen_reference_doc.py| + docs/gen_stats_doc.py| + examples/run_benchmarks.py| + fuzzers/tools/generate_initial_corpus.py| + test/http_proxy.py| + test/socks.py| + test/web_server.py| + tools/benchmark_checking.py| + tools/dht_flood.py| + tools/gen_convenience_header.py| + tools/gen_fwd.py| + tools/libtorrent_lldb.py| + tools/parse_dht_stats.py| + tools/parse_dht_log.py| + tools/parse_lookup_log.py| + tools/parse_peer_log.py| + tools/parse_sample.py| + tools/parse_session_stats.py| + tools/parse_utp_log.py| + tools/run_benchmark.py| + tools/update_copyright.py + )$ +#- repo: local +# hooks: +# - id: gen_fwd +# name: gen_fwd +# language: system +# entry: ./tools/gen_fwd.py +# always_run: true diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..c8a2154 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,25 @@ +Written by Arvid Norberg. Copyright (c) 2003-2021 + +Contributions by: +Andrei Kurushin +Steven Siloti +Alden Torres +Thomas Fischer +Massaroddel +Tianhao Qiu. +Shyam +Magnus Jonsson +Daniel Wallin +Cory Nelson +Stas Khirman +Ryan Norton +Andrew Resch + +Thanks to (github user) nervoir for bug reports + +Thanks to Reimond Retz for bugfixes, suggestions and testing + +Thanks to University of Umeå for providing development and test hardware. + +Project is hosted by Github + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..dce7459 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,994 @@ +cmake_minimum_required(VERSION 3.16.0 FATAL_ERROR) # Configurable policies: <= CMP0097 + +cmake_policy(SET CMP0091 NEW) +cmake_policy(SET CMP0092 NEW) + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules) +include(LibtorrentMacros) + +read_version("${CMAKE_CURRENT_SOURCE_DIR}/include/libtorrent/version.hpp" VER_MAJOR VER_MINOR VER_TINY) + +project(libtorrent + DESCRIPTION "Bittorrent library" + VERSION ${VER_MAJOR}.${VER_MINOR}.${VER_TINY} +) +set (SOVERSION "${VER_MAJOR}.${VER_MINOR}") + +include(GNUInstallDirs) +include(GeneratePkgConfig) + +set(libtorrent_include_files + add_torrent_params.hpp + address.hpp + alert.hpp + alert_types.hpp + announce_entry.hpp + assert.hpp + bdecode.hpp + bencode.hpp + bitfield.hpp + bloom_filter.hpp + bt_peer_connection.hpp + choker.hpp + client_data.hpp + close_reason.hpp + config.hpp + copy_ptr.hpp + crc32c.hpp + create_torrent.hpp + deadline_timer.hpp + debug.hpp + disk_buffer_holder.hpp + disk_interface.hpp + disk_observer.hpp + download_priority.hpp + entry.hpp + enum_net.hpp + error.hpp + error_code.hpp + extensions.hpp + file.hpp + file_storage.hpp + file_layout.hpp + fingerprint.hpp + flags.hpp + fwd.hpp + gzip.hpp + hash_picker.hpp + hasher.hpp + hex.hpp + http_connection.hpp + http_parser.hpp + http_seed_connection.hpp + http_stream.hpp + http_tracker_connection.hpp + i2p_stream.hpp + identify_client.hpp + index_range.hpp + io.hpp + io_service.hpp + ip_filter.hpp + ip_voter.hpp + libtorrent.hpp + link.hpp + lsd.hpp + magnet_uri.hpp + mmap_disk_io.hpp + mmap_storage.hpp + natpmp.hpp + netlink.hpp + operations.hpp + optional.hpp + parse_url.hpp + part_file.hpp + peer.hpp + peer_class.hpp + peer_class_set.hpp + peer_class_type_filter.hpp + peer_connection.hpp + peer_connection_handle.hpp + peer_connection_interface.hpp + peer_id.hpp + peer_info.hpp + peer_list.hpp + peer_request.hpp + performance_counters.hpp + pex_flags.hpp + piece_block.hpp + piece_block_progress.hpp + piece_picker.hpp + platform_util.hpp + portmap.hpp + proxy_base.hpp + puff.hpp + random.hpp + read_resume_data.hpp + request_blocks.hpp + resolve_links.hpp + session.hpp + session_handle.hpp + session_params.hpp + session_settings.hpp + session_stats.hpp + session_status.hpp + session_types.hpp + settings_pack.hpp + sha1.hpp + sha1_hash.hpp + sha256.hpp + sliding_average.hpp + socket.hpp + socket_io.hpp + socket_type.hpp + socks5_stream.hpp + span.hpp + ssl.hpp + ssl_stream.hpp + stack_allocator.hpp + stat.hpp + stat_cache.hpp + storage_defs.hpp + string_util.hpp + string_view.hpp + tailqueue.hpp + time.hpp + torrent.hpp + torrent_flags.hpp + torrent_handle.hpp + torrent_info.hpp + torrent_peer.hpp + torrent_peer_allocator.hpp + torrent_status.hpp + tracker_manager.hpp + truncate.hpp + udp_socket.hpp + udp_tracker_connection.hpp + union_endpoint.hpp + units.hpp + upnp.hpp + utf8.hpp + vector_utils.hpp + version.hpp + web_connection_base.hpp + web_peer_connection.hpp + write_resume_data.hpp + xml_parse.hpp +) + +set(libtorrent_kademlia_include_files + announce_flags.hpp + dht_observer.hpp + dht_settings.hpp + dht_state.hpp + dht_storage.hpp + dht_tracker.hpp + direct_request.hpp + dos_blocker.hpp + ed25519.hpp + find_data.hpp + get_item.hpp + get_peers.hpp + io.hpp + item.hpp + msg.hpp + node.hpp + node_entry.hpp + node_id.hpp + observer.hpp + put_data.hpp + refresh.hpp + routing_table.hpp + rpc_manager.hpp + sample_infohashes.hpp + traversal_algorithm.hpp + types.hpp +) + +set(libtorrent_extensions_include_files + smart_ban.hpp + ut_metadata.hpp + ut_pex.hpp +) + +set(libtorrent_aux_include_files + alert_manager.hpp + aligned_union.hpp + alloca.hpp + allocating_handler.hpp + apply_pad_files.hpp + array.hpp + bandwidth_limit.hpp + bandwidth_manager.hpp + bandwidth_queue_entry.hpp + bandwidth_socket.hpp + bind_to_device.hpp + buffer.hpp + byteswap.hpp + chained_buffer.hpp + cpuid.hpp + deferred_handler.hpp + deprecated.hpp + deque.hpp + dev_random.hpp + directory.hpp + disable_warnings_pop.hpp + disable_warnings_push.hpp + disk_buffer_pool.hpp + mmap_disk_job.hpp + disk_io_thread_pool.hpp + disk_job_fence.hpp + disk_job_pool.hpp + drive_info.hpp + ed25519.hpp + escape_string.hpp + export.hpp + ffs.hpp + file_descriptor.hpp + file_progress.hpp + file_view_pool.hpp + has_block.hpp + heterogeneous_queue.hpp + instantiate_connection.hpp + invariant_check.hpp + io.hpp + ip_helpers.hpp + ip_notifier.hpp + keepalive.hpp + listen_socket_handle.hpp + lsd.hpp + merkle.hpp + merkle_tree.hpp + netlink_utils.hpp + noexcept_movable.hpp + numeric_cast.hpp + packet_buffer.hpp + packet_pool.hpp + path.hpp + polymorphic_socket.hpp + pool.hpp + portmap.hpp + posix_part_file.hpp + proxy_settings.hpp + range.hpp + receive_buffer.hpp + resolver.hpp + resolver_interface.hpp + scope_end.hpp + session_call.hpp + session_impl.hpp + session_interface.hpp + session_settings.hpp + session_udp_sockets.hpp + set_socket_buffer.hpp + set_traffic_class.hpp + set_traffic_class.hpp + socket_type.hpp + storage_free_list.hpp + storage_utils.hpp + string_ptr.hpp + strview_less.hpp + suggest_piece.hpp + throw.hpp + time.hpp + timestamp_history.hpp + torrent_impl.hpp + torrent_list.hpp + unique_ptr.hpp + utp_socket_manager.hpp + utp_stream.hpp + vector.hpp + win_crypto_provider.hpp + win_file_handle.hpp + win_util.hpp +) + +set(try_signal_include_files + try_signal + signal_error_code + try_signal_mingw + try_signal_msvc + try_signal_posix +) + +set(sources + add_torrent_params.cpp + alert.cpp + alert_manager.cpp + announce_entry.cpp + assert.cpp + bandwidth_limit.cpp + bandwidth_manager.cpp + bandwidth_queue_entry.cpp + bdecode.cpp + bitfield.cpp + bloom_filter.cpp + bt_peer_connection.cpp + chained_buffer.cpp + choker.cpp + close_reason.cpp + copy_file.cpp + cpuid.cpp + crc32c.cpp + create_torrent.cpp + directory.cpp + disabled_disk_io.cpp + disk_buffer_holder.cpp + disk_buffer_pool.cpp + disk_interface.cpp + disk_io_thread_pool.cpp + disk_job_fence.cpp + disk_job_pool.cpp + drive_info.cpp + entry.cpp + enum_net.cpp + error_code.cpp + escape_string.cpp + ffs.cpp + file.cpp + file_progress.cpp + file_storage.cpp + file_view_pool.cpp + fingerprint.cpp + generate_peer_id.cpp + gzip.cpp + hash_picker.cpp + hasher.cpp + hex.cpp + http_connection.cpp + http_parser.cpp + http_seed_connection.cpp + http_tracker_connection.cpp + i2p_stream.cpp + identify_client.cpp + instantiate_connection.cpp + ip_filter.cpp + ip_helpers.cpp + ip_notifier.cpp + ip_voter.cpp + listen_socket_handle.cpp + load_torrent.cpp + lsd.cpp + magnet_uri.cpp + merkle.cpp + merkle_tree.cpp + mmap.cpp + mmap_disk_io.cpp + mmap_disk_job.cpp + mmap_storage.cpp + natpmp.cpp + packet_buffer.cpp + parse_url.cpp + part_file.cpp + path.cpp + peer_class.cpp + peer_class_set.cpp + peer_connection.cpp + peer_connection_handle.cpp + peer_info.cpp + peer_list.cpp + performance_counters.cpp + piece_picker.cpp + platform_util.cpp + posix_disk_io.cpp + posix_part_file.cpp + posix_storage.cpp + proxy_base.cpp + proxy_settings.cpp + puff.cpp + random.cpp + read_resume_data.cpp + receive_buffer.cpp + request_blocks.cpp + resolve_links.cpp + resolver.cpp + session.cpp + session_call.cpp + session_handle.cpp + session_impl.cpp + session_params.cpp + session_settings.cpp + session_stats.cpp + settings_pack.cpp + sha1.cpp + sha1_hash.cpp + sha256.cpp + socket_io.cpp + socket_type.cpp + socks5_stream.cpp + ssl.cpp + stack_allocator.cpp + stat.cpp + stat_cache.cpp + storage_utils.cpp + string_util.cpp + time.cpp + timestamp_history.cpp + torrent.cpp + torrent_handle.cpp + torrent_info.cpp + torrent_peer.cpp + torrent_peer_allocator.cpp + torrent_status.cpp + tracker_manager.cpp + truncate.cpp + udp_socket.cpp + udp_tracker_connection.cpp + upnp.cpp + utf8.cpp + utp_socket_manager.cpp + utp_stream.cpp + version.cpp + web_connection_base.cpp + web_peer_connection.cpp + write_resume_data.cpp + xml_parse.cpp + +# -- extensions -- + smart_ban.cpp + ut_pex.cpp + ut_metadata.cpp +) + +# -- kademlia -- +set(kademlia_sources + dht_settings.cpp + dht_state.cpp + dht_storage.cpp + dht_tracker.cpp + dos_blocker.cpp + ed25519.cpp + find_data.cpp + get_item.cpp + get_peers.cpp + item.cpp + msg.cpp + node.cpp + node_entry.cpp + node_id.cpp + put_data.cpp + refresh.cpp + routing_table.cpp + rpc_manager.cpp + sample_infohashes.cpp + traversal_algorithm.cpp +) + +# -- ed25519 -- +set(ed25519_sources + add_scalar.cpp + fe.cpp + ge.cpp + key_exchange.cpp + keypair.cpp + sc.cpp + sign.cpp + verify.cpp + sha512.cpp + hasher512.cpp +) + +set(try_signal_sources + try_signal.cpp + signal_error_code.cpp +) + +list(TRANSFORM sources PREPEND "src/") +list(TRANSFORM kademlia_sources PREPEND "src/kademlia/") +list(TRANSFORM ed25519_sources PREPEND "src/ed25519/") +list(TRANSFORM libtorrent_include_files PREPEND "include/libtorrent/") +list(TRANSFORM libtorrent_extensions_include_files PREPEND "include/libtorrent/extensions/") +list(TRANSFORM libtorrent_aux_include_files PREPEND "include/libtorrent/aux_/") +list(TRANSFORM libtorrent_kademlia_include_files PREPEND "include/libtorrent/kademlia/") +list(TRANSFORM try_signal_sources PREPEND "deps/try_signal/") + +# these options control target creation and thus have to be declared before the add_library() call +feature_option(BUILD_SHARED_LIBS "build libtorrent as a shared library" ON) +feature_option(static_runtime "build libtorrent with static runtime" OFF) + +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_public_dependency(Threads REQUIRED) + +if(CMAKE_CXX_COMPILER_ID MATCHES Clang) + add_compile_options( + -Weverything + -Wno-c++98-compat-pedantic + -Wno-c++11-compat-pedantic + -Wno-padded + -Wno-alloca + -Wno-global-constructors + -Wno-exit-time-destructors + -Wno-weak-vtables + -Wno-return-std-move-in-c++11 + -Wno-unsafe-buffer-usage + -Wno-unknown-warning-option + ) +elseif(CMAKE_CXX_COMPILER_ID MATCHES GNU) + add_compile_options( + -Wall + -Wextra + -Wpedantic + -Wvla + -Wno-noexcept-type + -Wno-format-zero-length + -ftemplate-depth=512 + ) +elseif(MSVC) + add_compile_options( + /W4 + # C4251: 'identifier' : class 'type' needs to have dll-interface to be + # used by clients of class 'type2' + /wd4251 + # C4268: 'identifier' : 'const' static/global data initialized + # with compiler generated default constructor fills the object with zeros + /wd4268 + # C4275: non DLL-interface classkey 'identifier' used as base for + # DLL-interface classkey 'identifier' + /wd4275 + # C4373: virtual function overrides, previous versions of the compiler + # did not override when parameters only differed by const/volatile qualifiers + /wd4373 + # C4503: 'identifier': decorated name length exceeded, name was truncated + /wd4503 + ) +endif() + +if(static_runtime) + if (MSVC) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + else() + include(ucm_flags) + ucm_set_runtime(STATIC) + endif() + set(Boost_USE_MULTITHREADED ON) + set(Boost_USE_STATIC_RUNTIME ON) + set(OPENSSL_MSVC_STATIC_RT ON) +endif() + +add_library(torrent-rasterbar + ${sources} + ${try_signal_sources} + ${libtorrent_include_files} + ${libtorrent_extensions_include_files} + ${libtorrent_aux_include_files} +) + +# C++ 14 support is required +target_compile_features(torrent-rasterbar + PUBLIC + cxx_std_14 + cxx_attribute_deprecated + cxx_binary_literals + cxx_contextual_conversions + cxx_decltype_auto + cxx_digit_separators + cxx_generic_lambdas + cxx_lambda_init_captures + cxx_relaxed_constexpr + cxx_variable_templates +) + +if (BUILD_SHARED_LIBS) + target_compile_definitions(torrent-rasterbar + PRIVATE TORRENT_BUILDING_SHARED + INTERFACE TORRENT_LINKING_SHARED + ) +endif() + +set_target_properties(torrent-rasterbar + PROPERTIES + CXX_VISIBILITY_PRESET "hidden" + VISIBILITY_INLINES_HIDDEN "true" + VERSION ${PROJECT_VERSION} + SOVERSION ${SOVERSION} +) + +target_include_directories(torrent-rasterbar PUBLIC + $ + $ + PRIVATE deps/try_signal +) + +target_compile_definitions(torrent-rasterbar + PUBLIC + $<$:TORRENT_USE_ASSERTS> + BOOST_ASIO_ENABLE_CANCELIO + BOOST_ASIO_NO_DEPRECATED + PRIVATE + TORRENT_BUILDING_LIBRARY + BOOST_EXCEPTION_DISABLE + BOOST_ASIO_HAS_STD_CHRONO +) + +if (NOT WIN32) + target_compile_definitions(torrent-rasterbar + PRIVATE + _FILE_OFFSET_BITS=64 + ) +endif() + +target_link_libraries(torrent-rasterbar + PUBLIC + Threads::Threads +) + +# Unconditional platform-specific settings +if (WIN32) + target_link_libraries(torrent-rasterbar + PUBLIC + bcrypt mswsock ws2_32 iphlpapi + debug dbghelp crypt32 + ) + + add_definitions(-D_WIN32_WINNT=0x0A00) # target Windows 10 or later + + target_compile_definitions(torrent-rasterbar + PUBLIC WIN32_LEAN_AND_MEAN # prevent winsock1 to be included + ) + + if (MSVC) + target_compile_definitions(torrent-rasterbar + PUBLIC + BOOST_ALL_NO_LIB + _SCL_SECURE_NO_DEPRECATE _CRT_SECURE_NO_DEPRECATE # disable bogus deprecation warnings on msvc8 + ) + target_compile_options(torrent-rasterbar + PRIVATE + # allow larger .obj files (with more sections) + /bigobj + # https://learn.microsoft.com/en-us/cpp/build/reference/permissive-standards-conformance?view=msvc-170 + /permissive- + # https://docs.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170 + /utf-8 + # https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ + /Zc:__cplusplus + ) + set_target_properties(torrent-rasterbar PROPERTIES LINK_FLAGS_RELEASE "/OPT:ICF=5 /OPT:REF") + endif() +endif() + +if (ANDROID) + target_link_libraries(torrent-rasterbar PRIVATE ${CMAKE_DL_LIBS}) +endif() + +if (APPLE) + # for ip_notifier + target_link_libraries(torrent-rasterbar PRIVATE "-framework CoreFoundation" "-framework SystemConfiguration") +endif() + +# check if we need to link with libatomic (not needed on MSVC) +if (NOT Windows) + # TODO: migrate to CheckSourceCompiles in CMake >= 3.19 + include(CheckCXXSourceCompiles) + + set(ATOMICS_TEST_SOURCE [=[ + #include + #include + std::atomic x{0}; + int main() { + x.fetch_add(1, std::memory_order_relaxed); + return 0; + } + ]=]) + string(REPLACE "std::atomic" "std::atomic" ATOMICS8_TEST_SOURCE "${ATOMICS_TEST_SOURCE}") + string(REPLACE "std::atomic" "std::atomic" ATOMICS64_TEST_SOURCE "${ATOMICS_TEST_SOURCE}") + + if(APPLE) + set(CMAKE_REQUIRED_FLAGS "-std=c++11") + endif() + check_cxx_source_compiles("${ATOMICS_TEST_SOURCE}" HAVE_CXX_ATOMICS_WITHOUT_LIB) + check_cxx_source_compiles("${ATOMICS8_TEST_SOURCE}" HAVE_CXX_ATOMICS8_WITHOUT_LIB) + check_cxx_source_compiles("${ATOMICS64_TEST_SOURCE}" HAVE_CXX_ATOMICS64_WITHOUT_LIB) + if((NOT HAVE_CXX_ATOMICS_WITHOUT_LIB) OR (NOT HAVE_CXX_ATOMICS8_WITHOUT_LIB) OR (NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB)) + set(CMAKE_REQUIRED_LIBRARIES "atomic") + check_cxx_source_compiles("${ATOMICS_TEST_SOURCE}" HAVE_CXX_ATOMICS_WITH_LIB) + check_cxx_source_compiles("${ATOMICS8_TEST_SOURCE}" HAVE_CXX_ATOMICS8_WITH_LIB) + check_cxx_source_compiles("${ATOMICS64_TEST_SOURCE}" HAVE_CXX_ATOMICS64_WITH_LIB) + if ((NOT HAVE_CXX_ATOMICS_WITH_LIB) OR (NOT HAVE_CXX_ATOMICS8_WITH_LIB) OR (NOT HAVE_CXX_ATOMICS64_WITH_LIB)) + message(FATAL_ERROR "No native support for std::atomic, or libatomic not found!") + else() + message(STATUS "Linking with libatomic for atomics support") + unset(CMAKE_REQUIRED_LIBRARIES) + target_link_libraries(torrent-rasterbar PUBLIC atomic) + endif() + endif() + if(APPLE) + unset(CMAKE_REQUIRED_FLAGS) + endif() +endif() + +feature_option(build_tests "build tests" OFF) +feature_option(build_examples "build examples" OFF) +feature_option(build_tools "build tools" OFF) +feature_option(python-bindings "build python bindings" OFF) +feature_option(python-egg-info "generate python egg info" OFF) +feature_option(python-install-system-dir "Install python bindings to the system installation directory rather than the CMake installation prefix" OFF) + +# these options require existing target +feature_option(dht "enable support for Mainline DHT" ON) +target_optional_compile_definitions(torrent-rasterbar PUBLIC FEATURE NAME deprecated-functions DEFAULT ON + DESCRIPTION "enable deprecated functions for backwards compatibility" DISABLED TORRENT_NO_DEPRECATE) +feature_option(encryption "Enables encryption in libtorrent" ON) +feature_option(exceptions "build with exception support" ON) +feature_option(gnutls "build using GnuTLS instead of OpenSSL" OFF) +target_optional_compile_definitions(torrent-rasterbar PUBLIC FEATURE NAME extensions DEFAULT ON + DESCRIPTION "Enables protocol extensions" DISABLED TORRENT_DISABLE_EXTENSIONS) +target_optional_compile_definitions(torrent-rasterbar PUBLIC FEATURE NAME i2p DEFAULT ON + DESCRIPTION "build with I2P support" DISABLED TORRENT_USE_I2P=0) +target_optional_compile_definitions(torrent-rasterbar PUBLIC FEATURE NAME logging DEFAULT ON + DESCRIPTION "build with logging" DISABLED TORRENT_DISABLE_LOGGING) +target_optional_compile_definitions(torrent-rasterbar PUBLIC FEATURE NAME mutable-torrents DEFAULT ON + DESCRIPTION "Enables mutable torrent support" DISABLED TORRENT_DISABLE_MUTABLE_TORRENTS) +target_optional_compile_definitions(torrent-rasterbar PUBLIC FEATURE NAME streaming DEFAULT ON + DESCRIPTION "Enables support for piece deadline" DISABLED TORRENT_DISABLE_STREAMING) + +if(NOT gnutls) + find_public_dependency(OpenSSL) + set_package_properties(OpenSSL + PROPERTIES + URL "https://www.openssl.org/" + DESCRIPTION "Full-strength general purpose cryptography library" + TYPE RECOMMENDED + PURPOSE "Provides HTTPS support to libtorrent" + ) + + if(TARGET OpenSSL::SSL) + # TODO: needed until https://gitlab.kitware.com/cmake/cmake/issues/19263 is fixed + if(WIN32 AND OPENSSL_USE_STATIC_LIBS) + target_link_libraries(torrent-rasterbar PRIVATE crypt32) + endif() + target_link_libraries(torrent-rasterbar PUBLIC OpenSSL::SSL) + target_compile_definitions(torrent-rasterbar + PUBLIC + TORRENT_USE_OPENSSL + TORRENT_USE_LIBCRYPTO + TORRENT_SSL_PEERS + OPENSSL_NO_SSL2) + endif() +endif() + +if(gnutls OR NOT TARGET OpenSSL::SSL) + find_public_dependency(GnuTLS) + set_package_properties(GnuTLS + PROPERTIES + URL "https://www.gnutls.org/" + DESCRIPTION "GnuTLS is a free software implementation of the TLS and DTLS protocols" + TYPE RECOMMENDED + PURPOSE "Provides HTTPS support to libtorrent" + ) + if(GNUTLS_FOUND) + target_link_libraries(torrent-rasterbar PUBLIC GnuTLS::GnuTLS) + target_compile_definitions(torrent-rasterbar + PUBLIC + TORRENT_USE_GNUTLS + TORRENT_SSL_PEERS) + target_include_directories(torrent-rasterbar PUBLIC + $ + $) + install(DIRECTORY deps/asio-gnutls/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + elseif(gnutls) + message(FATAL_ERROR "GnuTLS library not found") + endif() +endif() + +if (NOT GNUTLS_FOUND AND NOT TARGET OpenSSL::SSL) + if(TARGET OpenSSL::Crypto) + target_link_libraries(torrent-rasterbar PUBLIC OpenSSL::Crypto) + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_USE_LIBCRYPTO) + else() + find_public_dependency(LibGcrypt) + set_package_properties(LibGcrypt + PROPERTIES + URL "https://www.gnupg.org/software/libgcrypt/index.html" + DESCRIPTION "A general purpose cryptographic library" + TYPE RECOMMENDED + PURPOSE "Use GCrypt instead of the built-in functions for RC4 and SHA1" + ) + if (LibGcrypt_FOUND) + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_USE_LIBGCRYPT) + target_link_libraries(torrent-rasterbar PRIVATE LibGcrypt::LibGcrypt) + endif() + endif() +endif() + +if (encryption) + target_sources(torrent-rasterbar PRIVATE include/libtorrent/pe_crypto.hpp src/pe_crypto.cpp) +else() + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_DISABLE_ENCRYPTION) +endif() + +if (dht) + target_sources(torrent-rasterbar PRIVATE + ${libtorrent_kademlia_include_files} + include/libtorrent/aux_/hasher512.hpp + ${kademlia_sources} + ${ed25519_sources} + ) +else() + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_DISABLE_DHT) +endif() + +# Boost +find_public_dependency(Boost REQUIRED) +target_link_libraries(torrent-rasterbar PUBLIC Boost::headers) +if (Boost_MAJOR_VERSION LESS_EQUAL 1 AND Boost_MINOR_VERSION LESS 69) + find_public_dependency(Boost REQUIRED COMPONENTS system) + target_link_libraries(torrent-rasterbar PUBLIC Boost::system) +endif() + +if (exceptions) + if (MSVC) + target_compile_options(torrent-rasterbar PUBLIC /EHsc) + else (MSVC) + target_compile_options(torrent-rasterbar PUBLIC -fexceptions) + endif (MSVC) +else() + if (MSVC) + target_compile_definitions(torrent-rasterbar PUBLIC _HAS_EXCEPTIONS=0) + else (MSVC) + target_compile_options(torrent-rasterbar PUBLIC -fno-exceptions) + endif (MSVC) +endif() + +# developer options +option(developer-options "Activates options useful for a developer") +if(developer-options) + set(asserts "auto" CACHE STRING "use assertions") + set_property(CACHE asserts PROPERTY STRINGS auto on off production system) + if ("${asserts}" MATCHES "on|production|system") + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_USE_ASSERTS=1) + endif() + if ("${asserts}" STREQUAL "production") + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_PRODUCTION_ASSERTS=1) + elseif("${asserts}" STREQUAL "system") + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_USE_SYSTEM_ASSERTS=1) + endif() + + target_optional_compile_definitions(torrent-rasterbar PUBLIC NAME asio-debugging DEFAULT OFF + ENABLED TORRENT_ASIO_DEBUGGING) + target_optional_compile_definitions(torrent-rasterbar PUBLIC NAME picker-debugging DEFAULT OFF + ENABLED TORRENT_DEBUG_REFCOUNTS) + set(invariant-checks "off" CACHE STRING "") + set_property(CACHE invariant-checks PROPERTY STRINGS off on full) + if (invariant-checks MATCHES "on|full") + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_USE_INVARIANT_CHECKS=1) + endif() + if (invariant-checks STREQUAL "full") + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_EXPENSIVE_INVARIANT_CHECKS) + endif() + + target_optional_compile_definitions(torrent-rasterbar PUBLIC NAME utp-log DEFAULT OFF + ENABLED TORRENT_UTP_LOG_ENABLE) + target_optional_compile_definitions(torrent-rasterbar PUBLIC NAME simulate-slow-read DEFAULT OFF + ENABLED TORRENT_SIMULATE_SLOW_READ) + option(debug-iterators "" OFF) + if (debug-iterators) + if (MSVC) + target_compile_definitions(torrent-rasterbar PUBLIC _ITERATOR_DEBUG_LEVEL=2) + endif() + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + target_compile_definitions(torrent-rasterbar PUBLIC _GLIBCXX_DEBUG _GLIBCXX_DEBUG_PEDANTIC) + endif() + endif() + target_optional_compile_definitions(torrent-rasterbar PUBLIC NAME profile-calls DEFAULT OFF + ENABLED TORRENT_PROFILE_CALLS=1) +endif() + +# This is best effort attempt to propagate whether the library was built with +# C++11 or not. It affects the ABI of entry. A client building with C++14 and +# linking against a libtorrent binary built with C++11 can still define +# TORRENT_CXX11_ABI +if ("${CMAKE_CXX_STANDARD}" STREQUAL "11") + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_CXX11_ABI) +endif() + +# There is little to none support for using pkg-config with MSVC and most users won't bother with it. +# However, msys is a linux-like platform on Windows that do support/prefer using pkg-config. +if (NOT MSVC) + generate_and_install_pkg_config_file(torrent-rasterbar libtorrent-rasterbar) +endif() + +include(CheckCXXCompilerFlag) + +add_subdirectory(bindings) + +if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}") + file(RELATIVE_PATH CMAKE_INSTALL_LIBDIR "${CMAKE_INSTALL_PREFIX}" "${CMAKE_INSTALL_LIBDIR}") +endif() + +install(TARGETS torrent-rasterbar EXPORT LibtorrentRasterbarTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) +install(DIRECTORY include/libtorrent DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILES_MATCHING PATTERN "*.h*") + +# === generate a CMake Config File === +include(CMakePackageConfigHelpers) +set(ConfigPackageLocation ${CMAKE_INSTALL_LIBDIR}/cmake/LibtorrentRasterbar) +string(REGEX REPLACE "([^;]+)" "find_dependency(\\1)" _find_dependency_calls "${_package_dependencies}") +string(REPLACE ";" "\n" _find_dependency_calls "${_find_dependency_calls}") + +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/LibtorrentRasterbar/LibtorrentRasterbarConfigVersion.cmake" + VERSION ${libtorrent_VERSION} + COMPATIBILITY AnyNewerVersion +) + +export(EXPORT LibtorrentRasterbarTargets + FILE "${CMAKE_CURRENT_BINARY_DIR}/LibtorrentRasterbar/LibtorrentRasterbarTargets.cmake" + NAMESPACE LibtorrentRasterbar:: +) + +configure_package_config_file(LibtorrentRasterbarConfig.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/LibtorrentRasterbar/LibtorrentRasterbarConfig.cmake" + INSTALL_DESTINATION "${ConfigPackageLocation}" + NO_SET_AND_CHECK_MACRO + NO_CHECK_REQUIRED_COMPONENTS_MACRO +) + +install(EXPORT LibtorrentRasterbarTargets + NAMESPACE + LibtorrentRasterbar:: + DESTINATION + ${ConfigPackageLocation} +) +install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/LibtorrentRasterbar/LibtorrentRasterbarConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/LibtorrentRasterbar/LibtorrentRasterbarConfigVersion.cmake" + DESTINATION + ${ConfigPackageLocation} +) + +install( + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/examples/cmake/FindLibtorrentRasterbar.cmake + DESTINATION + ${CMAKE_INSTALL_DATADIR}/cmake/Modules +) + +if (MSVC) + set_target_properties(torrent-rasterbar + PROPERTIES + PDB_NAME torrent-rasterbar + PDB_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} + COMPILE_PDB_NAME torrent-rasterbar + COMPILE_PDB_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} + ) + + if (static_runtime) + set(PDB_INSTALL_DIR lib) + else() + set(PDB_INSTALL_DIR bin) + endif() + + install( + FILES + ${CMAKE_BINARY_DIR}/torrent-rasterbar.pdb + DESTINATION + ${PDB_INSTALL_DIR} + CONFIGURATIONS + Debug RelWithDebInfo + OPTIONAL + ) +endif() + +# === build tools === +if (build_tools) + add_subdirectory(tools) +endif() + +# === build examples === +if (build_examples) + add_subdirectory(examples) +endif() + +# === build tests === +if(build_tests) + enable_testing() + # this will make some internal functions available in the DLL interface + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_EXPORT_EXTRA) + add_subdirectory(test) +endif() + +feature_summary(DEFAULT_DESCRIPTION WHAT ALL) diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..e747efb --- /dev/null +++ b/COPYING @@ -0,0 +1,28 @@ +Copyright (c) 2003-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of Rasterbar Software nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..c98ab02 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,2235 @@ + +2.0.11 released + + * validate add_torrent_params::save_path at run-time + * use stricter rules for what filenames are valid on Android + * fix applying IP filter to DHT traffic (HanabishiRecca) + * fix race condition when cancelling requests after becoming a seed + * fix performance bug in the file pool, evicting MRU instead of LRU (HanabishiRecca) + * fix bug where file_progress could sometimes be reported as >100% + * don't hint FADV_RANDOM on posix systems. May improve seeding performance + * allow boost connect while checking resume data if no_verify_files flag is set + * fix BEP-40 peer priority for IPv6 + * limit piece size in torrent creator + * fix file pre-allocation when changing file priority (HanabishiRecca) + * fix uTP issue where closing the connection could corrupt the payload + * apply DSCP/TOS to sockets before initiating the TCP connection + * assume copy_file_range() exists on linux (unless old glibc) + * fix issue where set_piece_deadline() did not correctly post read_piece_alert + * fix integer overflow in piece picker + * torrent_status::num_pieces counts pieces passed hash check, as documented + * check settings_pack::max_out_request_queue before performance alert + * add announce_port setting to override the port announced to trackers + +2.0.10 released + + * allow on_unknown_torrent method in the absence of active torrents (new plugin feature added) + * add feature to async_move_storage() to not move files + * fix reject resume data if it contains mismatching info hashes + * fix clear the candidate_cache when clear peer_list + * fix missing python converter for dht::announce_flags_t + +2.0.9 released + + * fix issue with web seed connections when they close and re-open + * fallocate() not supported is not a fatal error + * fix proxying of IPv6 connections via IPv4 proxy + * treat CGNAT address range as local IPs + * add stricter checking of piece layers when loading torrents + * add stricter checking of v1 and v2 hashes being consistent + * cache failed DNS lookups as well as successful ones + * add an i2p torrent state to control interactions with clear swarms + * fix i2p SAM protocol parsing of quoted messages + * expose i2p peer destination in peer_info + * fix i2p tracker announces + * fix issue with read_piece() stopping torrent on pieces not yet downloaded + * improve handling of allow_i2p_mixed setting to work for magnet links + * fix web seed request for renamed single-file torrents + * fix issue where web seeds could disappear from resume data + * extend save_resume with additional conditional flags + * fix issue with retrying trackers in tiers > 0 + * fix last_upload and last_download resume data fields to use posix time + * improve error messages for no_connect_privileged_ports, by untangle it from the port filter + * fix I2P issue introduced in 2.0.0 + * add async tracker status query, post_trackers() + * add async torrent status query, post_status() + * support loading version 2 of resume data format + * fix issue with odd piece sizes + * add async piece availability query, post_piece_availability() + * add async download queue query, post_download_queue() + * add async file_progress query, post_file_progress() + * add async peer_info query, post_peer_info() + +2.0.8 released + + * fix uTP streams timing out instead of closing cleanly + * add write_torrent_file_buf() overload for generating .torrent files + * add create_torrent::generate_buf() function to generate into a buffer + * fix copy_file when the file ends with a sparse region + * uTP performance, fix packet loss when sending is stalled + * fix trackers being stuck after session pause/resume + * fix bug in hash_picker with empty files + * uTP performance, prevent premature timeouts/resends + * add option to not memory map files below a certain size + * settings_pack now returns default values when queried for missing settings + * fix copy_file fall-back when SEEK_HOL/SEEK_DATA is not supported + * improve error reporting from file copy and move + * tweak pad file placement to match reference implementation (tail-padding) + * uTP performance, more lenient nagle's algorithm to always allow one outstanding undersized packet + * uTP performance, piggy-back held back undersized packet with ACKs + * uTP performance, don't send redundant deferred ACKs + * support incoming SOCKS5 packets with hostnames as source address, for UDP trackers + * ignore duplicate network interface change notifications on linux + * fix total_want/want accounting when forcing a recheck + * fix merging metadata with magnet links added on top of existing torrents + * add torrent_flag to default all file priorities to dont_download + * fix &so= feature in magnet links + * improve compatibility of SOCKS5 UDP ASSOCIATE + * fix madvise range for flushing cache in mmap_storage + * open files with no_cache set in O_SYNC mode + +* 2.0.7 released + + * fix issue in use of copy_file_range() on linux + * avoid open-file race in the file_view_pool + * fix issue where stop-when-ready would not close files + * fix issue with duplicate hybrid torrent via separate v1 and v2 magnet links + * added new function to load torrent files, load_torrent_*() + * support sync_file_range() on linux + * fix issue in write_torrent_file() when file size is exactly piece size + * fix file_num_blocks() and file_num_pieces() for empty files + * add new overload to make_magnet_uri() + * add missing protocol version to tracker_reply_alert and tracker_error_alert + * fix privilege issue with SetFileValidData() + * add asynchronous overload of torrent_handle::add_piece() + * default to a single hashing thread, for full checks + * Fix bug when checking files and the first piece is invalid + +* 2.0.6 released + + * fix issue creating a v2 torrent from torrent_info containing an empty file + * make recheck files also update which files use partfile + * add write_through disk_io_write_mode, which flushes pieces to disk immediately + * improve copy file function to preserve sparse regions (when supported) + * add function to truncate over-sized files part of a torrent + * fix directory creation on windows shared folders + * add flag to make add_files() not record file attributes + * deprecate (unused) allow_partial_disk_writes settings + * fix disk-full error reporting in mmap_disk_io + * fixed similar-torrents feature for v2 torrents + * fix potential unbounded recursion in add_completed_job, in disk I/O + * deprecated (unused) volatile_read_cache setting + * fix part files being marked as hidden on windows + +* 2.0.5 released + + * on windows, explicitly flush memory mapped files periodically + * fix build with WolfSSL + * fix issue where incoming uTP connections were not accepted over SOCKS5 + * fix several issues in handling of checking files of v2 torrents, esp. from magnet links + * make the token limit when parsing metadata from magnet files configurable + * fix issue with stalled pieces on disk full errors + * fix missing python binding for file_progress_flags + * fix torrent_file_with_hashes() to fail when we don't have the piece layers + * restore path character encoding conversion for non UTF-8 locales on linux + * fix use-after-free bug in make_magnet_uri + * add write_torrent_file() to produce a .torrent file from add_torrent_params + * allow loading v2 .torrent files without piece layer + * fix issue with adding v2 torrents with invalid file root hash + +* 2.0.4 released + + * fix piece picker bug causing double-picks with prefer-contiguous enabled + * expose session_params in python bindings + * fix (deprecated) use of add_torrent_params::info_hash + * fix issue creating and loading v2 torrents with empty files. Improves + conformance to BEP52 reference implementation + +* 2.0.3 released + + * add new torrent_file_with_hashes() which includes piece layers for + creating .torrent files + * add file_prio_alert, posted when file priorities are updated + * fix issue where set_piece_hashes() would not propagate file errors + * add missing python binding for event_t + * add work-around for systems without fseeko() (such as Android) + * add convenience header libtorrent/libtorrent.hpp + * increase default max_allowed_in_request_queue + * fix loading non-ascii filenames on windows with torrent_info constructor (2.0 regression) + * add std::hash<> specialization for info_hash_t + * fix integer overflow in hash_picker and properly restrict max file sizes in torrents + * strengthen SSRF mitigation for web seeds + +* 2.0.2 released + + * add v1() and v2() functions to torrent_info + * fix piece_layers() to work for single-piece files + * fix python binding regression in session constructor flags + * fix unaligned piece requests in mmap_storage + * improve client_data_t ergonomics + * fix issue with concurrent access to part files + +* 2.0.1 released + + * fix attribute in single-file v2 torrent creation + * fix padding for empty files in v2 torrent creation + * add function to ask a file_storage whether it's v2 or not + * fix mtime field when creating single-file v2 torrents + * fix performance regression in checking files + * disable use of SetFileValidData() by default (windows). A new setting + allows enabling it + +2.0 released + + * dropped dependency on iconv + * deprecate set_file_hash() in torrent creator, as it's superseded by v2 torrents + * deprecate mutable access to info_section in torrent_info + * removed deprecated lazy_entry/lazy_bdecode + * stats_alert deprecated + * remove bittyrant choking algorithm + * update userdata in add_torrent_params to be type-safe and add to torrent_handle + * add ip_filter to session_params + * added support for wolfSSL for SHA-1 hash and HTTPS (no Torrents over SSL) + * requires OpenSSL minimum version 1.0.0 with SNI support + * deprecated save_state() and load_state() on session in favour of new + write_session_params() and read_session_params() + * added support for BitTorrent v2 (see docs/upgrade_to_2.0.html) + * create_torrent() pad_file_limit parameter removed + * create_torrent() merkle- and optimize-alignment flags removed + * merkle_tree removed from add_torrent_params + * announce_entry expose information per v1 and v2 info-hash announces + * deprecated sha1_hash info_hash members on torrent_removed_alert, + torrent_deleted_alert, torrent_delete_failed_alert and add_torrent_params + * undeprecate error_file_metadata for torrent errors related to its metadata + * remove support for adding a torrent under a UUID (used for previous RSS support) + * remove deprecated feature to add torrents by file:// URL + * remove deprecated feature to download .torrent file from URL + * requires boost >= 1.66 to build + * update networking API to networking TS compatible boost.asio + * overhauled disk I/O subsystem to use memory mapped files (where available) + * libtorrent now requires C++14 to build + * added support for GnuTLS for HTTPS and torrents over SSL + + + * fix issue where stop-when-ready would not close files + * uTP performance, fix packet loss when sending is stalled + * uTP performance, prevent premature timeouts/resends + * uTP performance, more lenient nagle's algorithm to always allow one outstanding undersized packet + * uTP performance, piggy-back held back undersized packet with ACKs + * uTP performance, don't send redundant deferred ACKs + * fix wanted_done/done accounting when force-rechecking + * expose userdata via torrent_handle (back-port from 2.0) + * fix renaming of filenames that are too long for the filesystem + * made UPnP and LSD code avoid using select_reactor (to work around an issue on windows in boost.asio < 1.80) + +1.2.17 released + + * fixed tracker connections spinning when hostname lookups stall + * fixed error in pkg-config file generation in Jamfile + * improve backwards compatibility with loading magnet link resume files + * fix bind-to-device for tracker announces and UPnP + * rename peer_tos setting to peer_dscp + * fix bdecode support for large strings (>= 100 MB) + +1.2.16 released + + * send User-Agent field in anonymous mode + * fix python binding for settings_pack conversion + * fix DHT announce timer issue + * use DSCP_TRAFFIC_TYPE socket option on windows + * update default ToS setting according to RFC 8622 + * keep trying to announce to trackers even when all fail + * don't disable announcing from local endpoints because of temporary failures + * fix issue in parsing UPnP XML response with multiple forwarding services + +1.2.15 released + + * cache DNS lookups for SOCKS5 proxy + * fix stalled pieces on disk-full errors + * fix build configuration issue on NetBSD, OpenBSD and DragonFly + * make UTF-8 sanitization a bit stricter. This will re-write invalid UTF-8 + code points encoding surrogate pairs + * fix restoring last_seen_complete from resume data + * fix issue on MacOS where the DHT was not restarted on a network-up notification + * make remove_torrent flags be treated as flags (instead of an enum) + +1.2.14 released + + * improve handling of seed flag in PEX messages + * fix issue of accruing unlimited DHT node candidates when DHT is disabled + * fix bug in parsing chunked encoding + * fix incorrect reporting of active_duration when entering graceful-pause + * fix python binding for functions taking string_view + * fix python binding for torrent_info constructor overloads + * issue python deprecation warnings for some deprecated functions in the python bindings + * fix python binding for torrent_info::add_url_seed, add_tracker and add_http_seed + +1.2.13 released + + * Use /etc/ssl/cert.pem to validate HTTPS connections on MacOS + * allow no-interest timeouts of peer connections before all connections slots are full + * fix issue where a DHT message would count as an incoming connection + * fix issue when failing to parse outgoing_interfaces setting + * fix super-seeding issue that could cause a segfault + * fix data race in python binding of session::get_torrent_status() + * fix need_save_resume_data() for renaming files, share-mode, upload-mode, + disable- pex, lsd, and dht. + * fix incoming TCP connections when using tracker-only proxy + * fix issue with paths starting with ./ + * fix integer overflow when setting a high DHT upload rate limit + * improve Path MTU discovery logic in uTP + * fix overflow issue when rlimit_nofile is set to infinity + * fix issue in python binding interpreting int settings > INT_MAX + * Fix cxxflags and linkflags injection via environment variables + +1.2.12 released + + * fix loading of DHT node ID from previous session on startup + * use getrandom(), when available, and fall back to /dev/urandom + * fix python binding for "value" in dht put alerts + * fix bug in python binding for dht_put_mutable_item + * fix uTP issue acking FIN packets + * validate HTTPS certificates by default (trackers and web seeds) + * load SSL certificates from windows system certificate store, to authenticate trackers + * introduce mitigation for Server Side Request Forgery in tracker and web seed URLs + * fix error handling for pool allocation failure + +1.2.11 released + + * fix issue with moving the session object + * deprecate torrent_status::allocating. This state is no longer used + * fix bug creating torrents with symbolic links + * remove special case to save metadata in resume data unconditionally when added through magnet link + * fix bugs in mutable-torrent support (reusing identical files from different torrents) + * fix incorrectly inlined move-assignment of file_storage + * add session::paused flag, and the ability to construct a session in paused mode + * fix session-pause causing tracker announces to fail + * fix peer-exchange flags bug + * allow saving resume data before metadata has been downloaded (for magnet links) + * record blocks in the disk queue as downloaded in the resume data + * fix bug in set_piece_deadline() when set in a zero-priority piece + * fix issue in URL parser, causing issues with certain tracker URLs + * use a different error code than host-unreachable, when skipping tracker announces + +1.2.10 released + + * fix regression in python binding for move_storage() + * improve stat_file() performance on Windows + * fix issue with loading invalid torrents with only 0-sized files + * fix to avoid large stack allocations + +1.2.9 released + + * add macro TORRENT_CXX11_ABI for clients building with C++14 against + libtorrent build with C++11 + * refreshed m4 scripts for autotools + * removed deprecated wstring overloads on non-windows systems + * drop dependency on Unicode's ConvertUTF code (which had a license + incompatible with Debian) + * fix bugs exposed on big-endian systems + * fix detection of hard-links not being supported by filesystem + * fixed resume data regression for seeds with prio 0 files + +1.2.8 released + + * validate UTF-8 encoding of client version strings from peers + * don't time out tracker announces as eagerly while resolving hostnames + * fix NAT-PMP shutdown issue + * improve hostname lookup by merging identical lookups + * fix network route enumeration for large routing tables + * fixed issue where pop_alerts() could return old, invalid alerts + * fix issue when receiving have-all message before the metadata + * don't leave lingering part files handles open + * disallow calling add_piece() during checking + * fix incorrect filename truncation at multi-byte character + * always announce listen port 1 when using a proxy + +1.2.7 released + + * add set_alert_fd in python binding, to supersede set_alert_notify + * fix bug in part files > 2 GiB + * add function to clear the peer list for a torrent + * fix resume data functions to save/restore more torrent flags + * limit number of concurrent HTTP announces + * fix queue position for force_rechecking a torrent that is not auto-managed + * improve rate-based choker documentation, and minor tweak + * undeprecate upnp_ignore_nonrouters (but referring to devices on our subnet) + * increase default tracker timeout + * retry failed socks5 server connections + * allow UPnP lease duration to be changed after device discovery + * fix IPv6 address change detection on Windows + +1.2.6 released + + * fix peer timeout logic + * simplify proxy handling. A proxy now overrides listen_interfaces + * fix issues when configured to use a non-default choking algorithm + * fix issue in reading resume data + * revert NXDOMAIN change from 1.2.4 + * don't open any listen sockets if listen_interfaces is empty or misconfigured + * fix bug in auto disk cache size logic + * fix issue with outgoing_interfaces setting, where bind() would be called twice + * add build option to disable share-mode + * support validation of HTTPS trackers + * deprecate strict super seeding mode + * make UPnP port-mapping lease duration configurable + * deprecate the bittyrant choking algorithm + * add build option to disable streaming + +1.2.5 release + + * announce port=1 instead of port=0, when there is no listen port + * fix LSD over IPv6 + * support TCP_NOTSENT_LOWAT on Linux + * fix correct interface binding of local service discovery multicast + * fix issue with knowing which interfaces to announce to trackers and DHT + * undeprecate settings_pack::dht_upload_rate_limit + +1.2.4 release + + * fix binding TCP and UDP sockets to the same port, when specifying port 0 + * fix announce_to_all_trackers and announce_to_all_tiers behavior + * fix suggest_read_cache setting + * back-off tracker hostname looksups resulting in NXDOMAIN + * lower SOCKS5 UDP keepalive timeout + * fix external IP voting for multi-homed DHT nodes + * deprecate broadcast_lsd setting. Just use multicast + * deprecate upnp_ignore_nonrouters setting + * don't attempt sending event=stopped if event=start never succeeded + * make sure &key= stays consistent between different source IPs (as mandated by BEP7) + * fix binding sockets to outgoing interface + * add new socks5_alert to trouble shoot SOCKS5 proxies + +1.2.3 release + + * fix erroneous event=completed tracker announce when checking files + * promote errors in parsing listen_interfaces to post listen_failed_alert + * fix bug in protocol encryption/obfuscation + * fix buffer overflow in SOCKS5 UDP logic + * fix issue of rapid calls to file_priority() clobbering each other + * clear tracker errors on success + * optimize setting with unlimited unchoke slots + * fixed restoring of trackers, comment, creation date and created-by in resume data + * fix handling of torrents with too large pieces + * fixed division by zero in anti-leech choker + * fixed bug in torrent_info::swap + +1.2.2 release + + * fix cases where the disable_hash_checks setting was not honored + * fix updating of is_finished torrent status, when changing piece priorities + * fix regression in &left= reporting when adding a seeding torrent + * fix integer overflow in http parser + * improve sanitation of symlinks, to support more complex link targets + * add DHT routing table affinity for BEP 42 nodes + * add torrent_info constructor overloads to control torrent file limits + * feature to disable DHT, PEX and LSD per torrent + * fix issue where trackers from magnet links were not included in create_torrent() + * make peer_info::client a byte array in python binding + * pick contiguous pieces from peers with high download rate + * fix error handling of moving storage to a drive letter that isn't mounted + * fix HTTP Host header when using proxy + +1.2.1 release + + * add dht_pkt_alert and alerts_dropped_alert to python bindings + * fix python bindings for block_uploaded_alert + * optimize resolving duplicate filenames in loading torrent files + * fix python binding of dht_settings + * tighten up various input validation checks + * fix create_torrent python binding + * update symlinks to conform to BEP 47 + * fix python bindings for peer_info + * support creating symlinks, for torrents with symlinks in them + * fix error in seed_mode flag + * support magnet link parameters with number suffixes + * consistently use "lt" namespace in examples and documentation + * fix Mingw build to use native cryptoAPI + * uPnP/NAT-PMP errors no longer set the client's advertised listen port to zero + +1.2 release + + * requires boost >= 1.58 to build + * tweak heuristic of how to interpret url seeds in multi-file torrents + * support &ipv4= tracker argument for private torrents + * renamed debug_notification to connect_notification + * when updating listen sockets, only post alerts for new ones + * deprecate anonymous_mode_alert + * deprecated force_proxy setting (when set, the proxy is always used) + * add support for Port Control Protocol (PCP) + * deliver notification of alerts being dropped via alerts_dropped_alert + * deprecated alert::progress_notification alert category, split into + finer grained categories + * update plugin interface functions for improved type-safety + * implemented support magnet URI extension, select specific file indices + for download, BEP53 + * make tracker keys multi-homed. remove set_key() function on session. + * add flags()/set_flags()/unset_flags() to torrent_handle, deprecate individual functions + * added alert for block being sent to the send buffer + * drop support for windows compilers without std::wstring + * implemented support for DHT info hash indexing, BEP51 + * removed deprecated support for file_base in file_storage + * added support for running separate DHT nodes on each network interface + * added support for establishing UTP connections on any network interface + * added support for sending tracker announces on every network interface + * introduce "lt" namespace alias + * need_save_resume_data() will no longer return true every 15 minutes + * make the file_status interface explicitly public types + * added resolver_cache_timeout setting for internal host name resolver + * make parse_magnet_uri take a string_view instead of std::string + * deprecate add_torrent_params::url field. use parse_magnet_uri instead + * optimize download queue management + * deprecated (undocumented) file:// urls + * add limit for number of web seed connections + * added support for retrieval of DHT live nodes + * complete UNC path support + * add packets pool allocator + * remove disk buffer pool allocator + * fix last_upload and last_download overflow after 9 hours in past + * python binding add more add_torrent_params fields and an invalid key check + * introduce introduce distinct types for peer_class_t, piece_index_t and + file_index_t. + * fix crash caused by empty bitfield + * removed disk-access-log build configuration + * removed mmap_cache feature + * strengthened type safety in handling of piece and file indices + * deprecate identify_client() and fingerprint type + * make sequence number for mutable DHT items backed by std::int64_t + * tweaked storage_interface to have stronger type safety + * deprecate relative times in torrent_status, replaced by std::chrono::time_point + * refactor in alert types to use more const fields and more clear API + * changed session_stats_alert counters type to signed (std::int64_t) + * remove torrent eviction/ghost torrent feature + * include target in DHT lookups, when queried from the session + * improve support for HTTP redirects for web seeds + * use string_view in entry interface + * deprecate "send_stats" property on trackers (since lt_tracker extension has + been removed) + * remove deprecate session_settings API (use settings_pack instead) + * improve file layout optimization when creating torrents with padfiles + * remove remote_dl_rate feature + * source code migration from boost::shared_ptr to std::shared_ptr + * storage_interface API changed to use span and references + * changes in public API to work with std::shared_ptr + * extensions API changed to use span and std::shared_ptr + * plugin API changed to handle DHT requests using string_view + * removed support for lt_trackers and metadata_transfer extensions + (pre-dating ut_metadata) + * support windows' CryptoAPI for SHA-1 + * separated ssl and crypto options in build + * remove lazy-bitfield feature + * simplified suggest-read-cache feature to not depend on disk threads + * removed option to disable contiguous receive buffers + * deprecated public to_hex() and from_hex() functions + * separated address and port fields in listen alerts + * added support for parsing new x.pe parameter from BEP 9 + * peer_blocked_alert now derives from peer_alert + * transitioned exception types to system_error + * made alerts move-only + * move files one-by-one when moving storage for a torrent + * removed RSS support + * removed feature to resolve country for peers + * added support for BEP 32, "IPv6 extension for DHT" + * overhauled listen socket and UDP socket handling, improving multi-home + support and bind-to-device + * resume data is now communicated via add_torrent_params objects + * added new read_resume_data()/write_resume_data functions to write bencoded, + backwards compatible resume files + * removed deprecated fields from add_torrent_params + * deprecate "resume_data" field in add_torrent_params + * improved support for bind-to-device + * deprecated ssl_listen, SSL sockets are specified in listen_interfaces now + * improved support for listening on multiple sockets and interfaces + * resume data no longer has timestamps of files + * require C++11 to build libtorrent + + * replace use of boost-endian with boost-predef + +1.1.12 release + + * uTP performance fixes + +1.1.11 release + + * fix move_storage with save_path with a trailing slash + * fix tracker announce issue, advertising port 0 in secondary IPv6 announce + * fix missing boost/noncopyable.hpp includes + * fix python binding for torrent_info::creation_date() + +1.1.10 release + + * fix issue in udp_socket with unusual socket failure + * split progress_notification alert category into file-, piece- and block progress + * utp close-reason fix + * exposed default add_torrent_params flags to python bindings + * fix redundant flushes of partfile metadata + * add option to ignore min-interval from trackers on force-reannounce + * raise default setting for active_limit + * fall back to copy+remove if rename_file fails + * improve handling of filesystems not supporting fallocate() + * force-proxy no longer disables DHT + * improve connect-boost feature, to make new torrents quickly connect peers + +1.1.9 release + + * save both file and piece priorities in resume file + * added missing stats_metric python binding + * uTP connections are no longer exempt from rate limits by default + * fix exporting files from partfile while seeding + * fix potential deadlock on Windows, caused by performing restricted + tasks from within DllMain + * fix issue when subsequent file priority updates cause torrent to stop + +1.1.8 release + + * coalesce reads and writes by default on windows + * fixed disk I/O performance of checking hashes and creating torrents + * fix race condition in part_file + * fix part_file open mode compatibility test + * fixed race condition in random number generator + * fix race condition in stat_cache (disk storage) + * improve error handling of failing to change file priority + The API for custom storage implementations was altered + * set the hidden attribute when creating the part file + * fix tracker announces reporting more data downloaded than the size of the torrent + * fix recent regression with force_proxy setting + +1.1.7 release + + * don't perform DNS lookups for the DHT bootstrap unless DHT is enabled + * fix issue where setting file/piece priority would stop checking + * expose post_dht_stats() to python binding + * fix backwards compatibility to downloads without partfiles + * improve part-file related error messages + * fix reporting &redundant= in tracker announces + * fix tie-break in duplicate peer connection disconnect logic + * fix issue with SSL tracker connections left in CLOSE_WAIT state + * defer truncating existing files until the first time we write to them + * fix issue when receiving a torrent with 0-sized padfiles as magnet link + * fix issue resuming 1.0.x downloads with a file priority 0 + * fix torrent_status::next_announce + * fix pad-file scalability issue + * made coalesce_reads/coalesce_writes settings take effect on linux and windows + * use unique peer_ids per connection + * fix iOS build on recent SDK + * fix tracker connection bind issue for IPv6 trackers + * fix error handling of some merkle torrents + * fix error handling of unsupported hard-links + +1.1.6 release + + * deprecate save_encryption_settings (they are part of the normal settings) + * add getters for peer_class_filter and peer_class_type_filter + * make torrent_handler::set_priority() to use peer_classes + * fix support for boost-1.66 (requires C++11) + * fix i2p support + * fix loading resume data when in seed mode + * fix part-file creation race condition + * fix issue with initializing settings on session construction + * fix issue with receiving interested before metadata + * fix IPv6 tracker announce issue + * restore path sanitization behavior of ":" + * fix listen socket issue when disabling "force_proxy" mode + * fix full allocation failure on APFS + +1.1.5 release + + * fix infinite loop when parsing certain invalid magnet links + * fix parsing of torrents with certain invalid filenames + * fix leak of torrent_peer objects (entries in peer_list) + * fix leak of peer_class objects (when setting per-torrent rate limits) + * expose peer_class API to python binding + * fix integer overflow in whole_pieces_threshold logic + * fix uTP path MTU discovery issue on windows (DF bit was not set correctly) + * fix python binding for torrent_handle, to be hashable + * fix IPv6 tracker support by performing the second announce in more cases + * fix utf-8 encoding check in torrent parser + * fix infinite loop when parsing maliciously crafted torrents + * fix invalid read in parse_int in bdecoder (CVE-2017-9847) + * fix issue with very long tracker- and web seed URLs + * don't attempt to create empty files on startup, if they already exist + * fix force-recheck issue (new files would not be picked up) + * fix inconsistency in file_priorities and override_resume_data behavior + * fix paused torrents not generating a state update when their ul/dl rate + transitions to zero + +1.1.4 release + + * corrected missing const qualifiers on bdecode_node + * fix changing queue position of paused torrents (1.1.3 regression) + * fix re-check issue after move_storage + * handle invalid arguments to set_piece_deadline() + * move_storage did not work for torrents without metadata + * improve shutdown time by only announcing to trackers whose IP we know + * fix python3 portability issue in python binding + * delay 5 seconds before reconnecting socks5 proxy for UDP ASSOCIATE + * fix NAT-PMP crash when removing a mapping at the wrong time + * improve path sanitization (filter unicode text direction characters) + * deprecate partial_piece_info::piece_state + * bind upnp requests to correct local address + * save resume data when removing web seeds + * fix proxying of https connections + * fix race condition in disk I/O storage class + * fix http connection timeout on multi-homed hosts + * removed dependency on boost::uintptr_t for better compatibility + * fix memory leak in the disk cache + * fix double free in disk cache + * forward declaring libtorrent types is discouraged. a new fwd.hpp header is provided + +1.1.3 release + + * removed (broken) support for incoming connections over socks5 + * restore announce_entry's timestamp fields to posix time in python binding + * deprecate torrent_added_alert (in favor of add_torrent_alert) + * fix python binding for parse_magnet_uri + * fix minor robustness issue in DHT bootstrap logic + * fix issue where torrent_status::num_seeds could be negative + * document deprecation of dynamic loading/unloading of torrents + * include user-agent in tracker announces in anonymous_mode for private torrents + * add support for IPv6 peers from udp trackers + * correctly URL encode the IPv6 argument to trackers + * fix default file pool size on windows + * fix bug where settings_pack::file_pool_size setting was not being honored + * add feature to periodically close files (to make windows clear disk cache) + * fix bug in torrent_handle::file_status + * fix issue with peers not updated on metadata from magnet links + +1.1.2 release + + * default TOS marking to 0x20 + * fix invalid access when leaving seed-mode with outstanding hash jobs + * fix ABI compatibility issue introduced with preformatted entry type + * add web_seed_name_lookup_retry to session_settings + * slightly improve proxy settings backwards compatibility + * add function to get default settings + * updating super seeding would include the torrent in state_update_alert + * fix issue where num_seeds could be greater than num_peers in torrent_status + * finished non-seed torrents can also be in super-seeding mode + * fix issue related to unloading torrents + * fixed finished-time calculation + * add missing min_memory_usage() and high_performance_seed() settings presets to python + * fix stat cache issue that sometimes would produce incorrect resume data + * storage optimization to peer classes + * fix torrent name in alerts of builds with deprecated functions + * make torrent_info::is_valid() return false if torrent failed to load + * fix per-torrent rate limits for >256 peer classes + * don't load user_agent and peer_fingerprint from session_state + * fix file rename issue with name prefix matching torrent name + * fix division by zero when setting tick_interval > 1000 + * fix move_storage() to its own directory (would delete the files) + * fix socks5 support for UDP + * add setting urlseed_max_request_bytes to handle large web seed requests + * fix python build with CC/CXX environment + * add trackers from add_torrent_params/magnet links to separate tiers + * fix resumedata check issue with files with priority 0 + * deprecated mmap_cache feature + * add utility function for generating peer ID fingerprint + * fix bug in last-seen-complete + * remove file size limit in torrent_info filename constructor + * fix tail-padding for last file in create_torrent + * don't send user-agent in metadata http downloads or UPnP requests when + in anonymous mode + * fix internal resolve links lookup for mutable torrents + * hint DHT bootstrap nodes of actual bootstrap request + +1.1.1 release + + * update puff.c for gzip inflation (CVE-2016-7164) + * add dht_bootstrap_node a setting in settings_pack (and add default) + * make pad-file and symlink support conform to BEP47 + * fix piece picker bug that could result in division by zero + * fix value of current_tracker when all tracker failed + * deprecate lt_trackers extension + * remove load_asnum_db and load_country_db from python bindings + * fix crash in session::get_ip_filter when not having set one + * fix filename escaping when repairing torrents with broken web seeds + * fix bug where file_completed_alert would not be posted unless file_progress + had been queries by the client + * move files one-by-one when moving storage for a torrent + * fix bug in enum_net() for BSD and Mac + * fix bug in python binding of announce_entry + * fixed bug related to flag_merge_resume_http_seeds flag in add_torrent_params + * fixed inverted priority of incoming piece suggestions + * optimize allow-fast logic + * fix issue where FAST extension messages were not used during handshake + * fixed crash on invalid input in http_parser + * upgraded to libtommath 1.0 + * fixed parsing of IPv6 endpoint with invalid port character separator + * added limited support for new x.pe parameter from BEP 9 + * fixed dht stats counters that weren't being updated + * make sure add_torrent_alert is always posted before other alerts for + the torrent + * fixed peer-class leak when settings per-torrent rate limits + * added a new "preformatted" type to bencode entry variant type + * improved Socks5 support and test coverage + * fix set_settings in python binding + * Added missing alert categories in python binding + * Added dht_get_peers_reply_alert alert in python binding + * fixed updating the node id reported to peers after changing IPs + +1.1.0 release + + * improve robustness and performance of uTP PMTU discovery + * fix duplicate ACK issue in uTP + * support filtering which parts of session state are loaded by load_state() + * deprecate support for adding torrents by HTTP URL + * allow specifying which tracker to scrape in scrape_tracker + * tracker response alerts from user initiated announces/scrapes are now + posted regardless of alert mask + * improve DHT performance when changing external IP (primarily affects + bootstrapping). + * add feature to stop torrents immediately after checking files is done + * make all non-auto managed torrents exempt from queuing logic, including + checking torrents. + * add option to not proxy tracker connections through proxy + * removed sparse-regions feature + * support using 0 disk threads (to perform disk I/O in network thread) + * removed deprecated handle_alert template + * enable logging build config by default (but alert mask disabled by default) + * deprecated RSS API + * experimental support for BEP 38, "mutable torrents" + * replaced lazy_bdecode with a new bdecoder that's a lot more efficient + * deprecate time functions, expose typedefs of boost::chrono in the + libtorrent namespace instead + * deprecate file_base feature in file_storage/torrent_info + * changed default piece and file priority to 4 (previously 1) + * improve piece picker support for reverse picking (used for snubbed peers) + to not cause priority inversion for regular peers + * improve piece picker to better support torrents with very large pieces + and web seeds. (request large contiguous ranges, but not necessarily a + whole piece). + * deprecated session_status and session::status() in favor of performance + counters. + * improve support for HTTP where one direction of the socket is shut down. + * remove internal fields from web_seed_entry + * separate crypto library configuration and whether to support + bittorrent protocol encryption + * simplify bittorrent protocol encryption by just using internal RC4 + implementation. + * optimize copying torrent_info and file_storage objects + * cancel non-critical DNS lookups when shutting down, to cut down on + shutdown delay. + * greatly simplify the debug logging infrastructure. logs are now delivered + as alerts, and log level is controlled by the alert mask. + * removed auto_expand_choker. use rate_based_choker instead + * optimize UDP tracker packet handling + * support SSL over uTP connections + * support web seeds that resolve to multiple IPs + * added auto-sequential feature. download well-seeded torrents in-order + * removed built-in GeoIP support (this functionality is orthogonal to + libtorrent) + * deprecate proxy settings in favor of regular settings + * deprecate separate settings for peer protocol encryption + * support specifying listen interfaces and outgoing interfaces as device + names (eth0, en2, tun0 etc.) + * support for using purgrable memory as disk cache on Mac OS. + * be more aggressive in corking sockets, to coalesce messages into larger + packets. + * pre-emptively unchoke peers to save one round-trip at connection start-up. + * add session constructor overload that takes a settings_pack + * torrent_info is no longer an intrusive_ptr type. It is held by shared_ptr. + This is a non-backwards compatible change + * move listen interface and port to the settings + * move use_interfaces() to be a setting + * extend storage interface to allow deferred flushing and flush the part-file + metadata periodically + * make statistics propagate instantly rather than on the second tick + * support for partfiles, where partial pieces belonging to skipped files are + put + * support using multiple threads for socket operations (especially useful for + high performance SSL connections) + * allow setting rate limits for arbitrary peer groups. Generalizes + per-torrent rate limits, and local peer limits + * improved disk cache complexity O(1) instead of O(log(n)) + * add feature to allow storing disk cache blocks in an mmapped file + (presumably on an SSD) + * optimize peer connection distribution logic across torrents to scale + better with many torrents + * replaced std::map with boost::unordered_map for torrent list, to scale + better with many torrents + * optimized piece picker + * optimized disk cache + * optimized .torrent file parsing + * optimized initialization of storage when adding a torrent + * added support for adding torrents asynchronously (for improved startup + performance) + * added support for asynchronous disk I/O + * almost completely changed the storage interface (for custom storage) + * added support for hashing pieces in multiple threads + + * fix padfile issue + * fix PMTUd bug + * update puff to fix gzip crash + +1.0.10 release + + * fixed inverted priority of incoming piece suggestions + * fixed crash on invalid input in http_parser + * added a new "preformatted" type to bencode entry variant type + * fix division by zero in super-seeding logic + +1.0.9 release + + * fix issue in checking outgoing interfaces (when that option is enabled) + * python binding fix for boost-1.60.0 + * optimize enumeration of network interfaces on windows + * improve reliability of binding listen sockets + * support SNI in https web seeds and trackers + * fix unhandled exception in DHT when receiving a DHT packet over IPv6 + +1.0.8 release + + * fix bug where web seeds were not used for torrents added by URL + * fix support for symlinks on windows + * fix long filename issue (on unixes) + * fixed performance bug in DHT torrent eviction + * fixed win64 build (GetFileAttributesEx) + * fixed bug when deleting files for magnet links before they had metadata + +1.0.7 release + + * fix bug where loading settings via load_state() would not trigger all + appropriate actions + * fix bug where 32 bit builds could use more disk cache than the virtual + address space (when set to automatic) + * fix support for torrents with > 500'000 pieces + * fix ip filter bug when banning peers + * fix IPv6 IP address resolution in URLs + * introduce run-time check for torrent info-sections being too large + * fix web seed bug when using proxy and proxy-peer-connections=false + * fix bug in magnet link parser + * introduce add_torrent_params flags to merge web seeds with resume data + (similar to trackers) + * fix bug where dont_count_slow_torrents could not be disabled + * fix fallocate hack on linux (fixes corruption on some architectures) + * fix auto-manage bug with announce to tracker/lsd/dht limits + * improve DHT routing table to not create an unbalanced tree + * fix bug in uTP that would cause any connection taking more than one second + to connect be timed out (introduced in the vulnerability path) + * fixed falling back to sending UDP packets direct when socks proxy fails + * fixed total_wanted bug (when setting file priorities in add_torrent_params) + * fix python3 compatibility with sha1_hash + +1.0.6 release + + * fixed uTP vulnerability + * make utf8 conversions more lenient + * fix loading of piece priorities from resume data + * improved seed-mode handling (seed-mode will now automatically be left when + performing operations implying it's not a seed) + * fixed issue with file priorities and override resume data + * fix request queue size performance issue + * slightly improve UDP tracker performance + * fix http scrape + * add missing port mapping functions to python binding + * fix bound-checking issue in bdecoder + * expose missing dht_settings fields to python + * add function to query the DHT settings + * fix bug in 'dont_count_slow_torrents' feature, which would start too many + torrents + +1.0.5 release + + * improve ip_voter to avoid flapping + * fixed bug when max_peerlist_size was set to 0 + * fix issues with missing exported symbols when building dll + * fix division by zero bug in edge case while connecting peers + +1.0.4 release + + * fix bug in python binding for file_progress on torrents with no metadata + * fix assert when removing a connected web seed + * fix bug in tracker timeout logic + * switch UPnP post back to HTTP 1.1 + * support conditional DHT get + * OpenSSL build fixes + * fix DHT scrape bug + +1.0.3 release + + * python binding build fix for boost-1.57.0 + * add --enable-export-all option to configure script, to export all symbols + from libtorrent + * fix if_nametoindex build error on windows + * handle overlong utf-8 sequences + * fix link order bug in makefile for python binding + * fix bug in interest calculation, causing premature disconnects + * tweak flag_override_resume_data semantics to make more sense (breaks + backwards compatibility of edge-cases) + * improve DHT bootstrapping and periodic refresh + * improve DHT maintenance performance (by pinging instead of full lookups) + * fix bug in DHT routing table node-id prefix optimization + * fix incorrect behavior of flag_use_resume_save_path + * fix protocol race-condition in super seeding mode + * support read-only DHT nodes + * remove unused partial hash DHT lookups + * remove potentially privacy leaking extension (non-anonymous mode) + * peer-id connection ordering fix in anonymous mode + * mingw fixes + +1.0.2 release + + * added missing force_proxy to python binding + * anonymous_mode defaults to false + * make DHT DOS detection more forgiving to bursts + * support IPv6 multicast in local service discovery + * simplify CAS function in DHT put + * support IPv6 traffic class (via the TOS setting) + * made uTP re-enter slow-start after time-out + * fixed uTP upload performance issue + * fix missing support for DHT put salt + +1.0.1 release + + * fix alignment issue in bitfield + * improved error handling of gzip + * fixed crash when web seeds redirect + * fix compiler warnings + +1.0 release + + * fix bugs in convert_to/from_native() on windows + * fix support for web servers not supporting keepalive + * support storing save_path in resume data + * don't use full allocation on network drives (on windows) + * added clear_piece_deadlines() to remove all piece deadlines + * improve queuing logic of inactive torrents (dont_count_slow_torrents) + * expose optimistic unchoke logic to plugins + * fix issue with large UDP packets on windows + * remove set_ratio() feature + * improve piece_deadline/streaming + * honor pieces with priority 7 in sequential download mode + * simplified building python bindings + * make ignore_non_routers more forgiving in the case there are no UPnP + devices at a known router. Should improve UPnP compatibility. + * include reason in peer_blocked_alert + * support magnet links wrapped in .torrent files + * rate limiter optimization + * rate limiter overflow fix (for very high limits) + * non-auto-managed torrents no longer count against the torrent limits + * handle DHT error responses correctly + * allow force_announce to only affect a single tracker + * add moving_storage field to torrent_status + * expose UPnP and NAT-PMP mapping in session object + * DHT refactoring and support for storing arbitrary data with put and get + * support building on android + * improved support for web seeds that don't support keep-alive + * improve DHT routing table to return better nodes (lower RTT and closer + to target) + * don't use pointers to resume_data and file_priorities in + add_torrent_params + * allow moving files to absolute paths, out of the download directory + * make move_storage more generic to allow both overwriting files as well + as taking existing ones + * fix choking issue at high upload rates + * optimized rate limiter + * make disk cache pool allocator configurable + * fix library ABI to not depend on logging being enabled + * use hex encoding instead of base32 in create_magnet_uri + * include name, save_path and torrent_file in torrent_status, for + improved performance + * separate anonymous mode and force-proxy mode, and tighten it up a bit + * add per-tracker scrape information to announce_entry + * report errors in read_piece_alert + * DHT memory optimization + * improve DHT lookup speed + * improve support for windows XP and earlier + * introduce global connection priority for improved swarm performance + * make files deleted alert non-discardable + * make built-in sha functions not conflict with libcrypto + * improve web seed hash failure case + * improve DHT lookup times + * uTP path MTU discovery improvements + * optimized the torrent creator optimizer to scale significantly better + with more files + * fix uTP edge case where udp socket buffer fills up + * fix nagle implementation in uTP + + * fix bug in error handling in protocol encryption + +0.16.18 release + + * fix uninitialized values in DHT DOS mitigation + * fix error handling in file::phys_offset + * fix bug in HTTP scrape response parsing + * enable TCP keepalive for socks5 connection for UDP associate + * fix python3 support + * fix bug in lt_donthave extension + * expose i2p_alert to python. cleaning up of i2p connection code + * fixed overflow and download performance issue when downloading at high rates + * fixed bug in add_torrent_alert::message for magnet links + * disable optimistic disconnects when connection limit is low + * improved error handling of session::listen_on + * suppress initial 'completed' announce to trackers added with replace_trackers + after becoming a seed + * SOCKS4 fix for trying to connect over IPv6 + * fix saving resume data when removing all trackers + * fix bug in udp_socket when changing socks5 proxy quickly + +0.16.17 release + + * don't fall back on wildcard port in UPnP + * fix local service discovery for magnet links + * fix bitfield issue in file_storage + * added work-around for MingW issue in file I/O + * fixed sparse file detection on windows + * fixed bug in gunzip + * fix to use proxy settings when adding .torrent file from URL + * fix resume file issue related to daylight savings time on windows + * improve error checking in lazy_bdecode + +0.16.16 release + + * add missing add_files overload to the python bindings + * improve error handling in http gunzip + * fix debug logging for banning web seeds + * improve support for de-selected files in full allocation mode + * fix dht_bootstrap_alert being posted + * SetFileValidData fix on windows (prevents zero-fill) + * fix minor lock_files issue on unix + +0.16.15 release + + * fix mingw time_t 64 bit issue + * fix use of SetFileValidData on windows + * fix crash when using full allocation storage mode + * improve error_code and error_category support in python bindings + * fix python binding for external_ip_alert + +0.16.14 release + + * make lt_tex more robust against bugs and malicious behavior + * HTTP chunked encoding fix + * expose file_granularity flag to python bindings + * fix DHT memory error + * change semantics of storage allocation to allocate on first write rather + than on startup (behaves better with changing file priorities) + * fix resend logic in response to uTP SACK messages + * only act on uTP RST packets with correct ack_nr + * make uTP errors log in normal log mode (not require verbose) + * deduplicate web seed entries from torrent files + * improve error reporting from lazy_decode() + +0.16.13 release + + * fix auto-manage issue when pausing session + * fix bug in non-sparse mode on windows, causing incorrect file errors to + be generated + * fix set_name() on file_storage actually affecting save paths + * fix large file support issue on mingw + * add some error handling to set_piece_hashes() + * fix completed-on timestamp to not be clobbered on each startup + * fix deadlock caused by some UDP tracker failures + * fix potential integer overflow issue in timers on windows + * minor fix to peer_proportional mixed_mode algorithm (TCP limit could go + too low) + * graceful pause fix + * i2p fixes + * fix issue when loading certain malformed .torrent files + * pass along host header with http proxy requests and possible + http_connection shutdown hang + +0.16.12 release + + * fix building with C++11 + * fix IPv6 support in UDP socket (uTP) + * fix mingw build issues + * increase max allowed outstanding piece requests from peers + * uTP performance improvement. only fast retransmit one packet at a time + * improve error message for 'file too short' + * fix piece-picker stat bug when only selecting some files for download + * fix bug in async_add_torrent when settings file_priorities + * fix boost-1.42 support for python bindings + * fix memory allocation issue (virtual address space waste) on windows + +0.16.11 release + + * fix web seed URL double escape issue + * fix string encoding issue in alert messages + * fix SSL authentication issue + * deprecate std::wstring overloads. long live utf-8 + * improve time-critical pieces feature (streaming) + * introduce bandwidth exhaustion attack-mitigation in allowed-fast pieces + * python binding fix issue where torrent_info objects where destructing when + their torrents were deleted + * added missing field to scrape_failed_alert in python bindings + * GCC 4.8 fix + * fix proxy failure semantics with regards to anonymous mode + * fix round-robin seed-unchoke algorithm + * add bootstrap.sh to generate configure script and run configure + * fix bug in SOCK5 UDP support + * fix issue where torrents added by URL would not be started immediately + +0.16.10 release + + * fix encryption level handle invalid values + * add a number of missing functions to the python binding + * fix typo in Jamfile for building shared libraries + * prevent tracker exchange for magnet links before metadata is received + * fix crash in make_magnet_uri when generating links longer than 1024 + characters + * fix hanging issue when closing files on windows (completing a download) + * fix piece picking edge case that could cause torrents to get stuck at + hash failure + * try unencrypted connections first, and fall back to encryption if it + fails (performance improvement) + * add missing functions to python binding (flush_cache(), remap_files() + and orig_files()) + * improve handling of filenames that are invalid on windows + * support 'implied_port' in DHT announce_peer + * don't use pool allocator for disk blocks (cache may now return pages + to the kernel) + +0.16.9 release + + * fix long filename truncation on windows + * distinguish file open mode when checking files and downloading/seeding + with bittorrent. updates storage interface + * improve file_storage::map_file when dealing with invalid input + * improve handling of invalid utf-8 sequences in strings in torrent files + * handle more cases of broken .torrent files + * fix bug filename collision resolver + * fix bug in filename utf-8 verification + * make need_save_resume() a bit more robust + * fixed sparse flag manipulation on windows + * fixed streaming piece picking issue + +0.16.8 release + + * make rename_file create missing directories for new filename + * added missing python function: parse_magnet_uri + * fix alerts.all_categories in python binding + * fix torrent-abort issue which would cancel name lookups of other torrents + * make torrent file parser reject invalid path elements earlier + * fixed piece picker bug when using pad-files + * fix read-piece response for cancelled deadline-pieces + * fixed file priority vector-overrun + * fix potential packet allocation alignment issue in utp + * make 'close_redudnant_connections' cover more cases + * set_piece_deadline() also unfilters the piece (if its priority is 0) + * add work-around for bug in windows vista and earlier in + GetOverlappedResult + * fix traversal algorithm leak in DHT + * fix string encoding conversions on windows + * take torrent_handle::query_pieces into account in torrent_handle::statue() + * honor trackers responding with 410 + * fixed merkle tree torrent creation bug + * fixed crash with empty url-lists in torrent files + * added missing max_connections() function to python bindings + +0.16.7 release + + * fix string encoding in error messages + * handle error in read_piece and set_piece_deadline when torrent is removed + * DHT performance improvement + * attempt to handle ERROR_CANT_WAIT disk error on windows + * improve peers exchanged over PEX + * fixed rare crash in ut_metadata extension + * fixed files checking issue + * added missing pop_alerts() to python bindings + * fixed typos in configure script, inversing some feature-enable/disable flags + * added missing flag_update_subscribe to python bindings + * active_dht_limit, active_tracker_limit and active_lsd_limit now + interpret -1 as infinite + +0.16.6 release + + * fixed verbose log error for NAT holepunching + * fix a bunch of typos in python bindings + * make get_settings available in the python binding regardless of + deprecated functions + * fix typo in python settings binding + * fix possible dangling pointer use in peer list + * fix support for storing arbitrary data in the DHT + * fixed bug in uTP packet circle buffer + * fix potential crash when using torrent_handle::add_piece + * added missing add_torrent_alert to python binding + +0.16.5 release + + * udp socket refcounter fix + * added missing async_add_torrent to python bindings + * raised the limit for bottled http downloads to 2 MiB + * add support for magnet links and URLs in python example client + * fixed typo in python bindings' add_torrent_params + * introduce a way to add built-in plugins from python + * consistently disconnect the same peer when two peers simultaneously connect + * fix local endpoint queries for uTP connections + * small optimization to local peer discovery to ignore our own broadcasts + * try harder to bind the udp socket (uTP, DHT, UDP-trackers, LSD) to the + same port as TCP + * relax file timestamp requirements for accepting resume data + * fix performance issue in web seed downloader (coalescing of blocks + sometimes wouldn't work) + * web seed fixes (better support for torrents without trailing / in + web seeds) + * fix some issues with SSL over uTP connections + * fix UDP trackers trying all endpoints behind the hostname + +0.16.4 release + + * raise the default number of torrents allowed to announce to trackers + to 1600 + * improve uTP slow start behavior + * fixed UDP socket error causing it to fail on Win7 + * update use of boost.system to not use deprecated functions + * fix GIL issue in python bindings. Deprecated extension support in python + * fixed bug where setting upload slots to -1 would not mean infinite + * extend the UDP tracker protocol to include the request string from the + tracker URL + * fix mingw build for linux crosscompiler + +0.16.3 release + + * fix python binding backwards compatibility in replace_trackers + * fix possible starvation in metadata extension + * fix crash when creating torrents and optimizing file order with pad files + * disable support for large MTUs in uTP until it is more reliable + * expose post_torrent_updates and state_update_alert to python bindings + * fix incorrect SSL error messages + * fix windows build of shared library with openssl + * fix race condition causing shutdown hang + +0.16.2 release + + * fix permissions issue on linux with noatime enabled for non-owned files + * use random peer IDs in anonymous mode + * fix move_storage bugs + * fix unnecessary dependency on boost.date_time when building boost.asio as separate compilation + * always use SO_REUSEADDR and deprecate the flag to turn it on + * add python bindings for SSL support + * minor uTP tweaks + * fix end-game mode issue when some files are selected to not be downloaded + * improve uTP slow start + * make uTP less aggressive resetting cwnd when idle + +0.16.1 release + + * fixed crash when providing corrupt resume data + * fixed support for boost-1.44 + * fixed reversed semantics of queue_up() and queue_down() + * added missing functions to python bindings (file_priority(), set_dht_settings()) + * fixed low_prio_disk support on linux + * fixed time critical piece accounting in the request queue + * fixed semantics of rate_limit_utp to also ignore per-torrent limits + * fixed piece sorting bug of deadline pieces + * fixed python binding build on Mac OS and BSD + * fixed UNC path normalization (on windows, unless UNC paths are disabled) + * fixed possible crash when enabling multiple connections per IP + * fixed typo in win vista specific code, breaking the build + * change default of rate_limit_utp to true + * fixed DLL export issue on windows (when building a shared library linking statically against boost) + * fixed FreeBSD build + * fixed web seed performance issue with pieces > 1 MiB + * fixed unchoke logic when using web seeds + * fixed compatibility with older versions of boost (down to boost 1.40) + +0.16 release + + * support torrents with more than 262000 pieces + * make tracker back-off configurable + * don't restart the swarm after downloading metadata from magnet links + * lower the default tracker retry intervals + * support banning web seeds sending corrupt data + * don't let hung outgoing connection attempts block incoming connections + * improve SSL torrent support by using SNI and a single SSL listen socket + * improved peer exchange performance by sharing incoming connections which advertise listen port + * deprecate set_ratio(), and per-peer rate limits + * add web seed support for torrents with pad files + * introduced a more scalable API for torrent status updates (post_torrent_updates()) and updated client_test to use it + * updated the API to add_torrent_params turning all bools into flags of a flags field + * added async_add_torrent() function to significantly improve performance when + adding many torrents + * change peer_states to be a bitmask (bw_limit, bw_network, bw_disk) + * changed semantics of send_buffer_watermark_factor to be specified as a percentage + * add incoming_connection_alert for logging all successful incoming connections + * feature to encrypt peer connections with a secret AES-256 key stored in .torrent file + * deprecated compact storage allocation + * close files in separate thread on systems where close() may block (Mac OS X for instance) + * don't create all directories up front when adding torrents + * support DHT scrape + * added support for fadvise/F_RDADVISE for improved disk read performance + * introduced pop_alerts() which pops the entire alert queue in a single call + * support saving metadata in resume file, enable it by default for magnet links + * support for receiving multi announce messages for local peer discovery + * added session::listen_no_system_port flag to prevent libtorrent from ever binding the listen socket to port 0 + * added option to not recheck on missing or incomplete resume data + * extended stats logging with statistics=on builds + * added new session functions to more efficiently query torrent status + * added alerts for added and removed torrents + * expanded plugin interface to support session wide states + * made the metadata block requesting algorithm more robust against hash check failures + * support a separate option to use proxies for peers or not + * pausing the session now also pauses checking torrents + * moved alert queue size limit into session_settings + * added support for DHT rss feeds (storing only) + * added support for RSS feeds + * fixed up some edge cases in DHT routing table and improved unit test of it + * added error category and error codes for HTTP errors + * made the DHT implementation slightly more robust against routing table poisoning and node ID spoofing + * support chunked encoding in http downloads (http_connection) + * support adding torrents by url to the .torrent file + * support CDATA tags in xml parser + * use a python python dictionary for settings instead of session_settings object (in python bindings) + * optimized metadata transfer (magnet link) startup time (shaved off about 1 second) + * optimized swarm startup time (shaved off about 1 second) + * support DHT name lookup + * optimized memory usage of torrent_info and file_storage, forcing some API changes + around file_storage and file_entry + * support trackerid tracker extension + * graceful peer disconnect mode which finishes transactions before disconnecting peers + * support chunked encoding for web seeds + * uTP protocol support + * resistance towards certain flood attacks + * support chunked encoding for web seeds (only for BEP 19, web seeds) + * optimized session startup time + * support SSL for web seeds, through all proxies + * support extending web seeds with custom authorization and extra headers + * settings that are not changed from the default values are not saved + in the session state + * made seeding choking algorithm configurable + * deprecated setters for max connections, max half-open, upload and download + rates and unchoke slots. These are now set through session_settings + * added functions to query an individual peer's upload and download limit + * full support for BEP 21 (event=paused) + * added share-mode feature for improving share ratios + * merged all proxy settings into a single one + * improved SOCKS5 support by proxying hostname lookups + * improved support for multi-homed clients + * added feature to not count downloaded bytes from web seeds in stats + * added alert for incoming local service discovery messages + * added option to set file priorities when adding torrents + * removed the session mutex for improved performance + * added upload and download activity timer stats for torrents + * made the reuse-address flag configurable on the listen socket + * moved UDP trackers over to use a single socket + * added feature to make asserts log to a file instead of breaking the process + (production asserts) + * optimized disk I/O cache clearing + * added feature to ask a torrent if it needs to save its resume data or not + * added setting to ignore file modification time when loading resume files + * support more fine-grained torrent states between which peer sources it + announces to + * supports calculating sha1 file-hashes when creating torrents + * made the send_buffer_watermark performance warning more meaningful + * supports complete_ago extension + * dropped zlib as a dependency and builds using puff.c instead + * made the default cache size depend on available physical RAM + * added flags to torrent::status() that can filter which values are calculated + * support 'explicit read cache' which keeps a specific set of pieces + in the read cache, without implicitly caching other pieces + * support sending suggest messages based on what's in the read cache + * clear sparse flag on files that complete on windows + * support retry-after header for web seeds + * replaced boost.filesystem with custom functions + * replaced dependency on boost.thread by asio's internal thread primitives + * added support for i2p torrents + * cleaned up usage of MAX_PATH and related macros + * made it possible to build libtorrent without RTTI support + * added support to build with libgcrypt and a shipped version of libtommath + * optimized DHT routing table memory usage + * optimized disk cache to work with large caches + * support variable number of optimistic unchoke slots and to dynamically + adjust based on the total number of unchoke slots + * support for BitTyrant choker algorithm + * support for automatically start torrents when they receive an + incoming connection + * added more detailed instrumentation of the disk I/O thread + +0.15.11 release + + * fixed web seed bug, sometimes causing infinite loops + * fixed race condition when setting session_settings immediately after creating session + * give up immediately when failing to open a listen socket (report the actual error) + * restored ABI compatibility with 0.15.9 + * added missing python bindings for create_torrent and torrent_info + +0.15.10 release + + * fix 'parameter incorrect' issue when using unbuffered IO on windows + * fixed UDP socket error handling on windows + * fixed peer_tos (type of service) setting + * fixed crash when loading resume file with more files than the torrent in it + * fix invalid-parameter error on windows when disabling filesystem disk cache + * fix connection queue issue causing shutdown delays + * fixed mingw build + * fix overflow bug in progress_ppm field + * don't filter local peers received from a non-local tracker + * fix python deadlock when using python extensions + * fixed small memory leak in DHT + +0.15.9 release + + * added some functions missing from the python binding + * fixed rare piece picker bug + * fixed invalid torrent_status::finished_time + * fixed bugs in dont-have and upload-only extension messages + * don't open files in random-access mode (speeds up hashing) + +0.15.8 release + + * allow NULL to be passed to create_torrent::set_comment and create_torrent::set_creator + * fix UPnP issue for routers with multiple PPPoE connections + * fix issue where event=stopped announces wouldn't be sent when closing session + * fix possible hang in file::readv() on windows + * fix CPU busy loop issue in tracker announce logic + * honor IOV_MAX when using writev and readv + * don't post 'operation aborted' UDP errors when changing listen port + * fix tracker retry logic, where in some configurations the next tier would not be tried + * fixed bug in http seeding logic (introduced in 0.15.7) + * add support for dont-have extension message + * fix for set_piece_deadline + * add reset_piece_deadline function + * fix merkle tree torrent assert + +0.15.7 release + + * exposed set_peer_id to python binding + * improve support for merkle tree torrent creation + * exposed comparison operators on torrent_handle to python + * exposed alert error_codes to python + * fixed bug in announce_entry::next_announce_in and min_announce_in + * fixed sign issue in set_alert_mask signature + * fixed unaligned disk access for unbuffered I/O in windows + * support torrents whose name is empty + * fixed connection limit to take web seeds into account as well + * fixed bug when receiving a have message before having the metadata + * fixed python bindings build with disabled DHT support + * fixed BSD file allocation issue + * fixed bug in session::delete_files option to remove_torrent + +0.15.6 release + + * fixed crash in udp trackers when using SOCKS5 proxy + * fixed reconnect delay when leaving upload only mode + * fixed default values being set incorrectly in add_torrent_params through add_magnet_uri in python bindings + * implemented unaligned write (for unbuffered I/O) + * fixed broadcast_lsd option + * fixed udp-socket race condition when using a proxy + * end-game mode optimizations + * fixed bug in udp_socket causing it to issue two simultaneous async. read operations + * fixed mingw build + * fixed minor bug in metadata block requester (for magnet links) + * fixed race condition in iconv string converter + * fixed error handling in torrent_info constructor + * fixed bug in torrent_info::remap_files + * fix python binding for wait_for_alert + * only apply privileged port filter to DHT-only peers + +0.15.5 release + + * support DHT extension to report external IPs + * fixed rare crash in http_connection's error handling + * avoid connecting to peers listening on ports < 1024 + * optimized piece picking to not cause busy loops in some end-game modes + * fixed python bindings for tcp::endpoint + * fixed edge case of pad file support + * limit number of torrents tracked by DHT + * fixed bug when allow_multiple_connections_per_ip was enabled + * potential WOW64 fix for unbuffered I/O (windows) + * expose set_alert_queue_size_limit to python binding + * support dht nodes in magnet links + * support 100 Continue HTTP responses + * changed default choker behavior to use 8 unchoke slots (instead of being rate based) + * fixed error reporting issue in disk I/O thread + * fixed file allocation issues on linux + * fixed filename encoding and decoding issue on platforms using iconv + * reports redundant downloads to tracker, fixed downloaded calculation to + be more stable when not including redundant. Improved redundant data accounting + to be more accurate + * fixed bugs in http seed connection and added unit test for it + * fixed error reporting when fallocate fails + * deprecate support for separate proxies for separate kinds of connections + +0.15.4 release + + * fixed piece picker issue triggered by hash failure and timed out requests to the piece + * fixed optimistic unchoke issue when setting per torrent unchoke limits + * fixed UPnP shutdown issue + * fixed UPnP DeletePortmapping issue + * fixed NAT-PMP issue when adding the same mapping multiple times + * no peers from tracker when stopping is no longer an error + * improved web seed retry behavior + * fixed announce issue + +0.15.3 release + + * fixed announce bug where event=completed would not be sent if it violated the + min-announce of the tracker + * fixed limitation in rate limiter + * fixed build error with boost 1.44 + +0.15.2 release + + * updated compiler to msvc 2008 for python binding + * restored default fail_limit to unlimited on all trackers + * fixed rate limit bug for DHT + * fixed SOCKS5 bug for routing UDP packets + * fixed bug on windows when verifying resume data for a torrent where + one of its directories had been removed + * fixed race condition in peer-list with DHT + * fix force-reannounce and tracker retry issue + +0.15.1 release + + * fixed rare crash when purging the peer list + * fixed race condition around m_abort in session_impl + * fixed bug in web_peer_connection which could cause a hang when downloading + from web servers + * fixed bug in metadata extensions combined with encryption + * refactored socket reading code to not use async. operations unnecessarily + * some timer optimizations + * removed the reuse-address flag on the listen socket + * fixed bug where local peer discovery and DHT wouldn't be announced to without trackers + * fixed bug in bdecoder when decoding invalid messages + * added build warning when building with UNICODE but the standard library + doesn't provide std::wstring + * fixed add_node python binding + * fixed issue where trackers wouldn't tried immediately when the previous one failed + * fixed synchronization issue between download queue and piece picker + * fixed bug in udp tracker scrape response parsing + * fixed bug in the disk thread that could get triggered under heavy load + * fixed bug in add_piece() that would trigger asserts + * fixed vs 2010 build + * recognizes more clients in identify_client() + * fixed bug where trackers wouldn't be retried if they failed + * slight performance fix in disk elevator algorithm + * fixed potential issue where a piece could be checked twice + * fixed build issue on windows related to GetCompressedSize() + * fixed deadlock when starting torrents with certain invalid tracker URLs + * fixed iterator bug in disk I/O thread + * fixed FIEMAP support on linux + * fixed strict aliasing warning on gcc + * fixed inconsistency when creating torrents with symlinks + * properly detect windows version to initialize half-open connection limit + * fixed bug in url encoder where $ would not be encoded + +0.15 release + + * introduced a session state save mechanism. load_state() and save_state(). + this saves all session settings and state (except torrents) + * deprecated dht_state functions and merged it with the session state + * added support for multiple trackers in magnet links + * added support for explicitly flushing the disk cache + * added torrent priority to affect bandwidth allocation for its peers + * reduced the number of floating point operations (to better support + systems without FPU) + * added new alert when individual files complete + * added support for storing symbolic links in .torrent files + * added support for uTorrent interpretation of multi-tracker torrents + * handle torrents with duplicate filenames + * piece timeouts are adjusted to download rate limits + * encodes urls in torrent files that needs to be encoded + * fixed not passing &supportcrypto=1 when encryption is disabled + * introduced an upload mode, which torrents are switched into when + it hits a disk write error, instead of stopping the torrent. + this lets libtorrent keep uploading the parts it has when it + encounters a disk-full error for instance + * improved disk error handling and expanded use of error_code in + error reporting. added a bandwidth state, bw_disk, when waiting + for the disk io thread to catch up writing buffers + * improved read cache memory efficiency + * added another cache flush algorithm to write the largest + contiguous blocks instead of the least recently used + * introduced a mechanism to be lighter on the disk when checking torrents + * applied temporary memory storage optimization to when checking + a torrent as well + * removed hash_for_slot() from storage_interface. It is now implemented + by using the readv() function from the storage implementation + * improved IPv6 support by announcing twice when necessary + * added feature to set a separate global rate limit for local peers + * added preset settings for low memory environments and seed machines + min_memory_usage() and high_performance_seeder() + * optimized overall memory usage for DHT nodes and requests, peer + entries and disk buffers + * change in API for block_info in partial_piece_info, instead of + accessing 'peer', call 'peer()' + * added support for fully automatic unchoker (no need to specify + number of upload slots). This is on by default + * added support for changing socket buffer sizes through + session_settings + * added support for merkle hash tree torrents (.merkle.torrent) + * added 'seed mode', which assumes that all files are complete + and checks hashes lazily, as blocks are requested + * added new extension for file attributes (executable and hidden) + * added support for unbuffered I/O for aligned files + * added workaround for sparse file issue on Windows Vista + * added new lt_trackers extension to exchange trackers between + peers + * added support for BEP 17 http seeds + * added read_piece() to read pieces from torrent storage + * added option for udp tracker preference + * added super seeding + * added add_piece() function to inject data from external sources + * add_tracker() function added to torrent_handle + * if there is no working tracker, current_tracker is the + tracker that is currently being tried + * torrents that are checking can now be paused, which will + pause the checking + * introduced another torrent state, checking_resume_data, which + the torrent is in when it's first added, and is comparing + the files on disk with the resume data + * DHT bandwidth usage optimizations + * rate limited DHT send socket + * tracker connections are now also subject to IP filtering + * improved optimistic unchoke logic + * added monitoring of the DHT lookups + * added bandwidth reports for estimated TCP/IP overhead and DHT + * includes DHT traffic in the rate limiter + * added support for bitcomet padding files + * improved support for sparse files on windows + * added ability to give seeding torrents preference to active slots + * added torrent_status::finished_time + * automatically caps files and connections by default to rlimit + * added session::is_dht_running() function + * added torrent_handle::force_dht_announce() + * added torrent_info::remap_files() + * support min_interval tracker extension + * added session saving and loading functions + * added support for min-interval in tracker responses + * only keeps one outstanding duplicate request per peer + reduces waste download, specifically when streaming + * added support for storing per-peer rate limits across reconnects + * improved fallocate support + * fixed magnet link issue when using resume data + * support disk I/O priority settings + * added info_hash to torrent_deleted_alert + * improved LSD performance and made the interval configurable + * improved UDP tracker support by caching connect tokens + * fast piece optimization + +release 0.14.10 + + * fixed udp tracker race condition + * added support for torrents with odd piece sizes + * fixed issue with disk read cache not being cleared when removing torrents + * made the DHT socket bind to the same interface as the session + * fixed issue where an http proxy would not be used on redirects + * Solaris build fixes + * disabled buggy disconnect_peers feature + +release 0.14.9 + + * disabled feature to drop requests after having been skipped too many times + * fixed range request bug for files larger than 2 GB in web seeds + * don't crash when trying to create torrents with 0 files + * fixed big_number __init__ in python bindings + * fixed optimistic unchoke timer + * fixed bug where torrents with incorrectly formatted web seed URLs would be + connected multiple times + * fixed MinGW support + * fixed DHT bootstrapping issue + * fixed UDP over SOCKS5 issue + * added support for "corrupt" tracker announce + * made end-game mode less aggressive + +release 0.14.8 + + * ignore unknown metadata messages + * fixed typo that would sometimes prevent queued torrents to be checked + * fixed bug in auto-manager where active_downloads and active_seeds would + sometimes be used incorrectly + * force_recheck() no longer crashes on torrents with no metadata + * fixed broadcast socket regression from 0.14.7 + * fixed hang in NATPMP when shut down while waiting for a response + * fixed some more error handling in bdecode + +release 0.14.7 + + * fixed deadlock in natpmp + * resume data alerts are always posted, regardless of alert mask + * added wait_for_alert to python binding + * improved invalid filename character replacement + * improved forward compatibility in DHT + * added set_piece_hashes that takes a callback to the python binding + * fixed division by zero in get_peer_info() + * fixed bug where pieces may have been requested before the metadata + was received + * fixed incorrect error when deleting files from a torrent where + not all files have been created + * announces torrents immediately to the DHT when it's started + * fixed bug in add_files that would fail to recurse if the path + ended with a / + * fixed bug in error handling when parsing torrent files + * fixed file checking bug when renaming a file before checking the torrent + * fixed race condition when receiving metadata from swarm + * fixed assert in ut_metadata plugin + * back-ported some fixes for building with no exceptions + * fixed create_torrent when passing in a path ending with / + * fixed move_storage when source doesn't exist + * fixed DHT state save bug for node-id + * fixed typo in python binding session_status struct + * broadcast sockets now join every network interface (used for UPnP and + local peer discovery) + +release 0.14.6 + + * various missing include fixes to be buildable with boost 1.40 + * added missing functions to python binding related to torrent creation + * fixed to add filename on web seed urls that lack it + * fixed BOOST_ASIO_HASH_MAP_BUCKETS define for boost 1.39 + * fixed checking of fast and suggest messages when used with magnet links + * fixed bug where web seeds would not disconnect if being resolved when + the torrent was paused + * fixed download piece performance bug in piece picker + * fixed bug in connect candidate counter + * replaces invalid filename characters with . + * added --with-libgeoip option to configure script to allow building and + linking against system wide library + * fixed potential pure virtual function call in extensions on shutdown + * fixed disk buffer leak in smart_ban extension + +release 0.14.5 + + * fixed bug when handling malformed webseed urls and an http proxy + * fixed bug when setting unlimited upload or download rates for torrents + * fix to make torrent_status::list_peers more accurate. + * fixed memory leak in disk io thread when not using the cache + * fixed bug in connect candidate counter + * allow 0 upload slots + * fixed bug in rename_file(). The new name would not always be saved in + the resume data + * fixed resume data compatibility with 0.13 + * fixed rare piece-picker bug + * fixed bug where one allowed-fast message would be sent even when + disabled + * fixed race condition in UPnP which could lead to crash + * fixed inversed seed_time ratio logic + * added get_ip_filter() to session + +release 0.14.4 + + * connect candidate calculation fix + * tightened up disk cache memory usage + * fixed magnet link parser to accept hex-encoded info-hashes + * fixed inverted logic when picking which peers to connect to + (should mean a slight performance improvement) + * fixed a bug where a failed rename_file() would leave the storage + in an error state which would pause the torrent + * fixed case when move_storage() would fail. Added a new alert + to be posted when it does + * fixed crash bug when shutting down while checking a torrent + * fixed handling of web seed urls that didn't end with a + slash for multi-file torrents + * lowered the default connection speed to 10 connection attempts + per second + * optimized memory usage when checking files fails + * fixed bug when checking a torrent twice + * improved handling of out-of-memory conditions in disk I/O thread + * fixed bug when force-checking a torrent with partial pieces + * fixed memory leak in disk cache + * fixed torrent file path vulnerability + * fixed upnp + * fixed bug when dealing with clients that drop requests (i.e. BitComet) + fixes assert as well + +release 0.14.3 + + * added python binding for create_torrent + * fixed boost-1.38 build + * fixed bug where web seeds would be connected before the files + were checked + * fixed filename bug when using wide characters + * fixed rare crash in peer banning code + * fixed potential HTTP compatibility issue + * fixed UPnP crash + * fixed UPnP issue where the control url contained the base url + * fixed a replace_trackers bug + * fixed bug where the DHT port mapping would not be removed when + changing DHT port + * fixed move_storage bug when files were renamed to be moved out + of the root directory + * added error handling for set_piece_hashes + * fixed missing include in enum_if.cpp + * fixed dual IP stack issue + * fixed issue where renamed files were sometimes not saved in resume data + * accepts tracker responses with no 'peers' field, as long as 'peers6' + is present + * fixed CIDR-distance calculation in the presence of IPv6 peers + * save partial resume data for torrents that are queued for checking + or checking, to maintain stats and renamed files + * Don't try IPv6 on windows if it's not installed + * move_storage fix + * fixed potential crash on shutdown + * fixed leaking exception from bdecode on malformed input + * fixed bug where connection would hang when receiving a keepalive + * fixed bug where an asio exception could be thrown when resolving + peer countries + * fixed crash when shutting down while checking a torrent + * fixed potential crash in connection_queue when a peer_connection + fail to open its socket + +release 0.14.2 + + * added missing functions to the python bindings torrent_info::map_file, + torrent_info::map_block and torrent_info::file_at_offset. + * removed support for boost-1.33 and earlier (probably didn't work) + * fixed potential freezes issues at shutdown + * improved error message for python setup script + * fixed bug when torrent file included announce-list, but no valid + tracker urls + * fixed bug where the files requested from web seeds would be the + renamed file names instead of the original file names in the torrent. + * documentation fix of queueing section + * fixed potential issue in udp_socket (affected udp tracker support) + * made name, comment and created by also be subject to utf-8 error + correction (filenames already were) + * fixed dead-lock when settings DHT proxy + * added missing export directives to lazy_entry + * fixed disk cache expiry settings bug (if changed, it would be set + to the cache size) + * fixed bug in http_connection when binding to a particular IP + * fixed typo in python binding (torrent_handle::piece_prioritize should + be torrent_handle::piece_priorities) + * fixed race condition when saving DHT state + * fixed bugs related to lexical_cast being locale dependent + * added support for SunPro C++ compiler + * fixed bug where messages sometimes could be encrypted in the + wrong order, for encrypted connections. + * fixed race condition where torrents could get stuck waiting to + get checked + * fixed mapped files bug where it wouldn't be properly restored + from resume data properly + * removed locale dependency in xml parser (caused asserts on windows) + * fixed bug when talking to https 1.0 servers + * fixed UPnP bug that could cause stack overflow + +release 0.14.1 + + * added converter for python unicode strings to utf-8 paths + * fixed bug in http downloader where the host field did not + include the port number + * fixed headers to not depend on NDEBUG, which would prohibit + linking a release build of libtorrent against a debug application + * fixed bug in disk I/O thread that would make the thread + sometimes quit when an error occurred + * fixed DHT bug + * fixed potential shutdown crash in disk_io_thread + * fixed usage of deprecated boost.filesystem functions + * fixed http_connection unit test + * fixed bug in DHT when a DHT state was loaded + * made rate limiter change in 0.14 optional (to take estimated + TCP/IP overhead into account) + * made the python plugin buildable through the makefile + * fixed UPnP bug when url base ended with a slash and + path started with a slash + * fixed various potentially leaking exceptions + * fixed problem with removing torrents that are checking + * fixed documentation bug regarding save_resume_data() + * added missing documentation on torrent creation + * fixed bugs in python client examples + * fixed missing dependency in package-config file + * fixed shared geoip linking in Jamfile + * fixed python bindings build on windows and made it possible + to generate a windows installer + * fixed bug in NAT-PMP implementation + +release 0.14 + + * deprecated add_torrent() in favor of a new add_torrent() + that takes a struct with parameters instead. Torrents + are paused and auto managed by default. + * removed 'connecting_to_tracker' torrent state. This changes + the enum values for the other states. + * Improved seeding and choking behavior. + * Fixed rare buffer overrun bug when calling get_download_queue + * Fixed rare bug where torrent could be put back into downloading + state even though it was finished, after checking files. + * Fixed rename_file to work before the file on disk has been + created. + * Fixed bug in tracker connections in case of errors caused + in the connection constructor. + * Updated alert system to be filtered by category instead of + severity level. Alerts can generate a message through + alert::message(). + * Session constructor will now start dht, upnp, natpmp, lsd by + default. Flags can be passed in to the constructor to not + do this, if these features are to be enabled and disabled + at a later point. + * Removed 'connecting_to_tracker' torrent state + * Fix bug where FAST pieces were cancelled on choke + * Fixed problems with restoring piece states when hash failed. + * Minimum peer reconnect time fix. Peers with no failures would + reconnect immediately. + * Improved web seed error handling + * DHT announce fixes and off-by-one loop fix + * Fixed UPnP xml parse bug where it would ignore the port number + for the control url. + * Fixed bug in torrent writer where the private flag was added + outside of the info dictionary + * Made the torrent file parser less strict of what goes in the + announce-list entry + * Fixed type overflow bug where some statistics was incorrectly + reported for file larger than 2 GB + * boost-1.35 support + * Fixed bug in statistics from web server peers where it sometimes + could report too many bytes downloaded. + * Fixed bug where statistics from the last second was lost when + disconnecting a peer. + * receive buffer optimizations (memcpy savings and memory savings) + * Support for specifying the TOS byte for peer traffic. + * Basic support for queueing of torrents. + * Better bias to give connections to downloading torrents + with fewer peers. + * Optimized resource usage (removed the checking thread) + * Support to bind outgoing connections to specific ports + * Disk cache support. + * New, more memory efficient, piece picker with sequential download + support (instead of the more complicated sequential download threshold). + * Auto Upload slots. Automatically opens up more slots if + upload limit is not met. + * Improved NAT-PMP support by querying the default gateway + * Improved UPnP support by ignoring routers not on the clients subnet. + +release 0.13 + + * Added scrape support + * Added add_extension() to torrent_handle. Can instantiate + extensions for torrents while downloading + * Added support for remove_torrent to delete the files as well + * Fixed issue with failing async_accept on windows + * DHT improvements, proper error messages are now returned when + nodes sends bad packets + * Optimized the country table used to resolve country of peers + * Copying optimization for sending data. Data is no longer copied from + the disk I/O buffer to the send buffer. + * Buffer optimization to use a raw buffer instead of std::vector + * Improved file storage to use sparse files + * Updated python bindings + * Added more clients to the identifiable clients list. + * Torrents can now be started in paused state (to better support queuing) + * Improved IPv6 support (support for IPv6 extension to trackers and + listens on both IPv6 and IPv4 interfaces). + * Improved asserts used. Generates a stacktrace on linux + * Piece picker optimizations and improvements + * Improved unchoker, connection limit and rate limiter + * Support for FAST extension + * Fixed invalid calculation in DHT node distance + * Fixed bug in URL parser that failed to parse IPv6 addresses + * added peer download rate approximation + * added port filter for outgoing connection (to prevent + triggering firewalls) + * made most parameters configurable via session_settings + * added encryption support + * added parole mode for peers whose data fails the hash check. + * optimized heap usage in piece-picker and web seed downloader. + * fixed bug in DHT where older write tokens weren't accepted. + * added support for sparse files. + * introduced speed categories for peers and pieces, to separate + slow and fast peers. + * added a half-open tcp connection limit that takes all connections + in to account, not just peer connections. + * added alerts for filtered IPs. + * added support for SOCKS4 and 5 proxies and HTTP CONNECT proxies. + * fixed proper distributed copies calculation. + * added option to use openssl for sha-1 calculations. + * optimized the piece picker in the case where a peer is a seed. + * added support for local peer discovery + * removed the dependency on the compiled boost.date_time library + * deprecated torrent_info::print() + * added UPnP support + * fixed problem where peer interested flags were not updated correctly + when pieces were filtered + * improvements to ut_pex messages, including support for seed flag + * prioritizes upload bandwidth to peers that might send back data + * the following functions have been deprecated: + void torrent_handle::filter_piece(int index, bool filter) const; + void torrent_handle::filter_pieces(std::vector const& pieces) const; + bool torrent_handle::is_piece_filtered(int index) const; + std::vector torrent_handle::filtered_pieces() const; + void torrent_handle::filter_files(std::vector const& files) const; + + instead, use the piece_priority functions. + + * added support for NAT-PMP + * added support for piece priorities. Piece filtering is now set as + a priority + * Fixed crash when last piece was smaller than one block and reading + fastresume data for that piece + * Makefiles should do a better job detecting boost + * Fixed crash when all tracker urls are removed + * Log files can now be created at user supplied path + * Log files failing to create is no longer fatal + * Fixed dead-lock in torrent_handle + * Made it build with boost 1.34 on windows + * Fixed bug in URL parser that failed to parse IPv6 addresses + * Fixed bug in DHT, related to IPv6 nodes + * DHT accepts transaction IDs that have garbage appended to them + * DHT logs messages that it fails to decode + +release 0.12 + + * fixes to make the DHT more compatible + * http seed improvements including error reporting and url encoding issues. + * fixed bug where directories would be left behind when moving storage + in some cases. + * fixed crashing bug when restarting or stopping the DHT. + * added python binding, using boost.python + * improved character conversion on windows when strings are not utf-8. + * metadata extension now respects the private flag in the torrent. + * made the DHT to only be used as a fallback to trackers by default. + * added support for HTTP redirection support for web seeds. + * fixed race condition when accessing a torrent that was checking its + fast resume data. + * fixed a bug in the DHT which could be triggered if the network was + dropped or extremely rare cases. + * if the download rate is limited, web seeds will now only use left-over + bandwidth after all bt peers have used up as much bandwidth as they can. + * added the possibility to have libtorrent resolve the countries of + the peers in torrents. + * improved the bandwidth limiter (it now implements a leaky bucket/node bucket). + * improved the HTTP seed downloader to report accurate progress. + * added more client peer-id signatures to be recognized. + * added support for HTTP servers that skip the CR before the NL at line breaks. + * fixed bug in the HTTP code that only accepted headers case sensitive. + * fixed bug where one of the session constructors didn't initialize boost.filesystem. + * fixed bug when the initial checking of a torrent fails with an exception. + * fixed bug in DHT code which would send incorrect announce messages. + * fixed bug where the http header parser was case sensitive to the header + names. + * Implemented an optimization which frees the piece_picker once a torrent + turns into a seed. + * Added support for uT peer exchange extension, implemented by Massaroddel. + * Modified the quota management to offer better bandwidth balancing + between peers. + * logging now supports multiple sessions (different sessions now log + to different directories). + * fixed random number generator seed problem, generating the same + peer-id for sessions constructed the same second. + * added an option to accept multiple connections from the same IP. + * improved tracker logging. + * moved the file_pool into session. The number of open files is now + limited per session. + * fixed uninitialized private flag in torrent_info + * fixed long standing issue with file.cpp on windows. Replaced the low level + io functions used on windows. + * made it possible to associate a name with torrents without metadata. + * improved http-downloading performance by requesting entire pieces via + http. + * added plugin interface for extensions. And changed the interface for + enabling extensions. + +release 0.11 + + * added support for incorrectly encoded paths in torrent files + (assumes Latin-1 encoding and converts to UTF-8). + * added support for destructing session objects asynchronously. + * fixed bug with file_progress() with files = 0 bytes + * fixed a race condition bug in udp_tracker_connection that could + cause a crash. + * fixed bug occurring when increasing the sequenced download threshold + with max availability lower than previous threshold. + * fixed an integer overflow bug occurring when built with gcc 4.1.x + * fixed crasing bug when closing while checking a torrent + * fixed bug causing a crash with a torrent with piece length 0 + * added an extension to the DHT network protocol to support the + exchange of nodes with IPv6 addresses. + * modified the ip_filter api slightly to support IPv6 + * modified the api slightly to make sequenced download threshold + a per torrent-setting. + * changed the address type to support IPv6 + * fixed bug in piece picker which would not behave as + expected with regard to sequenced download threshold. + * fixed bug with file_progress() with files > 2 GB. + * added --enable-examples option to configure script. + * fixed problem with the resource distribution algorithm + (controlling e.g upload/download rates). + * fixed incorrect asserts in storage related to torrents with + zero-sized files. + * added support for trackerless torrents (with kademlia DHT). + * support for torrents with the private flag set. + * support for torrents containing bootstrap nodes for the + DHT network. + * fixed problem with the configure script on FreeBSD. + * limits the pipelining used on url-seeds. + * fixed problem where the shutdown always would delay for + session_settings::stop_tracker_timeout seconds. + * session::listen_on() won't reopen the socket in case the port and + interface is the same as the one currently in use. + * added http proxy support for web seeds. + * fixed problem where upload and download stats could become incorrect + in case of high cpu load. + * added more clients to the identifiable list. + * fixed fingerprint parser to cope with latest Mainline versions. + +release 0.10 + + * fixed a bug where the requested number of peers in a tracker request could + be too big. + * fixed a bug where empty files were not created in full allocation mode. + * fixed a bug in storage that would, in rare cases, fail to do a + complete check. + * exposed more settings for tweaking parameters in the piece-picker, + downloader and uploader (http_settings replaced by session_settings). + * tweaked default settings to improve high bandwidth transfers. + * improved the piece picker performance and made it possible to download + popular pieces in sequence to improve disk performance. + * added the possibility to control upload and download limits per peer. + * fixed problem with re-requesting skipped pieces when peer was sending pieces + out of fifo-order. + * added support for http seeding (the GetRight protocol) + * renamed identifiers called 'id' in the public interface to support linking + with Objective.C++ + * changed the extensions protocol to use the new one, which is also + implemented by uTorrent. + * factorized the peer_connection and added web_peer_connection which is + able to download from http-sources. + * converted the network code to use asio (resulted in slight api changes + dealing with network addresses). + * made libtorrent build in vc7 (patches from Allen Zhao) + * fixed bug caused when binding outgoing connections to a non-local interface. + * add_torrent() will now throw if called while the session object is + being closed. + * added the ability to limit the number of simultaneous half-open + TCP connections. Flags in peer_info has been added. + +release 0.9.1 + + * made the session disable file name checks within the boost.filesystem library + * fixed race condition in the sockets + * strings that are invalid utf-8 strings are now decoded with the + local codepage on windows + * added the ability to build libtorrent both as a shared library + * client_test can now monitor a directory for torrent files and automatically + start and stop downloads while running + * fixed problem with file_size() when building on windows with unicode support + * added a new torrent state, allocating + * added a new alert, metadata_failed_alert + * changed the interface to session::add_torrent for some speed optimizations. + * greatly improved the command line control of the example client_test. + * fixed bug where upload rate limit was not being applied. + * files that are being checked will no longer stall files that don't need + checking. + * changed the way libtorrent identifies support for its excentions + to look for 'ext' at the end of the peer-id. + * improved performance by adding a circle buffer for the send buffer. + * fixed bugs in the http tracker connection when using an http proxy. + * fixed problem with storage's file pool when creating torrents and then + starting to seed them. + * hard limit on remote request queue and timeout on requests (a timeout + triggers rerequests). This makes libtorrent work much better with + "broken" clients like BitComet which may ignore requests. + +Initial release 0.9 + + * multitracker support + * serves multiple torrents on a single port and a single thread + * supports http proxies and proxy authentication + * gzipped tracker-responses + * block level piece picker + * queues torrents for file check, instead of checking all of them in parallel + * uses separate threads for checking files and for main downloader + * upload and download rate limits + * piece-wise, unordered, incremental file allocation + * fast resume support + * supports files > 2 gigabytes + * supports the no_peer_id=1 extension + * support for udp-tracker protocol + * number of connections limit + * delays sending have messages + * can resume pieces downloaded in any order + * adjusts the length of the request queue depending on download rate + * supports compact=1 + * selective downloading + * ip filter + diff --git a/Jamfile b/Jamfile new file mode 100644 index 0000000..aa10e1a --- /dev/null +++ b/Jamfile @@ -0,0 +1,1164 @@ +# This Jamfile requires boost-build v2 to build. +# The version shipped with boost 1.34.0 + +import modules ; +import path ; +import os ; +import errors ; +import feature : feature ; +import package ; +import virtual-target ; +import cast ; + +# we need version numbers in the form X.Y.Z in order to trigger the built-in +# support for generating symlinks to the installed library +VERSION = 2.0.11 ; + +BOOST_ROOT = [ modules.peek : BOOST_ROOT ] ; +CXXFLAGS = [ modules.peek : CXXFLAGS ] ; +LDFLAGS = [ modules.peek : LDFLAGS ] ; + +ECHO "CXXFLAGS =" $(CXXFLAGS) ; +ECHO "LDFLAGS =" $(LDFLAGS) ; +ECHO "OS =" [ os.name ] ; + +jam-version = [ modules.peek : JAM_VERSION ] ; + +if $(BOOST_ROOT) +{ + ECHO "building boost from source directory: " $(BOOST_ROOT) ; + + use-project /boost : $(BOOST_ROOT) ; + alias boost_system : /boost/system//boost_system : : : $(BOOST_ROOT) ; +} +else +{ + local boost-lib-search-path = + /usr/local/opt/boost/lib + /opt/homebrew/lib + ; + + local boost-include-path = + /usr/local/opt/boost/include + /opt/homebrew/include + ; + + lib boost_system : : boost_system $(boost-lib-search-path) : : $(boost-include-path) ; +} + +use-project /try_signal : ./deps/try_signal ; + +rule linking ( properties * ) +{ + local result ; + if on in $(properties) + { + result += /libsimulator//simulator ; + } + + if windows in $(properties) + && ( on in $(properties) + || production in $(properties) + || on in $(properties) ) + { + result += dbghelp ; + } + + if windows in $(properties) + { + switch [ feature.get-values : $(properties) ] + { + case xp : result += _WIN32_WINNT=0x0501 ; + case vista : result += _WIN32_WINNT=0x0600 ; + case win7 : result += _WIN32_WINNT=0x0601 ; + case win10 : result += _WIN32_WINNT=0x0A00 ; + } + } + + # gcrypt libraries, if enabled + if gcrypt in $(properties) + { + result += gcrypt ; + } + else if openssl in $(properties) + { + result += ssl ; + result += crypto ; + if linux in $(properties) + { + result += dl ; + } + } + else if gnutls in $(properties) + { + result += ./deps/asio-gnutls//asio-gnutls ; + result += gnutls/shared ; + } + else if libcrypto in $(properties) + { + result += crypto ; + if linux in $(properties) + { + result += dl ; + } + } + else if wolfssl in $(properties) + { + result += wolfssl ; + } + + if windows in $(properties) + || cygwin in $(properties) + { + # socket functions on windows require winsock libraries + result += ws2_32 + wsock32 + iphlpapi + WIN32_LEAN_AND_MEAN + __USE_W32_SOCKETS + WIN32 + _WIN32 + ; + + # when DHT is enabled, we need ed25519 which in turn + # needs entropy + if ! off in $(properties) + { + result += advapi32 ; + } + + # windows xp has no CNG + if ! xp in $(properties) + { + result += bcrypt ; + } + } + + if android in $(properties) + { + result += dl ; + } + + if beos in $(properties) + { + result += netkit gcc ; + } + + if haiku in $(properties) + { + result += libnetwork gcc ; + } + + + if solaris in $(properties) + { + result += libsocket libnsl ; + } + + if darwin in $(properties) + || iphone in $(properties) + { + # for ip_notifier + result += CoreFoundation SystemConfiguration ; + } + + if iphone in $(properties) + { + # boost.asio seems to mis-detect iOS as supporting the __thread + # keyword, resulting in the error: + # error: thread-local storage is not supported for the current target + result += BOOST_ASIO_DISABLE_THREAD_KEYWORD_EXTENSION ; + } + + if gcc in $(properties) + && linux in $(properties) + && ( on in $(properties) + || production in $(properties) + || on in $(properties) ) + { + # for backtraces in assertion failures + # which only works on ELF targets with gcc + result += -Wl,--export-dynamic -rdynamic ; + } + else if [ version.version-less $(jam-version) : 1990 0 ] + { + # the visibility feature was introduced in boost-1.69. This was close to + # when the versioning scheme changed from year to (low) version numbers. + # in boost-1.70 + result += hidden ; + } + + if static in $(properties) + { + if shared in $(properties) + { + # if libtorrent is being built as a shared library + # but we're linking against boost statically, we still + # need to make boost think it's being built as a shared + # library, so that it properly exports its symbols + result += BOOST_ALL_DYN_LINK ; + result += boost_system/static/BOOST_ALL_DYN_LINK ; + } + else + { + result += boost_system/static ; + } + + if gcc in $(properties) + && ! windows in $(properties) + && shared in $(properties) + { + result += on ; + } + + } + else if shared in $(properties) + { + result += boost_system/shared ; + } + else + { + result += boost_system ; + } + + if ! windows in $(properties) + { + # MingW defines a macro called "stat" if this is set, which causes build + # failures + result += _FILE_OFFSET_BITS=64 ; + } + + result += BOOST_ALL_NO_LIB + BOOST_MULTI_INDEX_DISABLE_SERIALIZATION + BOOST_SYSTEM_NO_DEPRECATED + ; + + if shared in $(properties) + { + result += /try_signal//try_signal/static/on ; + } + else + { + result += /try_signal//try_signal/static ; + } + + return $(result) ; +} + +rule warnings ( properties * ) +{ + local result ; + + if off in $(properties) + { + return $(result) ; + } + + if clang in $(properties) + || darwin in $(properties) + { + result += -Weverything ; + result += -Wno-documentation ; + result += -Wno-c++98-compat-pedantic ; + result += -Wno-c++11-compat-pedantic ; + result += -Wno-padded ; + result += -Wno-alloca ; + result += -Wno-global-constructors ; + result += -Wno-poison-system-directories ; +# this warns on any global static object, which are used for error_category +# objects + result += -Wno-exit-time-destructors ; + +# enable these warnings again, once the other ones are dealt with + result += -Wno-weak-vtables ; + + result += -Wno-return-std-move-in-c++11 ; + result += -Wno-unknown-warning-option ; + +# libtorrent uses alloca() carefully + result += -Wno-alloca ; + +# these warnings should all be addressed. Either by transitioning to span and +# array, or by suppressing the warning for specific code + result += -Wno-unsafe-buffer-usage ; + } + + if gcc in $(properties) + { + result += -Wall ; + result += -Wextra ; + result += -Wpedantic ; + result += -Wvla ; + result += -Wno-format-zero-length ; + result += -Wno-noexcept-type ; + } + + if msvc in $(properties) + { + # on msvc this resolves to /W4 + result += all ; + +# enable these warnings again, once the other ones are dealt with + +# disable warning C4251: 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2' + result += /wd4251 ; +# disable warning C4275: non DLL-interface classkey 'identifier' used as base for DLL-interface classkey 'identifier' + result += /wd4275 ; +# disable warning C4373: virtual function overrides, previous versions of the compiler did not override when parameters only differed by const/volatile qualifiers + result += /wd4373 ; + # C4268: 'identifier' : 'const' static/global data initialized + # with compiler generated default constructor fills the object with zeros + result += /wd4268 ; + # C4503: 'identifier': decorated name length exceeded, name was truncated + result += /wd4503 ; + } + + return $(result) ; +} + +# rule for adding the right source files +# depending on target-os and features +rule building ( properties * ) +{ + local result ; + + if ( off in $(properties) && + ! off in $(properties) ) + { + ECHO "'invariant-check' requires enabled 'asserts' mode. (e.g. specify build params: invariant-check=on asserts=on)" ; + result += no ; + } + + local CXXVER = [ feature.get-values : $(properties) ] ; + if ! $(CXXVER) || $(CXXVER) < 14 + { + ECHO "libtorrent requires at least C++14. Specify cxxstd=14 or higher" ; + result += no ; + } + + if msvc in $(properties) || intel-win in $(properties) + { + # allow larger .obj files (with more sections) + result += /bigobj ; + + # https://docs.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170 + result += /utf-8 ; + + # two-phase lookup is not supported by C++/CX on msvc, so it needs to be + # disabled. We can't set permissive- in that case + if ! store in $(properties) + { + # https://learn.microsoft.com/en-us/cpp/build/reference/permissive-standards-conformance?view=msvc-170 + result += /permissive- ; + } + } + + if gcc in $(properties) && windows in $(properties) + { + # allow larger .obj files (with more sections) + result += -Wa,-mbig-obj ; + } + + if ( production in $(properties) + || on in $(properties) ) + { + result += src/assert.cpp ; + } + + if on in $(properties) + { + result += src/pe_crypto.cpp ; + } + + if ( darwin in $(properties) + || gcc in $(properties) + || clang in $(properties) + || clang-darwin in $(properties) ) + # on GCC, enabling debugging in libstdc++ + # breaks the ABI and its ability to appear + # in shared object interfaces, so when it's + # enabled, just export everything (since we're) + # probably not a production build anyway + && ! on in $(properties) + { + if ( gcc in $(properties) ) + { + result += -Wl,-Bsymbolic ; + } + } + + return $(result) ; +} + +rule tag ( name : type ? : property-set ) +{ + # we only care about the names of our output static- or shared library, not + # other targets like object files + if $(type) != SHARED_LIB && $(type) != STATIC_LIB + { + return [ virtual-target.add-prefix-and-suffix $(name) : $(type) : $(property-set) ] ; + } + + # static libraries are not versioned + if $(type) = STATIC_LIB + { + return [ virtual-target.add-prefix-and-suffix $(name)-rasterbar : $(type) : $(property-set) ] ; + } + + # shared libraries have the version number before the filename extension on + # windows + if [ $(property-set).get ] in windows cygwin + { + # TODO: add version on windows too + # return [ virtual-target.add-prefix-and-suffix $(name)-rasterbar-$(VERSION) : $(type) : $(property-set) ] ; + return [ virtual-target.add-prefix-and-suffix $(name)-rasterbar : $(type) : $(property-set) ] ; + } + else + { + local name = [ virtual-target.add-prefix-and-suffix $(name)-rasterbar : $(type) : $(property-set) ] ; + return $(name).$(VERSION) ; + } +} + +# the search path to pick up the openssl libraries from. This is the +# property of those libraries +rule openssl-lib-path ( properties * ) +{ + local OPENSSL_LIB = [ feature.get-values : $(properties) ] ; + + if darwin in $(properties) && $(OPENSSL_LIB) = "" + { + # on macOS, default to pick up openssl from the homebrew installation + # brew install openssl + # homebrew on M1 Macs install to /opt/homebrew + OPENSSL_LIB = /opt/homebrew/opt/openssl/lib /usr/local/opt/openssl/lib ; + } + else if windows in $(properties) && $(OPENSSL_LIB) = "" + { + # the de-facto windows installer is https://slproweb.com/products/Win32OpenSSL.html, which installs to c:\Program Files\OpenSSL-Win{32,64}. + # chocolatey appears to use this installer. + local address_model = [ feature.get-values : $(properties) ] ; + OPENSSL_LIB += "C:/Program Files/OpenSSL-Win$(address_model)/lib" ; + OPENSSL_LIB += "C:/Program Files (x86)/OpenSSL-Win$(address_model)/lib" ; + OPENSSL_LIB += "C:/Program Files/OpenSSL/lib" ; + } + + local result ; + result += $(OPENSSL_LIB) ; + return $(result) ; +} + +# the include path to pick up openssl headers from. This is the +# usage-requirement for the openssl-related libraries +rule openssl-include-path ( properties * ) +{ + local OPENSSL_INCLUDE = [ feature.get-values : $(properties) ] ; + + if darwin in $(properties) && $(OPENSSL_INCLUDE) = "" + { + # on macOS, default to pick up openssl from the homebrew installation + # brew install openssl + # homebrew on M1 Macs install to /opt/homebrew + OPENSSL_INCLUDE = /opt/homebrew/opt/openssl/include /usr/local/opt/openssl/include ; + } + else if windows in $(properties) && $(OPENSSL_INCLUDE) = "" + { + # the de-facto windows installer is https://slproweb.com/products/Win32OpenSSL.html, which installs to c:\Program Files\OpenSSL-Win{32,64}. + # chocolatey appears to use this installer. + local address_model = [ feature.get-values : $(properties) ] ; + OPENSSL_INCLUDE += "C:/Program Files/OpenSSL-Win$(address_model)/include" ; + OPENSSL_INCLUDE += "C:/Program Files (x86)/OpenSSL-Win$(address_model)/include" ; + OPENSSL_INCLUDE += "C:/Program Files/OpenSSL/include" ; + } + + local result ; + result += $(OPENSSL_INCLUDE) ; + return $(result) ; +} + +# the search path to pick up the gnutls libraries from. This is the +# property of those libraries +rule gnutls-lib-path ( properties * ) +{ + local GNUTLS_LIB = [ feature.get-values : $(properties) ] ; + + if darwin in $(properties) && $(GNUTLS_LIB) = "" + { + # on macOS, default to pick up openssl from the homebrew installation + # brew install openssl + # homebrew on M1 Macs install to /opt/homebrew + GNUTLS_LIB = /opt/homebrew/opt/gnutls/lib /usr/local/opt/gnutls/lib ; + } + + local result ; + result += $(GNUTLS_LIB) ; + return $(result) ; +} + +# the include path to pick up gnutls headers from. This is the +# usage-requirement for the gnutls-related libraries +rule gnutls-include-path ( properties * ) +{ + local GNUTLS_INCLUDE = [ feature.get-values : $(properties) ] ; + + if darwin in $(properties) && $(GNUTLS_INCLUDE) = "" + { + # on macOS, default to pick up openssl from the homebrew installation + # brew install openssl + # homebrew on M1 Macs install to /opt/homebrew + GNUTLS_INCLUDE = /opt/homebrew/opt/gnutls/include /usr/local/opt/gnutls/include ; + } + + local result ; + result += $(GNUTLS_INCLUDE) ; + return $(result) ; +} + +# the search path to pick up the wolfssl libraries from. This is the +# property of those libraries +rule wolfssl-lib-path ( properties * ) +{ + local WOLFSSL_LIB = [ feature.get-values : $(properties) ] ; + + if linux in $(properties) && $(WOLFSSL_LIB) = "" + { + # on linux, default ./configure install path + WOLFSSL_LIB = /usr/local/lib ; + } + + local result ; + result += $(WOLFSSL_LIB) ; + return $(result) ; +} + +# the include path to pick up wolfssl headers from. This is the +# usage-requirement for the wolfssl-related libraries +rule wolfssl-include-path ( properties * ) +{ + local WOLFSSL_INCLUDE = [ feature.get-values : $(properties) ] ; + + if linux in $(properties) && $(WOLFSSL_INCLUDE) = "" + { + # on linux, default ./configure install path + WOLFSSL_INCLUDE = /usr/local/include ; + } + + local result ; + result += $(WOLFSSL_INCLUDE) ; + result += $(WOLFSSL_INCLUDE)/wolfssl ; + return $(result) ; +} + +path-constant blacklist-file : tools/sanitizer-blacklist.txt ; + +feature openssl-lib : : free path ; +feature openssl-include : : free path ; + +feature gnutls-lib : : free path ; +feature gnutls-include : : free path ; + +feature wolfssl-lib : : free path ; +feature wolfssl-include : : free path ; + +feature test-coverage : off on : composite propagated link-incompatible ; +feature.compose on : --coverage --coverage ; + +feature predictive-pieces : on off : composite propagated ; +feature.compose off : TORRENT_DISABLE_PREDICTIVE_PIECES ; + +feature share-mode : on off : composite propagated ; +feature.compose off : TORRENT_DISABLE_SHARE_MODE ; + +feature streaming : on off : composite propagated ; +feature.compose off : TORRENT_DISABLE_STREAMING ; + +feature super-seeding : on off : composite propagated ; +feature.compose off : TORRENT_DISABLE_SUPERSEEDING ; + +feature i2p : on off : composite propagated ; +feature.compose on : TORRENT_USE_I2P=1 ; +feature.compose off : TORRENT_USE_I2P=0 ; + +feature asserts : off on production system : composite propagated ; +feature.compose on : TORRENT_USE_ASSERTS=1 ; +feature.compose production : TORRENT_USE_ASSERTS=1 TORRENT_PRODUCTION_ASSERTS=1 ; +feature.compose system : TORRENT_USE_ASSERTS=1 TORRENT_USE_SYSTEM_ASSERTS=1 ; + +feature windows-version : win10 vista xp win7 : composite propagated ; + +feature extensions : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_EXTENSIONS ; + +feature asio-debugging : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_ASIO_DEBUGGING ; + +feature picker-debugging : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_DEBUG_REFCOUNTS ; + +feature mmap-disk-io : on off : composite propagated ; +feature.compose off : TORRENT_HAVE_MMAP=0 TORRENT_HAVE_MAP_VIEW_OF_FILE=0 ; + +feature simulator : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_BUILD_SIMULATOR ; + +feature invariant-checks : off on full : composite propagated link-incompatible ; +feature.compose on : TORRENT_USE_INVARIANT_CHECKS=1 ; +feature.compose full : TORRENT_USE_INVARIANT_CHECKS=1 TORRENT_EXPENSIVE_INVARIANT_CHECKS ; + +feature utp-log : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_UTP_LOG_ENABLE ; + +feature simulate-slow-read : off on : composite propagated ; +feature.compose on : TORRENT_SIMULATE_SLOW_READ ; + +feature simulate-slow-write : off on : composite propagated ; +feature.compose on : TORRENT_SIMULATE_SLOW_WRITE ; + +feature logging : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_LOGGING ; + +feature alert-msg : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_ALERT_MSG ; + +feature dht : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_DHT ; + +feature encryption : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_ENCRYPTION ; + +feature mutable-torrents : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_MUTABLE_TORRENTS ; + +feature crypto : openssl built-in wolfssl gnutls libcrypto gcrypt : composite propagated ; +feature.compose openssl + : TORRENT_USE_LIBCRYPTO + TORRENT_USE_OPENSSL + TORRENT_SSL_PEERS + OPENSSL_NO_SSL2 ; +feature.compose wolfssl + : TORRENT_USE_WOLFSSL + TORRENT_USE_LIBCRYPTO + TORRENT_USE_OPENSSL + OPENSSL_NO_SSL2 + BOOST_ASIO_USE_WOLFSSL + OPENSSL_ALL + WOLFSSL_SHA512 + WOLFSSL_NGINX + WC_NO_HARDEN ; +feature.compose gnutls + : TORRENT_USE_GNUTLS + TORRENT_SSL_PEERS ; +feature.compose libcrypto : TORRENT_USE_LIBCRYPTO ; +feature.compose gcrypt : TORRENT_USE_LIBGCRYPT ; + +feature openssl-version : 1.1 pre1.1 : composite propagated ; + +feature deprecated-functions : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_NO_DEPRECATE ; + +feature boost-link : default static shared : propagated composite ; + +# msvc enables debug iterators by default in debug builds whereas GCC and +# clang do not, that's why "default" is there. msvc has incorrect noexcept +# constructors on some containers when enabling debug iterators, so it's +# possible to turn them off +feature debug-iterators : default off on : composite propagated link-incompatible ; +feature.compose on : _GLIBCXX_DEBUG _GLIBCXX_DEBUG_PEDANTIC ; +feature.compose off : _ITERATOR_DEBUG_LEVEL=0 ; + +feature fpic : off on : composite propagated link-incompatible ; +feature.compose on : -fPIC ; + +feature profile-calls : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_PROFILE_CALLS=1 ; + +# controls whether or not to export some internal +# libtorrent functions. Used for unit testing +feature export-extra : off on : composite propagated ; +# export some internal libtorrent functions +# in order to me able to unit test them. +# this is off by default to keep the export +# symbol table reasonably small +feature.compose on : TORRENT_EXPORT_EXTRA ; + +feature msvc-version-macro : off on : composite propagated link-incompatible ; +# ask the compiler to correctly set __cplusplus version +feature.compose on : /Zc\:__cplusplus ; + +lib advapi32 : : advapi32 ; +lib user32 : : user32 ; +lib shell32 : : shell32 ; +lib gdi32 : : gdi32 ; +lib bcrypt : : bcrypt ; +lib crypt32 : : crypt32 ; +lib z : : shared z ; + +# openssl libraries on windows +# technically, crypt32 is not an OpenSSL dependency, but libtorrent needs it on +# windows to access the system certificate store, for authenticating trackers +alias ssl-deps : advapi32 user32 shell32 gdi32 crypt32 ; + +# pre OpenSSL 1.1 windows +lib crypto : ssl-deps : windows pre1.1 libeay32 + @openssl-lib-path : : @openssl-include-path ; +lib ssl : ssl-deps : windows pre1.1 ssleay32 + crypto @openssl-lib-path : : @openssl-include-path ; + +# OpenSSL 1.1+ windows +lib crypto : ssl-deps : windows 1.1 libcrypto + @openssl-lib-path : : @openssl-include-path ; +lib ssl : ssl-deps : windows 1.1 libssl crypto + @openssl-lib-path : : @openssl-include-path ; + +# generic OpenSSL +lib crypto : : crypto z @openssl-lib-path : : + @openssl-include-path ; +lib ssl : : ssl crypto @openssl-lib-path : : + @openssl-include-path ; + +lib gnutls : : gnutls @gnutls-lib-path : : + @gnutls-include-path ; + +lib wolfssl : : wolfssl @wolfssl-lib-path : : + @wolfssl-include-path ; + +lib dbghelp : : dbghelp ; + +# required for networking on beos +lib netkit : : net /boot/system/lib shared ; +lib gcc : : gcc static ; + +# gcrypt on linux/bsd etc. +lib gcrypt : : gcrypt shared /opt/local/lib ; +lib dl : : shared dl ; + +lib libsocket : : libnsl socket shared /usr/sfw/lib shared ; +lib libnsl : : nsl shared /usr/sfw/lib shared ; +lib libnetwork : : network shared ; + +# socket libraries on windows +lib wsock32 : : wsock32 shared ; +lib ws2_32 : : ws2_32 shared ; +lib iphlpapi : : iphlpapi shared ; + +SOURCES = + alert + alert_manager + announce_entry + assert + bandwidth_limit + bandwidth_manager + bandwidth_queue_entry + bdecode + bitfield + bloom_filter + chained_buffer + choker + close_reason + copy_file + cpuid + crc32c + create_torrent + directory + disk_buffer_holder + disk_buffer_pool + disk_interface + disk_io_thread_pool + disabled_disk_io + disk_job_fence + disk_job_pool + drive_info + entry + error_code + file_storage + escape_string + string_util + file + path + fingerprint + gzip + hasher + hash_picker + hex + http_connection + http_parser + identify_client + ip_filter + ip_helpers + ip_notifier + ip_voter + listen_socket_handle + merkle + merkle_tree + peer_connection + platform_util + bt_peer_connection + web_connection_base + web_peer_connection + http_seed_connection + peer_connection_handle + i2p_stream + instantiate_connection + natpmp + packet_buffer + piece_picker + peer_list + proxy_base + puff + random + read_resume_data + write_resume_data + receive_buffer + resolve_links + session + session_params + session_handle + session_impl + session_call + settings_pack + sha1 + sha1_hash + sha256 + socket_io + socket_type + socks5_stream + stat + storage_utils + torrent + torrent_handle + torrent_info + torrent_peer + torrent_peer_allocator + torrent_status + time + tracker_manager + http_tracker_connection + udp_tracker_connection + timestamp_history + udp_socket + upnp + utf8 + utp_socket_manager + utp_stream + file_view_pool + lsd + enum_net + magnet_uri + parse_url + xml_parse + version + peer_class + peer_class_set + part_file + stat_cache + request_blocks + session_stats + performance_counters + resolver + session_settings + proxy_settings + file_progress + ffs + add_torrent_params + peer_info + stack_allocator + generate_peer_id + mmap + mmap_disk_io + mmap_disk_job + mmap_storage + posix_disk_io + posix_part_file + posix_storage + ssl + truncate + load_torrent + +# -- extensions -- + ut_pex + ut_metadata + smart_ban + ; + +KADEMLIA_SOURCES = + dht_state + dht_storage + dht_tracker + msg + node + node_entry + refresh + rpc_manager + find_data + node_id + routing_table + traversal_algorithm + dos_blocker + get_peers + item + get_item + put_data + ed25519 + sample_infohashes + dht_settings + ; + +ED25519_SOURCES = + add_scalar + fe + ge + key_exchange + keypair + sc + sign + verify + hasher512 + sha512 + ; + +local usage-requirements = + ./include + ./include/libtorrent + release:NDEBUG +# enable cancel support in asio + BOOST_ASIO_ENABLE_CANCELIO +# make sure asio uses std::chrono + BOOST_ASIO_HAS_STD_CHRONO + BOOST_ASIO_NO_DEPRECATED + @linking +# msvc optimizations + msvc,release:"/OPT:ICF=5" + msvc,release:"/OPT:REF" + + # disable bogus deprecation warnings on msvc8 + windows:_SCL_SECURE_NO_DEPRECATE + windows:_CRT_SECURE_NO_DEPRECATE + + "$(CXXFLAGS:J= )" + ; + +project torrent ; + +lib torrent + + : # sources + src/$(SOURCES).cpp + + : # requirements + multi + TORRENT_BUILDING_LIBRARY + shared:TORRENT_BUILDING_SHARED + BOOST_NO_DEPRECATED + shared:BOOST_SYSTEM_SOURCE + + on:src/kademlia/$(KADEMLIA_SOURCES).cpp + on:src/ed25519/$(ED25519_SOURCES).cpp + + @building + @warnings + + @tag + + $(usage-requirements) + "$(LDFLAGS:J= )" + + : # default build + multi + 14 + 512 + + : # usage requirements + $(usage-requirements) + shared:TORRENT_LINKING_SHARED + + ; + + +# install rules + +# return libdir and includedir +rule install-paths ( properties * ) +{ + import version ; + + # package.paths was introduced in boost-1.70 (2018.02) + # however, boost build's versioning scheme changed in boost-1.71 to version + # 4.0 + # so, if versions are 4.0+ we want to use package.paths, but if it's a year, + # say 2018, that means it's old and we use the fallback below. Any version < + # 1990 is considered the 4.0 and later numbering scheme. + if [ version.version-less 1990 0 : $(jam-version) ] + { + import option ; + import property ; + local prefix = [ option.get prefix : [ property.select : $(properties) ] ] ; + prefix = $(prefix:G=) ; + # Or some likely defaults if neither is given. + if ! $(prefix) + { + if [ modules.peek : NT ] { prefix = C:\\$(package-name) ; } + else if [ modules.peek : UNIX ] { prefix = /usr/local ; } + } + + return $(prefix)/lib $(prefix)/include ; + } + else + { + local p = [ package.paths libtorrent : $(properties) ] ; + return [ $(p).libdir ] [ $(p).includedir ] ; + } +} + +rule generate-pkg-config ( properties * ) +{ + import property-set ; + import project ; + + local l = [ project.target [ project.module-name "." ] ] ; + + # this is the libtorrent library target + local t = [ $(l).find torrent : . ] ; + + # these are the properties we're using to build it with + local props = [ $(t).generate [ property-set.create $(properties) ] ] ; + local libname = [ $(props[2]).name ] ; + props = $(props[1]) ; + + p = [ install-paths $(properties) ] ; + + local libdir = $(p[1]) ; + local includes = $(p[2]) ; + + local defines ; + local shared_deps ; + local private_deps ; + for d in [ feature.expand $(properties) ] [ $(props).raw ] { + switch $(d) + { + case \TORRENT_* : { + d = [ SPLIT_BY_CHARACTERS $(d) : ">" ] ; + defines += $(d[2]) ; + } + case \BOOST_* : { + d = [ SPLIT_BY_CHARACTERS $(d) : ">" ] ; + defines += $(d[2]) ; + } + case \* : { + d = [ SPLIT_BY_CHARACTERS $(d) : ">" ] ; + d = $(d[2]) ; + if ( [ path.is-rooted $(d) ] ) + { + includes += $(d) ; + } + } + case \* : { + d = [ SPLIT_BY_CHARACTERS $(d) : ">" ] ; + # this is the target + local t = $(d[2]) ; + if [ $(t).type ] = SHARED_LIB + { + local path = [ $(t).path ] ; + if $(path) != "" + { + libdir += $(path) ; + } + shared_deps += [ $(t).name ] ; + } + else if [ $(t).type ] = SEARCHED_LIB + { + local path = [ $(t).search ] ; + if $(path) != "" + { + libdir += $(path) ; + } + shared_deps += [ $(t).name ] ; + } + else if ( [ $(t).type ] = STATIC_LIB ) + { + private_deps += [ $(t).name ] ; + } + } + } + } + + # TODO: use $(libname) in future versions + local config = "Name: libtorrent-rasterbar" + "\nDescription: libtorrent is an open source C++ library implementing the BitTorrent protocol" + "\nURL: https://libtorrent.org" + "\nVersion: $(VERSION)" + "\nLibs:" + " -L\"$(libdir)\"" + " -ltorrent-rasterbar" + " -l$(shared_deps)" + "\nLibs.private:" + " -L\"$(libdir)\"" + " -l$(private_deps)" + "\nCflags:" + " -D$(defines)" + " -I\"$(includes)\"" + "\n" + ; + + local dummy = @("libtorrent-rasterbar.pc":E=$(config)) ; +} + +rule install-pkg-config ( target-name : data * : requirements * ) +{ + import stage ; + local p = [ install-paths $(requirements) ] ; + local libdir = $(p[0]) ; + + stage.install $(target-name) + : $(data) + : $(requirements) $(libdir)/pkgconfig + ; + + import project ; + local c = [ project.current ] ; + local project-module = [ $(c).project-module ] ; + module $(project-module) + { + explicit $(1) ; + } +} + +headers = [ path.glob-tree include/libtorrent : *.hpp ] ; + +package.install install-torrent-lib + : libtorrent + : + : torrent + : $(headers) + ; + +package.install-data install-cmake-module + : cmake/Modules + : examples/cmake/FindLibtorrentRasterbar.cmake + ; + +install-pkg-config pkg-config-target : libtorrent-rasterbar.pc : @generate-pkg-config ; + +alias install : install-torrent-lib install-cmake-module pkg-config-target ; + +explicit install ; + + +# testing headers targets + +local header_targets ; +for local target in $(headers) +{ + if ! [ path.basename $(target) ] in storage.hpp windows.hpp win_util.hpp win_crypto_provider.hpp torrent_impl.hpp io_service.hpp + { + # this cast tells boost build that the header files really *are* cpp files + # otherwise the object rule doesn't know which language to interpret them as + obj header-build/$(target).o : [ cast.cast _ cpp : $(target) ] + : torrent -fsyntax-only + : 14 ; + explicit header-build/$(target).o ; + header_targets += $(target) ; + } +} + +alias check-headers : header-build/$(header_targets).o ; +explicit check-headers ; diff --git a/Jamroot.jam b/Jamroot.jam new file mode 100644 index 0000000..e69de29 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..05e9627 --- /dev/null +++ b/LICENSE @@ -0,0 +1,183 @@ +Copyright (c) 2003-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------ + +puff.c +Copyright (C) 2002, 2003 Mark Adler +For conditions of distribution and use, see copyright notice in puff.h +version 1.7, 3 Mar 2003 + +puff.c is a simple inflate written to be an unambiguous way to specify the +deflate format. It is not written for speed but rather simplicity. As a +side benefit, this code might actually be useful when small code is more +important than speed, such as bootstrap applications. For typical deflate +data, zlib's inflate() is about four times as fast as puff(). zlib's +inflate compiles to around 20K on my machine, whereas puff.c compiles to +around 4K on my machine (a PowerPC using GNU cc). If the faster decode() +function here is used, then puff() is only twice as slow as zlib's +inflate(). + +All dynamically allocated memory comes from the stack. The stack required +is less than 2K bytes. This code is compatible with 16-bit int's and +assumes that long's are at least 32 bits. puff.c uses the short data type, +assumed to be 16 bits, for arrays in order to conserve memory. The code +works whether integers are stored big endian or little endian. + +In the comments below are "Format notes" that describe the inflate process +and document some of the less obvious aspects of the format. This source +code is meant to supplement RFC 1951, which formally describes the deflate +format: + + http://www.zlib.org/rfc-deflate.html + +------------------------------------------------------------------------------ + +bindings/python/src/ + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +------------------------------------------------------------------------------ + +ed25519 implementation based on: + +Copyright (c) 2015 Orson Peters + +This software is provided 'as-is', without any express or implied warranty. In no event will the +authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial +applications, and to alter it and redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the + original software. If you use this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be misrepresented as + being the original software. + +3. This notice may not be removed or altered from any source distribution. + +------------------------------------------------------------------------------ + +src/sha1.cpp include/libtorrent/sha1.hpp + +SHA-1 in C +By Steve Reid +100% Public Domain + +------------------------------------------------------------------------------ + +include/libtorrent/_aux/route.h + + * Copyright (c) 2000-2008 Apple Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * Copyright (c) 1980, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)route.h 8.3 (Berkeley) 4/19/94 + * $FreeBSD: src/sys/net/route.h,v 1.36.2.1 2000/08/16 06:14:23 jayanth Exp $ + +------------------------------------------------------------------------------ + +src/sha256.cpp + +SHA-256. Adapted from LibTomCrypt. This code is Public Domain + diff --git a/LibtorrentRasterbarConfig.cmake.in b/LibtorrentRasterbarConfig.cmake.in new file mode 100644 index 0000000..69aeca1 --- /dev/null +++ b/LibtorrentRasterbarConfig.cmake.in @@ -0,0 +1,9 @@ +# - Config file for the @PROJECT_NAME@ package +# It defines the LibtorrentRasterbar::torrent-rasterbar target to link against + +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +@_find_dependency_calls@ + +include("${CMAKE_CURRENT_LIST_DIR}/LibtorrentRasterbarTargets.cmake") diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a8a997b --- /dev/null +++ b/Makefile @@ -0,0 +1,1149 @@ +VERSION=2.0.11 + +BUILD_CONFIG=release link=shared crypto=openssl warnings=off address-model=64 + +ifeq (${PREFIX},) +PREFIX=/usr/local/ +endif + +ALL: FORCE + BOOST_ROOT="" b2 ${BUILD_CONFIG} + +python-binding: FORCE + (cd bindings/python; BOOST_ROOT="" b2 ${BUILD_CONFIG} stage_module stage_dependencies) + +examples: FORCE + (cd examples; BOOST_ROOT="" b2 ${BUILD_CONFIG} stage_client_test stage_connection_tester) + +tools: FORCE + (cd tools; BOOST_ROOT="" b2 ${BUILD_CONFIG}) + +install: FORCE + BOOST_ROOT="" b2 ${BUILD_CONFIG} install --prefix=${PREFIX} + +sim: FORCE + (cd simulation; BOOST_ROOT="" b2 $(filter-out crypto=openssl,${BUILD_CONFIG}) crypto=built-in) + +check: FORCE + (cd test; BOOST_ROOT="" b2 crypto=openssl warnings=off) + +clean: FORCE + rm -rf \ + bin \ + examples/bin \ + tools/bin \ + bindings/python/bin \ + test/bin \ + simulation/bin \ + simulator/libsimulator/bin + +DOCS_IMAGES = \ + docs/img/screenshot.png \ + docs/img/screenshot_thumb.png \ + docs/img/cwnd.png \ + docs/img/cwnd_thumb.png \ + docs/img/delays.png \ + docs/img/delays_thumb.png \ + docs/img/our_delay_base.png \ + docs/img/our_delay_base_thumb.png \ + docs/img/read_disk_buffers.png \ + docs/img/read_disk_buffers.diagram \ + docs/img/storage.png \ + docs/img/write_disk_buffers.png \ + docs/img/write_disk_buffers.diagram \ + docs/img/ip_id_v4.png \ + docs/img/ip_id_v6.png \ + docs/img/hash_distribution.png \ + docs/img/complete_bit_prefixes.png \ + docs/img/troubleshooting.dot \ + docs/img/troubleshooting.png \ + docs/img/troubleshooting_thumb.png \ + docs/img/hacking.diagram \ + docs/img/hacking.png \ + docs/img/utp_stack.diagram \ + docs/img/utp_stack.png \ + docs/img/bitcoin.png \ + docs/img/logo-color-text.png \ + docs/img/pp-acceptance-medium.png \ + docs/style.css + +DOCS_PAGES = \ + docs/building.html \ + docs/client_test.html \ + docs/contributing.html \ + docs/dht_extensions.html \ + docs/dht_rss.html \ + docs/dht_sec.html \ + docs/dht_store.html \ + docs/examples.html \ + docs/extension_protocol.html \ + docs/features-ref.html \ + docs/index.html \ + docs/manual-ref.html \ + docs/projects.html \ + docs/python_binding.html \ + docs/tuning-ref.html \ + docs/settings.rst \ + docs/stats_counters.rst \ + docs/troubleshooting.html \ + docs/udp_tracker_protocol.html \ + docs/utp.html \ + docs/streaming.html \ + docs/building.rst \ + docs/client_test.rst \ + docs/contributing.rst \ + docs/dht_extensions.rst \ + docs/dht_rss.rst \ + docs/dht_sec.rst \ + docs/dht_store.rst \ + docs/examples.rst \ + docs/extension_protocol.rst \ + docs/features.rst \ + docs/index.rst \ + docs/manual.rst \ + docs/manual-ref.rst \ + docs/projects.rst \ + docs/python_binding.rst \ + docs/tuning.rst \ + docs/troubleshooting.rst \ + docs/udp_tracker_protocol.rst \ + docs/utp.rst \ + docs/streaming.rst \ + docs/tutorial.rst \ + docs/tutorial-ref.rst \ + docs/header.rst \ + docs/hacking.rst \ + docs/hacking.html \ + docs/todo.html \ + docs/tutorial-ref.html \ + docs/upgrade_to_1.2-ref.html \ + docs/upgrade_to_2.0-ref.html \ + docs/security-audit.html \ + docs/reference.html \ + docs/reference-Core.html \ + docs/reference-DHT.html \ + docs/reference-Session.html \ + docs/reference-Torrent_Handle.html \ + docs/reference-Torrent_Info.html \ + docs/reference-Trackers.html \ + docs/reference-PeerClass.html \ + docs/reference-Torrent_Status.html \ + docs/reference-Stats.html \ + docs/reference-Resume_Data.html \ + docs/reference-Add_Torrent.html \ + docs/reference-Plugins.html \ + docs/reference-Create_Torrents.html \ + docs/reference-Error_Codes.html \ + docs/reference-Storage.html \ + docs/reference-Custom_Storage.html \ + docs/reference-Utility.html \ + docs/reference-Bencoding.html \ + docs/reference-Alerts.html \ + docs/reference-Filter.html \ + docs/reference-Settings.html \ + docs/reference-Bdecoding.html \ + docs/reference-ed25519.html \ + docs/single-page-ref.html + +ED25519_SOURCE = \ + fe.h \ + fixedint.h \ + ge.h \ + precomp_data.h \ + sc.h \ + add_scalar.cpp \ + fe.cpp \ + ge.cpp \ + key_exchange.cpp \ + keypair.cpp \ + sc.cpp \ + sign.cpp \ + verify.cpp \ + sha512.cpp \ + hasher512.cpp \ + +EXTRA_DIST = \ + Jamfile \ + Jamroot.jam \ + project-config.jam \ + Makefile \ + CMakeLists.txt \ + cmake/Modules/FindLibGcrypt.cmake \ + cmake/Modules/GeneratePkgConfig.cmake \ + cmake/Modules/ucm_flags.cmake \ + cmake/Modules/LibtorrentMacros.cmake \ + cmake/Modules/GeneratePkgConfig/generate-pkg-config.cmake.in \ + cmake/Modules/GeneratePkgConfig/pkg-config.cmake.in \ + cmake/Modules/GeneratePkgConfig/target-compile-settings.cmake.in \ + LibtorrentRasterbarConfig.cmake.in \ + bindings/CMakeLists.txt \ + setup.py \ + LICENSE \ + src/ed25519/LICENSE \ + COPYING \ + AUTHORS \ + NEWS \ + README.rst \ + ChangeLog \ + $(DOCS_PAGES) \ + $(DOCS_IMAGES) + +PYTHON_FILES= \ + CMakeLists.txt \ + Jamfile \ + client.py \ + make_torrent.py \ + setup.py \ + setup.py.cmake.in \ + simple_client.py \ + src/alert.cpp \ + src/boost_python.hpp \ + src/bytes.hpp \ + src/converters.cpp \ + src/create_torrent.cpp \ + src/datetime.cpp \ + src/entry.cpp \ + src/error_code.cpp \ + src/fingerprint.cpp \ + src/gil.hpp \ + src/ip_filter.cpp \ + src/load_torrent.cpp \ + src/magnet_uri.cpp \ + src/module.cpp \ + src/optional.hpp \ + src/peer_info.cpp \ + src/session.cpp \ + src/session_settings.cpp \ + src/sha1_hash.cpp \ + src/sha256_hash.cpp \ + src/info_hash.cpp \ + src/string.cpp \ + src/torrent_handle.cpp \ + src/torrent_info.cpp \ + src/torrent_status.cpp \ + src/utility.cpp \ + src/version.cpp + +EXAMPLE_FILES= \ + CMakeLists.txt \ + Jamfile \ + bt-get.cpp \ + bt-get2.cpp \ + bt-get3.cpp \ + check_files.cpp \ + client_test.cpp \ + cmake/FindLibtorrentRasterbar.cmake \ + connection_tester.cpp \ + dump_torrent.cpp \ + dump_bdecode.cpp \ + magnet2torrent.cpp \ + make_torrent.cpp \ + print.cpp \ + print.hpp \ + session_view.cpp \ + session_view.hpp \ + simple_client.cpp \ + custom_storage.cpp \ + stats_counters.cpp \ + torrent2magnet.cpp \ + torrent_view.cpp \ + torrent_view.hpp \ + upnp_test.cpp + +TOOLS_FILES= \ + CMakeLists.txt \ + Jamfile \ + dht_put.cpp \ + dht_sample.cpp \ + disk_io_stress_test.cpp\ + parse_dht_log.py \ + parse_dht_rtt.py \ + parse_dht_stats.py \ + parse_peer_log.py \ + parse_sample.py \ + parse_session_stats.py \ + parse_utp_log.py \ + session_log_alerts.cpp + +KADEMLIA_SOURCES = \ + dht_settings.cpp \ + dht_state.cpp \ + dht_storage.cpp \ + dht_tracker.cpp \ + dos_blocker.cpp \ + ed25519.cpp \ + find_data.cpp \ + get_item.cpp \ + get_peers.cpp \ + item.cpp \ + msg.cpp \ + node.cpp \ + node_entry.cpp \ + node_id.cpp \ + put_data.cpp \ + refresh.cpp \ + routing_table.cpp \ + rpc_manager.cpp \ + sample_infohashes.cpp \ + traversal_algorithm.cpp + +SOURCES = \ + add_torrent_params.cpp \ + alert.cpp \ + alert_manager.cpp \ + announce_entry.cpp \ + assert.cpp \ + bandwidth_limit.cpp \ + bandwidth_manager.cpp \ + bandwidth_queue_entry.cpp \ + bdecode.cpp \ + bitfield.cpp \ + bloom_filter.cpp \ + bt_peer_connection.cpp \ + chained_buffer.cpp \ + choker.cpp \ + close_reason.cpp \ + copy_file.cpp \ + cpuid.cpp \ + crc32c.cpp \ + create_torrent.cpp \ + directory.cpp \ + disabled_disk_io.cpp \ + disk_buffer_holder.cpp \ + disk_buffer_pool.cpp \ + disk_interface.cpp \ + disk_io_thread_pool.cpp \ + disk_job_fence.cpp \ + disk_job_pool.cpp \ + drive_info.cpp \ + entry.cpp \ + enum_net.cpp \ + error_code.cpp \ + escape_string.cpp \ + ffs.cpp \ + file.cpp \ + file_progress.cpp \ + file_storage.cpp \ + file_view_pool.cpp \ + fingerprint.cpp \ + generate_peer_id.cpp \ + gzip.cpp \ + hash_picker.cpp \ + hasher.cpp \ + hex.cpp \ + http_connection.cpp \ + http_parser.cpp \ + http_seed_connection.cpp \ + http_tracker_connection.cpp \ + i2p_stream.cpp \ + identify_client.cpp \ + instantiate_connection.cpp \ + ip_filter.cpp \ + ip_helpers.cpp \ + ip_notifier.cpp \ + ip_voter.cpp \ + listen_socket_handle.cpp \ + load_torrent.cpp \ + lsd.cpp \ + magnet_uri.cpp \ + merkle.cpp \ + merkle_tree.cpp \ + mmap.cpp \ + mmap_disk_io.cpp \ + mmap_disk_job.cpp \ + mmap_storage.cpp \ + natpmp.cpp \ + packet_buffer.cpp \ + parse_url.cpp \ + part_file.cpp \ + path.cpp \ + pe_crypto.cpp \ + peer_class.cpp \ + peer_class_set.cpp \ + peer_connection.cpp \ + peer_connection_handle.cpp \ + peer_info.cpp \ + peer_list.cpp \ + performance_counters.cpp \ + piece_picker.cpp \ + platform_util.cpp \ + posix_disk_io.cpp \ + posix_part_file.cpp \ + posix_storage.cpp \ + proxy_base.cpp \ + proxy_settings.cpp \ + puff.cpp \ + random.cpp \ + read_resume_data.cpp \ + receive_buffer.cpp \ + request_blocks.cpp \ + resolve_links.cpp \ + resolver.cpp \ + session.cpp \ + session_call.cpp \ + session_handle.cpp \ + session_impl.cpp \ + session_params.cpp \ + session_settings.cpp \ + session_stats.cpp \ + settings_pack.cpp \ + sha1.cpp \ + sha1_hash.cpp \ + sha256.cpp \ + smart_ban.cpp \ + socket_io.cpp \ + socket_type.cpp \ + socks5_stream.cpp \ + ssl.cpp \ + stack_allocator.cpp \ + stat.cpp \ + stat_cache.cpp \ + storage_utils.cpp \ + string_util.cpp \ + time.cpp \ + timestamp_history.cpp \ + torrent.cpp \ + torrent_handle.cpp \ + torrent_info.cpp \ + torrent_peer.cpp \ + torrent_peer_allocator.cpp \ + torrent_status.cpp \ + tracker_manager.cpp \ + truncate.cpp \ + udp_socket.cpp \ + udp_tracker_connection.cpp \ + upnp.cpp \ + ut_metadata.cpp \ + ut_pex.cpp \ + utf8.cpp \ + utp_socket_manager.cpp \ + utp_stream.cpp \ + version.cpp \ + web_connection_base.cpp \ + web_peer_connection.cpp \ + write_resume_data.cpp \ + xml_parse.cpp + +HEADERS = \ + add_torrent_params.hpp \ + address.hpp \ + alert.hpp \ + alert_types.hpp \ + announce_entry.hpp \ + assert.hpp \ + bdecode.hpp \ + bencode.hpp \ + bitfield.hpp \ + bloom_filter.hpp \ + bt_peer_connection.hpp \ + choker.hpp \ + client_data.hpp \ + close_reason.hpp \ + config.hpp \ + copy_ptr.hpp \ + crc32c.hpp \ + create_torrent.hpp \ + deadline_timer.hpp \ + debug.hpp \ + disabled_disk_io.hpp \ + disk_buffer_holder.hpp \ + disk_interface.hpp \ + disk_observer.hpp \ + download_priority.hpp \ + entry.hpp \ + enum_net.hpp \ + error.hpp \ + error_code.hpp \ + extensions.hpp \ + file.hpp \ + file_storage.hpp \ + file_layout.hpp \ + fingerprint.hpp \ + flags.hpp \ + fwd.hpp \ + gzip.hpp \ + hash_picker.hpp \ + hasher.hpp \ + hex.hpp \ + http_connection.hpp \ + http_parser.hpp \ + http_seed_connection.hpp \ + http_stream.hpp \ + http_tracker_connection.hpp \ + i2p_stream.hpp \ + identify_client.hpp \ + index_range.hpp \ + info_hash.hpp \ + io.hpp \ + io_context.hpp \ + io_service.hpp \ + ip_filter.hpp \ + ip_voter.hpp \ + libtorrent.hpp \ + link.hpp \ + load_torrent.hpp \ + lsd.hpp \ + magnet_uri.hpp \ + mmap_disk_io.hpp \ + mmap_storage.hpp \ + natpmp.hpp \ + netlink.hpp \ + operations.hpp \ + optional.hpp \ + parse_url.hpp \ + part_file.hpp \ + pe_crypto.hpp \ + peer.hpp \ + peer_class.hpp \ + peer_class_set.hpp \ + peer_class_type_filter.hpp \ + peer_connection.hpp \ + peer_connection_handle.hpp \ + peer_connection_interface.hpp \ + peer_id.hpp \ + peer_info.hpp \ + peer_list.hpp \ + peer_request.hpp \ + performance_counters.hpp \ + pex_flags.hpp \ + piece_block.hpp \ + piece_block_progress.hpp \ + piece_picker.hpp \ + platform_util.hpp \ + portmap.hpp \ + posix_disk_io.hpp \ + proxy_base.hpp \ + puff.hpp \ + random.hpp \ + read_resume_data.hpp \ + request_blocks.hpp \ + resolve_links.hpp \ + session.hpp \ + session_handle.hpp \ + session_params.hpp \ + session_settings.hpp \ + session_stats.hpp \ + session_status.hpp \ + session_types.hpp \ + settings_pack.hpp \ + sha1.hpp \ + sha1_hash.hpp \ + sha256.hpp \ + sliding_average.hpp \ + socket.hpp \ + socket_io.hpp \ + socket_type.hpp \ + socks5_stream.hpp \ + span.hpp \ + ssl.hpp \ + ssl_stream.hpp \ + stack_allocator.hpp \ + stat.hpp \ + stat_cache.hpp \ + storage.hpp \ + storage_defs.hpp \ + string_util.hpp \ + string_view.hpp \ + tailqueue.hpp \ + time.hpp \ + torrent.hpp \ + torrent_flags.hpp \ + torrent_handle.hpp \ + torrent_info.hpp \ + torrent_peer.hpp \ + torrent_peer_allocator.hpp \ + torrent_status.hpp \ + tracker_manager.hpp \ + truncate.hpp \ + udp_socket.hpp \ + udp_tracker_connection.hpp \ + union_endpoint.hpp \ + units.hpp \ + upnp.hpp \ + utf8.hpp \ + vector_utils.hpp \ + version.hpp \ + web_connection_base.hpp \ + web_peer_connection.hpp \ + write_resume_data.hpp \ + xml_parse.hpp \ + \ + aux_/alert_manager.hpp \ + aux_/aligned_union.hpp \ + aux_/alloca.hpp \ + aux_/allocating_handler.hpp \ + aux_/announce_entry.hpp \ + aux_/apply_pad_files.hpp \ + aux_/array.hpp \ + aux_/bandwidth_limit.hpp \ + aux_/bandwidth_manager.hpp \ + aux_/bandwidth_queue_entry.hpp \ + aux_/bandwidth_socket.hpp \ + aux_/bind_to_device.hpp \ + aux_/buffer.hpp \ + aux_/byteswap.hpp \ + aux_/container_wrapper.hpp \ + aux_/chained_buffer.hpp \ + aux_/cpuid.hpp \ + aux_/deferred_handler.hpp \ + aux_/deprecated.hpp \ + aux_/deque.hpp \ + aux_/dev_random.hpp \ + aux_/directory.hpp \ + aux_/disable_deprecation_warnings_push.hpp \ + aux_/disable_warnings_pop.hpp \ + aux_/disable_warnings_push.hpp \ + aux_/disk_buffer_pool.hpp \ + aux_/disk_io_thread_pool.hpp \ + aux_/disk_job_fence.hpp \ + aux_/disk_job_pool.hpp \ + aux_/drive_info.hpp \ + aux_/ed25519.hpp \ + aux_/escape_string.hpp \ + aux_/export.hpp \ + aux_/ffs.hpp \ + aux_/file_descriptor.hpp \ + aux_/file_pointer.hpp \ + aux_/file_progress.hpp \ + aux_/file_view_pool.hpp \ + aux_/generate_peer_id.hpp \ + aux_/has_block.hpp \ + aux_/hasher512.hpp \ + aux_/heterogeneous_queue.hpp \ + aux_/instantiate_connection.hpp \ + aux_/invariant_check.hpp \ + aux_/io.hpp \ + aux_/ip_helpers.hpp \ + aux_/ip_notifier.hpp \ + aux_/keepalive.hpp \ + aux_/listen_socket_handle.hpp \ + aux_/lsd.hpp \ + aux_/merkle.hpp \ + aux_/merkle_tree.hpp \ + aux_/mmap.hpp \ + aux_/mmap_disk_job.hpp \ + aux_/netlink_utils.hpp \ + aux_/noexcept_movable.hpp \ + aux_/numeric_cast.hpp \ + aux_/open_mode.hpp \ + aux_/packet_buffer.hpp \ + aux_/packet_pool.hpp \ + aux_/path.hpp \ + aux_/polymorphic_socket.hpp \ + aux_/pool.hpp \ + aux_/portmap.hpp \ + aux_/posix_part_file.hpp \ + aux_/posix_storage.hpp \ + aux_/proxy_settings.hpp \ + aux_/range.hpp \ + aux_/receive_buffer.hpp \ + aux_/resolver.hpp \ + aux_/resolver_interface.hpp \ + aux_/route.h \ + aux_/scope_end.hpp \ + aux_/session_call.hpp \ + aux_/session_impl.hpp \ + aux_/session_interface.hpp \ + aux_/session_settings.hpp \ + aux_/session_udp_sockets.hpp \ + aux_/set_socket_buffer.hpp \ + aux_/set_traffic_class.hpp \ + aux_/sha512.hpp \ + aux_/socket_type.hpp \ + aux_/storage_free_list.hpp \ + aux_/storage_utils.hpp \ + aux_/store_buffer.hpp \ + aux_/string_ptr.hpp \ + aux_/strview_less.hpp \ + aux_/suggest_piece.hpp \ + aux_/throw.hpp \ + aux_/time.hpp \ + aux_/timestamp_history.hpp \ + aux_/torrent_impl.hpp \ + aux_/torrent_list.hpp \ + aux_/unique_ptr.hpp \ + aux_/utp_socket_manager.hpp \ + aux_/utp_stream.hpp \ + aux_/vector.hpp \ + aux_/windows.hpp \ + aux_/win_cng.hpp \ + aux_/win_crypto_provider.hpp \ + aux_/win_file_handle.hpp \ + aux_/win_util.hpp \ + \ + extensions/smart_ban.hpp \ + extensions/ut_metadata.hpp \ + extensions/ut_pex.hpp \ + \ + kademlia/announce_flags.hpp \ + kademlia/dht_observer.hpp \ + kademlia/dht_settings.hpp \ + kademlia/dht_state.hpp \ + kademlia/dht_storage.hpp \ + kademlia/dht_tracker.hpp \ + kademlia/direct_request.hpp \ + kademlia/dos_blocker.hpp \ + kademlia/ed25519.hpp \ + kademlia/find_data.hpp \ + kademlia/get_item.hpp \ + kademlia/get_peers.hpp \ + kademlia/io.hpp \ + kademlia/item.hpp \ + kademlia/msg.hpp \ + kademlia/node.hpp \ + kademlia/node_entry.hpp \ + kademlia/node_id.hpp \ + kademlia/observer.hpp \ + kademlia/put_data.hpp \ + kademlia/refresh.hpp \ + kademlia/routing_table.hpp \ + kademlia/rpc_manager.hpp \ + kademlia/sample_infohashes.hpp \ + kademlia/traversal_algorithm.hpp \ + kademlia/types.hpp + +TRY_SIGNAL = \ + signal_error_code.cpp \ + signal_error_code.hpp \ + try_signal.cpp \ + try_signal.hpp \ + try_signal_mingw.hpp \ + try_signal_msvc.hpp \ + try_signal_posix.hpp \ + LICENSE \ + README.rst \ + Jamfile \ + CMakeLists.txt + +ASIO_GNUTLS = \ + LICENSE_1_0.txt \ + Jamfile \ + include/boost/asio/gnutls.hpp \ + include/boost/asio/gnutls \ + include/boost/asio/gnutls/rfc2818_verification.hpp \ + include/boost/asio/gnutls/stream_base.hpp \ + include/boost/asio/gnutls/error.hpp \ + include/boost/asio/gnutls/host_name_verification.hpp \ + include/boost/asio/gnutls/stream.hpp \ + include/boost/asio/gnutls/context_base.hpp \ + include/boost/asio/gnutls/verify_context.hpp \ + include/boost/asio/gnutls/context.hpp \ + README.md \ + test/unit_test.hpp \ + test/gnutls/context_base.cpp \ + test/gnutls/stream_base.cpp \ + test/gnutls/host_name_verification.cpp \ + test/gnutls/error.cpp \ + test/gnutls/Jamfile.v2 \ + test/gnutls/context.cpp \ + test/gnutls/rfc2818_verification.cpp \ + test/gnutls/stream.cpp + +SIM_SOURCES = \ + Jamfile \ + create_torrent.cpp \ + create_torrent.hpp \ + fake_peer.hpp \ + disk_io.hpp \ + disk_io.cpp \ + make_proxy_settings.hpp \ + setup_dht.cpp \ + setup_dht.hpp \ + setup_swarm.cpp \ + setup_swarm.hpp \ + test_auto_manage.cpp \ + test_checking.cpp \ + test_dht.cpp \ + test_dht_bootstrap.cpp \ + test_dht_rate_limit.cpp \ + test_dht_storage.cpp \ + test_error_handling.cpp \ + test_fast_extensions.cpp \ + test_http_connection.cpp \ + test_ip_filter.cpp \ + test_metadata_extension.cpp \ + test_optimistic_unchoke.cpp \ + test_pause.cpp \ + test_pe_crypto.cpp \ + test_peer_connection.cpp \ + test_save_resume.cpp \ + test_session.cpp \ + test_socks5.cpp \ + test_super_seeding.cpp \ + test_swarm.cpp \ + test_thread_pool.cpp \ + test_torrent_status.cpp \ + test_tracker.cpp \ + test_transfer.cpp \ + test_transfer_full_invalid_files.cpp \ + test_transfer_no_files.cpp \ + test_transfer_partial_valid_files.cpp \ + test_utp.cpp \ + test_web_seed.cpp \ + transfer_sim.hpp \ + transfer_sim.cpp \ + utils.cpp \ + utils.hpp + +LIBSIM_SOURCES = \ + acceptor.cpp \ + default_config.cpp \ + high_resolution_clock.cpp \ + high_resolution_timer.cpp \ + http_proxy.cpp \ + http_server.cpp \ + io_service.cpp \ + pcap.cpp \ + queue.cpp \ + resolver.cpp \ + simulation.cpp \ + simulator.cpp \ + sink_forwarder.cpp \ + socks_server.cpp \ + tcp_socket.cpp \ + udp_socket.cpp + +LIBSIM_HEADERS = \ + chrono.hpp \ + config.hpp \ + function.hpp \ + handler_allocator.hpp \ + http_proxy.hpp \ + http_server.hpp \ + noexcept_movable.hpp \ + packet.hpp \ + pcap.hpp \ + pop_warnings.hpp \ + push_warnings.hpp \ + queue.hpp \ + simulator.hpp \ + sink.hpp \ + sink_forwarder.hpp \ + socks_server.hpp \ + utils.hpp + +LIBSIM_EXTRA = \ + CMakeLists.txt \ + Jamfile \ + Jamroot.jam \ + LICENSE \ + README.rst + +LIBSIM_TESTS = \ + acceptor.cpp \ + main.cpp \ + multi_accept.cpp \ + multi_homed.cpp \ + null_buffers.cpp \ + parse_request.cpp \ + resolver.cpp \ + timer.cpp \ + udp_socket.cpp \ + catch.hpp + +TEST_SOURCES = \ + enum_if.cpp \ + test_add_torrent.cpp \ + test_alert_manager.cpp \ + test_alert_types.cpp \ + test_alloca.cpp \ + test_apply_pad.cpp \ + test_auto_unchoke.cpp \ + test_bandwidth_limiter.cpp \ + test_bdecode.cpp \ + test_bencoding.cpp \ + test_bitfield.cpp \ + test_bloom_filter.cpp \ + test_buffer.cpp \ + test_checking.cpp \ + test_copy_file.cpp \ + test_crc32.cpp \ + test_create_torrent.cpp \ + test_dht.cpp \ + test_dht_storage.cpp \ + test_direct_dht.cpp \ + test_dos_blocker.cpp \ + test_ed25519.cpp \ + test_enum_net.cpp \ + test_fast_extension.cpp \ + test_fence.cpp \ + test_ffs.cpp \ + test_file.cpp \ + test_file_progress.cpp \ + test_file_storage.cpp \ + test_flags.cpp \ + test_generate_peer_id.cpp \ + test_gzip.cpp \ + test_hash_picker.cpp \ + test_hasher.cpp \ + test_hasher512.cpp \ + test_heterogeneous_queue.cpp \ + test_http_connection.cpp \ + test_http_parser.cpp \ + test_identify_client.cpp \ + test_info_hash.cpp \ + test_io.cpp \ + test_ip_filter.cpp \ + test_ip_voter.cpp \ + test_listen_socket.cpp \ + test_lsd.cpp \ + test_magnet.cpp \ + test_merkle.cpp \ + test_merkle_tree.cpp \ + test_mmap.cpp \ + test_packet_buffer.cpp \ + test_part_file.cpp \ + test_pe_crypto.cpp \ + test_peer_classes.cpp \ + test_peer_list.cpp \ + test_peer_priority.cpp \ + test_piece_picker.cpp \ + test_primitives.cpp \ + test_priority.cpp \ + test_privacy.cpp \ + test_read_piece.cpp \ + test_read_resume.cpp \ + test_receive_buffer.cpp \ + test_recheck.cpp \ + test_remap_files.cpp \ + test_remove_torrent.cpp \ + test_resolve_links.cpp \ + test_resume.cpp \ + test_session.cpp \ + test_session_params.cpp \ + test_settings_pack.cpp \ + test_sha1_hash.cpp \ + test_similar_torrent.cpp \ + test_sliding_average.cpp \ + test_socket_io.cpp \ + test_span.cpp \ + test_ssl.cpp \ + test_stack_allocator.cpp \ + test_stat_cache.cpp \ + test_storage.cpp \ + test_store_buffer.cpp \ + test_string.cpp \ + test_tailqueue.cpp \ + test_threads.cpp \ + test_time.cpp \ + test_time_critical.cpp \ + test_timestamp_history.cpp \ + test_torrent.cpp \ + test_torrent_info.cpp \ + test_torrent_list.cpp \ + test_tracker.cpp \ + test_truncate.cpp \ + test_transfer.cpp \ + test_upnp.cpp \ + test_url_seed.cpp \ + test_utf8.cpp \ + test_utp.cpp \ + test_web_seed.cpp \ + test_web_seed_ban.cpp \ + test_web_seed_chunked.cpp \ + test_web_seed_http.cpp \ + test_web_seed_http_pw.cpp \ + test_web_seed_redirect.cpp \ + test_web_seed_socks4.cpp \ + test_web_seed_socks5.cpp \ + test_web_seed_socks5_no_peers.cpp \ + test_web_seed_socks5_pw.cpp \ + test_xml.cpp \ + \ + main.cpp \ + broadcast_socket.cpp \ + broadcast_socket.hpp \ + test.cpp \ + setup_transfer.cpp \ + dht_server.cpp \ + udp_tracker.cpp \ + peer_server.cpp \ + bittorrent_peer.cpp \ + make_torrent.cpp \ + web_seed_suite.cpp \ + swarm_suite.cpp \ + test_utils.cpp \ + settings.cpp \ + print_alerts.cpp \ + test.hpp \ + setup_transfer.hpp \ + dht_server.hpp \ + peer_server.hpp \ + udp_tracker.hpp \ + web_seed_suite.hpp \ + swarm_suite.hpp \ + test_utils.hpp \ + settings.hpp \ + make_torrent.hpp \ + bittorrent_peer.hpp \ + print_alerts.hpp + +TEST_TORRENTS = \ + absolute_filename.torrent \ + backslash_path.torrent \ + bad_name.torrent \ + base.torrent \ + collection.torrent \ + collection2.torrent \ + creation_date.torrent \ + dht_nodes.torrent \ + duplicate_files.torrent \ + duplicate_web_seeds.torrent \ + empty_httpseed.torrent \ + empty_path.torrent \ + empty_path_multi.torrent \ + empty-files-1.torrent \ + empty-files-2.torrent \ + empty-files-3.torrent \ + empty-files-4.torrent \ + empty-files-5.torrent \ + hidden_parent_path.torrent \ + httpseed.torrent \ + invalid_file_size.torrent \ + invalid_filename.torrent \ + invalid_filename2.torrent \ + invalid_info.torrent \ + invalid_name.torrent \ + invalid_name2.torrent \ + invalid_name3.torrent \ + invalid_path_list.torrent \ + invalid_piece_len.torrent \ + invalid_pieces.torrent \ + invalid_symlink.torrent \ + large.torrent \ + large_piece_size.torrent \ + long_name.torrent \ + many_pieces.torrent \ + missing_path_list.torrent \ + missing_piece_len.torrent \ + negative_file_size.torrent \ + negative_piece_len.torrent \ + negative_size.torrent \ + no_creation_date.torrent \ + no_files.torrent \ + no_name.torrent \ + overlapping_symlinks.torrent \ + pad_file.torrent \ + pad_file_no_path.torrent \ + parent_path.torrent \ + sample.torrent \ + similar.torrent \ + similar2.torrent \ + single_multi_file.torrent \ + slash_path.torrent \ + slash_path2.torrent \ + slash_path3.torrent \ + string.torrent \ + symlink1.torrent \ + symlink2.torrent \ + symlink_zero_size.torrent \ + unaligned_pieces.torrent \ + unordered.torrent \ + url_list.torrent \ + url_list2.torrent \ + url_list3.torrent \ + url_seed.torrent \ + url_seed_multi.torrent \ + url_seed_multi_single_file.torrent \ + url_seed_multi_space.torrent \ + url_seed_multi_space_nolist.torrent \ + whitespace_url.torrent \ + v2.torrent \ + v2_empty_file.torrent \ + v2_multipiece_file.torrent \ + v2_only.torrent \ + v2_invalid_filename.torrent \ + v2_mismatching_metadata.torrent \ + v2_no_power2_piece.torrent \ + v2_invalid_file.torrent \ + v2_deep_recursion.torrent \ + v2_non_multiple_piece_layer.torrent \ + v2_piece_layer_invalid_file_hash.torrent \ + v2_incomplete_piece_layer.torrent \ + v2_invalid_pad_file.torrent \ + v2_invalid_piece_layer.torrent \ + v2_invalid_piece_layer_root.torrent \ + v2_invalid_piece_layer_size.torrent \ + v2_unknown_piece_layer_entry.torrent \ + v2_multiple_files.torrent \ + v2_bad_file_alignment.torrent \ + v2_unordered_files.torrent \ + v2_overlong_integer.torrent \ + v2_missing_file_root_invalid_symlink.torrent \ + v2_symlinks.torrent \ + v2_no_piece_layers.torrent \ + v2_large_file.torrent \ + v2_large_offset.torrent \ + v2_piece_size.torrent \ + v2_zero_root.torrent \ + v2_zero_root_small.torrent \ + v2_hybrid.torrent \ + v2_hybrid-missing-tailpad.torrent \ + v2_invalid_root_hash.torrent \ + zero.torrent \ + zero2.torrent + +MUTABLE_TEST_TORRENTS = \ + test1.torrent \ + test1_pad_files.torrent \ + test1_single.torrent \ + test1_single_padded.torrent \ + test2.torrent \ + test2_pad_files.torrent \ + test3.torrent \ + test3_pad_files.torrent + +TEST_EXTRA = Jamfile \ + Jamfile \ + CMakeLists.txt \ + $(addprefix test_torrents/,${TEST_TORRENTS}) \ + $(addprefix mutable_test_torrents/,${MUTABLE_TEST_TORRENTS}) \ + root1.xml \ + root2.xml \ + root3.xml \ + ssl/regenerate_test_certificate.sh \ + ssl/dhparams.pem \ + ssl/invalid_peer_certificate.pem \ + ssl/invalid_peer_private_key.pem \ + ssl/peer_certificate.pem \ + ssl/peer_private_key.pem \ + ssl/root_ca_cert.pem \ + ssl/root_ca_private.pem \ + ssl/server.pem \ + zeroes.gz \ + corrupt.gz \ + invalid1.gz \ + utf8_test.txt \ + web_server.py \ + socks.py \ + http_proxy.py \ + root1.xml \ + root2.xml \ + root3.xml + +dist: FORCE + (cd docs; make) + rm -rf libtorrent-rasterbar-${VERSION} libtorrent-rasterbar-${VERSION}.tar.gz + mkdir libtorrent-rasterbar-${VERSION} + rsync -R ${EXTRA_DIST} \ + $(addprefix src/,${SOURCES}) \ + $(addprefix src/kademlia/,${KADEMLIA_SOURCES}) \ + $(addprefix include/libtorrent/,${HEADERS}) \ + $(addprefix examples/,${EXAMPLE_FILES}) \ + $(addprefix tools/,${TOOLS_FILES}) \ + $(addprefix bindings/python/,${PYTHON_FILES}) \ + $(addprefix test/,${TEST_SOURCES}) \ + $(addprefix test/,${TEST_EXTRA}) \ + $(addprefix simulation/,${SIM_SOURCES}) \ + $(addprefix deps/try_signal/,${TRY_SIGNAL}) \ + $(addprefix deps/asio-gnutls/,${ASIO_GNUTLS}) \ + $(addprefix simulation/libsimulator/,${LIBSIM_EXTRA}) \ + $(addprefix simulation/libsimulator/test,${LIBSIM_TEST}) \ + $(addprefix simulation/libsimulator/include/simulator/,${LIBSIM_HEADERS}) \ + $(addprefix simulation/libsimulator/src/,${LIBSIM_SOURCES}) \ + $(addprefix src/ed25519/,$(ED25519_SOURCE)) \ + libtorrent-rasterbar-${VERSION} + tar -czf libtorrent-rasterbar-${VERSION}.tar.gz libtorrent-rasterbar-${VERSION} + +FORCE: + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..58378c1 --- /dev/null +++ b/NEWS @@ -0,0 +1 @@ +See ChangeLog diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..207b248 --- /dev/null +++ b/README.rst @@ -0,0 +1,64 @@ +.. image:: docs/img/logo-color-text.png + +.. image:: https://github.com/arvidn/libtorrent/actions/workflows/windows.yml/badge.svg + :target: https://github.com/arvidn/libtorrent/actions/workflows/windows.yml + +.. image:: https://github.com/arvidn/libtorrent/actions/workflows/macos.yml/badge.svg + :target: https://github.com/arvidn/libtorrent/actions/workflows/macos.yml + +.. image:: https://github.com/arvidn/libtorrent/actions/workflows/linux.yml/badge.svg + :target: https://github.com/arvidn/libtorrent/actions/workflows/linux.yml + +.. image:: https://github.com/arvidn/libtorrent/actions/workflows/python.yml/badge.svg + :target: https://github.com/arvidn/libtorrent/actions/workflows/python.yml + +.. image:: https://ci.appveyor.com/api/projects/status/w7teauvub5813mew/branch/RC_2_0?svg=true + :target: https://ci.appveyor.com/project/arvidn/libtorrent/branch/RC_2_0 + +.. image:: https://api.cirrus-ci.com/github/arvidn/libtorrent.svg?branch=RC_2_0 + :target: https://cirrus-ci.com/github/arvidn/libtorrent + +.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/libtorrent.svg + :target: https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&q=proj%3Alibtorrent&can=1 + +.. image:: https://codecov.io/github/arvidn/libtorrent/coverage.svg?branch=RC_2_0 + :target: https://codecov.io/github/arvidn/libtorrent?branch=RC_2_0&view=all#sort=missing&dir=desc + +.. image:: https://www.openhub.net/p/rasterbar-libtorrent/widgets/project_thin_badge.gif + :target: https://www.openhub.net/p/rasterbar-libtorrent + +.. image:: https://bestpractices.coreinfrastructure.org/projects/3020/badge + :target: https://bestpractices.coreinfrastructure.org/en/projects/3020 + +libtorrent is an open source C++ library implementing the BitTorrent protocol, +along with most popular extensions, making it suitable for real world +deployment. It is configurable to be able to fit both servers and embedded +devices. + +The main goals of libtorrent are to be efficient and easy to use. + +See `libtorrent.org`__ for more detailed build and usage instructions. + +.. __: https://libtorrent.org + +To build with boost-build, make sure boost and boost-build is installed and run: + + b2 + +In the libtorrent root. To build the examples, run ``b2`` in the ``examples`` +directory. + +See `building.html`__ for more details on how to build and which configuration +options are available. For python bindings, see `the python docs`__. + +libtorrent `ABI report`_. + +.. _`ABI report`: https://abi-laboratory.pro/index.php?view=timeline&l=libtorrent + +libtorrent package versions in linux distributions, on repology_. + +.. _repology: https://repology.org/project/libtorrent-rasterbar/versions + +.. __: docs/building.rst +.. __: docs/python_binding.rst + diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..0c877c8 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,107 @@ +version: "{build}" +branches: + only: + - master + - RC_2_0 + - RC_1_2 + - RC_1_1 +image: Visual Studio 2017 +clone_depth: 1 +environment: + matrix: + - variant: debug + compiler: gcc + model: 32 + crypto: openssl + ssl_lib: /usr/local/include + ssl_include: /usr/local/lib + lib: 1 + - cmake: 1 + - variant: release + compiler: msvc-14.1 + model: 64 + crypto: openssl + ssl_lib: c:\OpenSSL-v111-Win64\lib + ssl_include: c:\OpenSSL-v111-Win64\include + tests: 1 + +artifacts: + - path: bindings/python/dist/* + name: python-module + +install: + - git submodule update --init --recursive + - set ROOT_DIRECTORY=%CD% + - cd %ROOT_DIRECTORY% + - if not defined api ( set api="desktop" ) + - if not defined compiler ( set compiler="" ) + - if not defined crypto ( set crypto=built-in ) + - if not defined ssl_lib ( set ssl_lib=c:\ ) + - if not defined ssl_include ( set ssl_include=c:\ ) + - cd %ROOT_DIRECTORY% + - set BOOST_ROOT=c:\Libraries\boost_1_69_0 + - set BOOST_BUILD_PATH=%BOOST_ROOT%\tools\build + - echo %BOOST_ROOT% + - echo %BOOST_BUILD_PATH% + - set PATH=%PATH%;%BOOST_BUILD_PATH% + - ps: '"using msvc : 14.1 ;`nusing gcc ;`nusing python : 3.7 : c:\\Python37-x64 : c:\\Python37-x64\\include : c:\\Python37-x64\\libs ;`n" | Set-Content $env:HOMEDRIVE\$env:HOMEPATH\user-config.jam' + - type %HOMEDRIVE%%HOMEPATH%\user-config.jam + - cd %ROOT_DIRECTORY% + - set PATH=c:\msys64\mingw32\bin;%PATH% + - g++ --version + - set PATH=c:\Python37-x64;%PATH% + - set PYTHON_INTERPRETER=c:\Python37-x64\python.exe + - python --version + - echo %ROOT_DIRECTORY% + - cd %BOOST_BUILD_PATH% + - bootstrap.bat >nul + - cd %ROOT_DIRECTORY% + +build_script: + + # just the library + - cd %ROOT_DIRECTORY% + - if defined lib ( + b2.exe --hash openssl-lib=%ssl_lib% openssl-include=%ssl_include% warnings=all warnings-as-errors=on %compiler% address-model=%model% picker-debugging=on invariant-checks=full variant=%variant% link=shared crypto=%crypto% asserts=on export-extra=on windows-api=%api% windows-version=win10 cxxstd=14 + ) + + # test + - cd %ROOT_DIRECTORY%\test + - if defined tests ( + b2.exe --hash openssl-lib=%ssl_lib% openssl-include=%ssl_include% warnings=all warnings-as-errors=on %compiler% address-model=%model% picker-debugging=on invariant-checks=full variant=%variant% link=shared crypto=%crypto% asserts=on export-extra=on windows-api=%api% windows-version=win10 testing.execute=off deterministic-tests + ) + + # python binding + - cd %ROOT_DIRECTORY%\bindings\python + # we use 64 bit python builds + # boost.python itself doesn't build warning free, so we can't build + # with warnings-as-errors + - if defined python ( + b2.exe --hash openssl-lib=%ssl_lib% openssl-include=%ssl_include% warnings=all %compiler% address-model=%model% picker-debugging=on invariant-checks=full variant=%variant% link=shared crypto=%crypto% asserts=on export-extra=on windows-api=%api% windows-version=win10 libtorrent-link=shared stage_module stage_dependencies + ) + - if defined python_dist ( + c:\Python37-x64\python.exe setup.py bdist --format=msi + ) + + # minimal support for cmake build + # we need to build the boost libraries we use with C++14 + # and stage it for cmake to pick up + - if defined cmake ( + cd %BOOST_ROOT% && + bjam cxxstd=14 release --with-python --with-system --layout=system address-model=64 link=shared stage && + cd %ROOT_DIRECTORY% && + mkdir build && + cd build && + cmake -DBOOST_LIBRARYDIR=%BOOST_ROOT%\stage\lib -DCMAKE_CXX_STANDARD=14 -Dbuild_tests=ON -Dbuild_examples=ON -Dbuild_tools=ON -Dpython-bindings=%python% -Dboost-python-module-name="python" -Dskip-python-runtime-test=true -DPython_ADDITIONAL_VERSIONS="2.7" -G "Visual Studio 15 2017" -A x64 .. && + cmake --build . --config Release --parallel %NUMBER_OF_PROCESSORS% -- -verbosity:minimal + ) + +test_script: + - cd %ROOT_DIRECTORY%\test + - if defined tests ( + appveyor-retry b2.exe -l500 --hash openssl-lib=%ssl_lib% openssl-include=%ssl_include% warnings=all warnings-as-errors=on %compiler% address-model=%model% picker-debugging=on invariant-checks=full variant=%variant% link=shared crypto=%crypto% asserts=on export-extra=on windows-api=%api% windows-version=win10 deterministic-tests + ) + + - if defined cmake ( + appveyor-retry ctest + ) diff --git a/bindings/CMakeLists.txt b/bindings/CMakeLists.txt new file mode 100644 index 0000000..5dd618f --- /dev/null +++ b/bindings/CMakeLists.txt @@ -0,0 +1,3 @@ +if (python-bindings) + add_subdirectory(python) +endif() diff --git a/bindings/Makefile.am b/bindings/Makefile.am new file mode 100644 index 0000000..c03bfbd --- /dev/null +++ b/bindings/Makefile.am @@ -0,0 +1,4 @@ + +SUBDIRS = python + +EXTRA_DIST = README.txt CMakeLists.txt diff --git a/bindings/README.txt b/bindings/README.txt new file mode 100644 index 0000000..977ca8c --- /dev/null +++ b/bindings/README.txt @@ -0,0 +1,3 @@ +Documentation covering building and using the python binding for libtorrent +is located in the main doc directory. See docs/python_binding.html + diff --git a/bindings/c/Jamfile b/bindings/c/Jamfile new file mode 100644 index 0000000..1933a90 --- /dev/null +++ b/bindings/c/Jamfile @@ -0,0 +1,38 @@ +use-project /torrent : ../.. ; + +rule libtorrent_linking ( properties * ) +{ + local result ; + + if gcc in $(properties) && shared in $(properties) + { + result += on ; + } + +# if gcc in $(properties) || darwin in $(properties) +# { +# result += hidden ; +# } + + return $(result) ; +} + +lib torrentc + + : # sources + library.cpp + + : # requirements + @libtorrent_linking + /torrent//torrent/static + . + + : # default build + static + + : # usage-requirements + . +; + +exe simple_client : simple_client.c torrentc ; + diff --git a/bindings/c/library.cpp b/bindings/c/library.cpp new file mode 100644 index 0000000..5b537a4 --- /dev/null +++ b/bindings/c/library.cpp @@ -0,0 +1,611 @@ +/* + +Copyright (c) 2009, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/session_status.hpp" +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/torrent_handle.hpp" + +#include +#include + +namespace +{ + std::vector handles; + + int find_handle(lt::torrent_handle h) + { + std::vector::const_iterator i + = std::find(handles.begin(), handles.end(), h); + if (i == handles.end()) return -1; + return i - handles.begin(); + } + + lt::torrent_handle get_handle(int i) + { + if (i < 0 || i >= int(handles.size())) return lt::torrent_handle(); + return handles[i]; + } + + int add_handle(lt::torrent_handle const& h) + { + std::vector::iterator i = std::find_if(handles.begin() + , handles.end() + , [](lt::torrent_handle const& h) { return !h.is_valid(); }); + if (i != handles.end()) + { + *i = h; + return i - handles.begin(); + } + + handles.push_back(h); + return handles.size() - 1; + } + + int set_int_value(void* dst, int* size, int val) + { + if (*size < sizeof(int)) return -2; + *((int*)dst) = val; + *size = sizeof(int); + return 0; + } + + void copy_proxy_setting(lt::proxy_settings* s, proxy_setting const* ps) + { + s->hostname.assign(ps->hostname); + s->port = ps->port; + s->username.assign(ps->username); + s->password.assign(ps->password); + s->type = (lt::proxy_settings::proxy_type)ps->type; + } +} + +extern "C" +{ + +TORRENT_EXPORT void* session_create(int tag, ...) +{ + using namespace lt; + + va_list lp; + va_start(lp, tag); + + fingerprint fing("LT", lt::version_major, lt::version_minor, lt::version_tiny, 0); + std::pair listen_range(-1, -1); + char const* listen_interface = "0.0.0.0"; + int flags = session::start_default_features | session::add_default_plugins; + int alert_mask = alert::error_notification; + + while (tag != TAG_END) + { + switch (tag) + { + case SES_FINGERPRINT: + { + char const* f = va_arg(lp, char const*); + fing.name[0] = f[0]; + fing.name[1] = f[1]; + break; + } + case SES_LISTENPORT: + listen_range.first = va_arg(lp, int); + break; + case SES_LISTENPORT_END: + listen_range.second = va_arg(lp, int); + break; + case SES_VERSION_MAJOR: + fing.major_version = va_arg(lp, int); + break; + case SES_VERSION_MINOR: + fing.minor_version = va_arg(lp, int); + break; + case SES_VERSION_TINY: + fing.revision_version = va_arg(lp, int); + break; + case SES_VERSION_TAG: + fing.tag_version = va_arg(lp, int); + break; + case SES_FLAGS: + flags = va_arg(lp, int); + break; + case SES_ALERT_MASK: + alert_mask = va_arg(lp, int); + break; + case SES_LISTEN_INTERFACE: + listen_interface = va_arg(lp, char const*); + break; + default: + // skip unknown tags + va_arg(lp, void*); + break; + } + + tag = va_arg(lp, int); + } + va_end(lp); + + if (listen_range.first != -1 && (listen_range.second == -1 + || listen_range.second < listen_range.first)) + listen_range.second = listen_range.first; + + return new (std::nothrow) session(fing, listen_range, listen_interface, flags, alert_mask); +} + +TORRENT_EXPORT void session_close(void* ses) +{ + delete (lt::session*)ses; +} + +TORRENT_EXPORT int session_add_torrent(void* ses, int tag, ...) +{ + using namespace lt; + + va_list lp; + va_start(lp, tag); + session* s = (session*)ses; + add_torrent_params params; + + char const* torrent_data = 0; + int torrent_size = 0; + + char const* resume_data = 0; + int resume_size = 0; + + char const* magnet_url = 0; + + error_code ec; + + while (tag != TAG_END) + { + switch (tag) + { + case TOR_FILENAME: + params.ti.reset(new (std::nothrow) torrent_info(va_arg(lp, char const*), ec)); + break; + case TOR_TORRENT: + torrent_data = va_arg(lp, char const*); + break; + case TOR_TORRENT_SIZE: + torrent_size = va_arg(lp, int); + break; + case TOR_INFOHASH: + params.ti.reset(new (std::nothrow) torrent_info(sha1_hash(va_arg(lp, char const*)))); + break; + case TOR_INFOHASH_HEX: + { + sha1_hash ih; + from_hex(va_arg(lp, char const*), 40, (char*)&ih[0]); + params.ti.reset(new (std::nothrow) torrent_info(ih)); + break; + } + case TOR_MAGNETLINK: + magnet_url = va_arg(lp, char const*); + break; + case TOR_TRACKER_URL: + params.tracker_url = va_arg(lp, char const*); + break; + case TOR_RESUME_DATA: + resume_data = va_arg(lp, char const*); + break; + case TOR_RESUME_DATA_SIZE: + resume_size = va_arg(lp, int); + break; + case TOR_SAVE_PATH: + params.save_path = va_arg(lp, char const*); + break; + case TOR_NAME: + params.name = va_arg(lp, char const*); + break; + case TOR_PAUSED: + params.paused = va_arg(lp, int) != 0; + break; + case TOR_AUTO_MANAGED: + params.auto_managed = va_arg(lp, int) != 0; + break; + case TOR_DUPLICATE_IS_ERROR: + params.duplicate_is_error = va_arg(lp, int) != 0; + break; + case TOR_USER_DATA: + params.userdata = va_arg(lp, void*); + break; + case TOR_SEED_MODE: + params.seed_mode = va_arg(lp, int) != 0; + break; + case TOR_OVERRIDE_RESUME_DATA: + params.override_resume_data = va_arg(lp, int) != 0; + break; + case TOR_STORAGE_MODE: + params.storage_mode = (lt::storage_mode_t)va_arg(lp, int); + break; + default: + // ignore unknown tags + va_arg(lp, void*); + break; + } + + tag = va_arg(lp, int); + } + va_end(lp); + + if (!params.ti && torrent_data && torrent_size) + params.ti.reset(new (std::nothrow) torrent_info(torrent_data, torrent_size)); + + if (resume_data && resume_size) + { + params.resume_data.assign(resume_data, resume_data + resume_size); + } + torrent_handle h; + if (!params.ti && magnet_url) + { + h = add_magnet_uri(*s, magnet_url, params, ec); + } + else + { + h = s->add_torrent(params, ec); + } + + if (!h.is_valid()) + { + return -1; + } + + int i = find_handle(h); + if (i == -1) i = add_handle(h); + + return i; +} + +TORRENT_EXPORT void session_remove_torrent(void* ses, int tor, int flags) +{ + using namespace lt; + torrent_handle h = get_handle(tor); + if (!h.is_valid()) return; + + session* s = (session*)ses; + s->remove_torrent(h, flags); +} + +TORRENT_EXPORT int session_pop_alert(void* ses, char* dest, int len, int* category) +{ + using namespace lt; + + session* s = (session*)ses; + + std::auto_ptr a = s->pop_alert(); + if (!a.get()) return -1; + + if (category) *category = a->category(); + strncpy(dest, a->message().c_str(), len - 1); + dest[len - 1] = 0; + + return 0; // for now +} + +TORRENT_EXPORT int session_set_settings(void* ses, int tag, ...) +{ + using namespace lt; + + session* s = (session*)ses; + + va_list lp; + va_start(lp, tag); + + while (tag != TAG_END) + { + switch (tag) + { + case SET_UPLOAD_RATE_LIMIT: + s->set_upload_rate_limit(va_arg(lp, int)); + break; + case SET_DOWNLOAD_RATE_LIMIT: + s->set_download_rate_limit(va_arg(lp, int)); + break; + case SET_LOCAL_UPLOAD_RATE_LIMIT: + s->set_local_upload_rate_limit(va_arg(lp, int)); + break; + case SET_LOCAL_DOWNLOAD_RATE_LIMIT: + s->set_local_download_rate_limit(va_arg(lp, int)); + break; + case SET_MAX_UPLOAD_SLOTS: + s->set_max_uploads(va_arg(lp, int)); + break; + case SET_MAX_CONNECTIONS: + s->set_max_connections(va_arg(lp, int)); + break; + case SET_HALF_OPEN_LIMIT: + s->set_max_half_open_connections(va_arg(lp, int)); + break; + case SET_PEER_PROXY: + { + lt::proxy_settings ps; + copy_proxy_setting(&ps, va_arg(lp, struct proxy_setting const*)); + s->set_peer_proxy(ps); + } + case SET_WEB_SEED_PROXY: + { + lt::proxy_settings ps; + copy_proxy_setting(&ps, va_arg(lp, struct proxy_setting const*)); + s->set_web_seed_proxy(ps); + } + case SET_TRACKER_PROXY: + { + lt::proxy_settings ps; + copy_proxy_setting(&ps, va_arg(lp, struct proxy_setting const*)); + s->set_tracker_proxy(ps); + } + case SET_ALERT_MASK: + { + s->set_alert_mask(va_arg(lp, int)); + } +#ifndef TORRENT_DISABLE_DHT + case SET_DHT_PROXY: + { + lt::proxy_settings ps; + copy_proxy_setting(&ps, va_arg(lp, struct proxy_setting const*)); + s->set_dht_proxy(ps); + } +#endif + case SET_PROXY: + { + lt::proxy_settings ps; + copy_proxy_setting(&ps, va_arg(lp, struct proxy_setting const*)); + s->set_peer_proxy(ps); + s->set_web_seed_proxy(ps); + s->set_tracker_proxy(ps); +#ifndef TORRENT_DISABLE_DHT + s->set_dht_proxy(ps); +#endif + } + default: + // ignore unknown tags + va_arg(lp, void*); + break; + } + + tag = va_arg(lp, int); + } + va_end(lp); + return 0; +} + +TORRENT_EXPORT int session_get_setting(void* ses, int tag, void* value, int* value_size) +{ + using namespace lt; + session* s = (session*)ses; + + switch (tag) + { + case SET_UPLOAD_RATE_LIMIT: + return set_int_value(value, value_size, s->upload_rate_limit()); + case SET_DOWNLOAD_RATE_LIMIT: + return set_int_value(value, value_size, s->download_rate_limit()); + case SET_LOCAL_UPLOAD_RATE_LIMIT: + return set_int_value(value, value_size, s->local_upload_rate_limit()); + case SET_LOCAL_DOWNLOAD_RATE_LIMIT: + return set_int_value(value, value_size, s->local_download_rate_limit()); + case SET_MAX_UPLOAD_SLOTS: + return set_int_value(value, value_size, s->max_uploads()); + case SET_MAX_CONNECTIONS: + return set_int_value(value, value_size, s->max_connections()); + case SET_HALF_OPEN_LIMIT: + return set_int_value(value, value_size, s->max_half_open_connections()); + default: + return -2; + } +} + +TORRENT_EXPORT int session_get_status(void* sesptr, struct session_status* s, int struct_size) +{ + lt::session* ses = (lt::session*)sesptr; + + lt::session_status ss = ses->status(); + if (struct_size != sizeof(session_status)) return -1; + + s->has_incoming_connections = ss.has_incoming_connections; + + s->upload_rate = ss.upload_rate; + s->download_rate = ss.download_rate; + s->total_download = ss.total_download; + s->total_upload = ss.total_upload; + + s->payload_upload_rate = ss.payload_upload_rate; + s->payload_download_rate = ss.payload_download_rate; + s->total_payload_download = ss.total_payload_download; + s->total_payload_upload = ss.total_payload_upload; + + s->ip_overhead_upload_rate = ss.ip_overhead_upload_rate; + s->ip_overhead_download_rate = ss.ip_overhead_download_rate; + s->total_ip_overhead_download = ss.total_ip_overhead_download; + s->total_ip_overhead_upload = ss.total_ip_overhead_upload; + + s->dht_upload_rate = ss.dht_upload_rate; + s->dht_download_rate = ss.dht_download_rate; + s->total_dht_download = ss.total_dht_download; + s->total_dht_upload = ss.total_dht_upload; + + s->tracker_upload_rate = ss.tracker_upload_rate; + s->tracker_download_rate = ss.tracker_download_rate; + s->total_tracker_download = ss.total_tracker_download; + s->total_tracker_upload = ss.total_tracker_upload; + + s->total_redundant_bytes = ss.total_redundant_bytes; + s->total_failed_bytes = ss.total_failed_bytes; + + s->num_peers = ss.num_peers; + s->num_unchoked = ss.num_unchoked; + s->allowed_upload_slots = ss.allowed_upload_slots; + + s->up_bandwidth_queue = ss.up_bandwidth_queue; + s->down_bandwidth_queue = ss.down_bandwidth_queue; + + s->up_bandwidth_bytes_queue = ss.up_bandwidth_bytes_queue; + s->down_bandwidth_bytes_queue = ss.down_bandwidth_bytes_queue; + + s->optimistic_unchoke_counter = ss.optimistic_unchoke_counter; + s->unchoke_counter = ss.unchoke_counter; + + s->dht_nodes = ss.dht_nodes; + s->dht_node_cache = ss.dht_node_cache; + s->dht_torrents = ss.dht_torrents; + s->dht_global_nodes = ss.dht_global_nodes; + return 0; +} + +TORRENT_EXPORT int torrent_get_status(int tor, torrent_status* s, int struct_size) +{ + lt::torrent_handle h = get_handle(tor); + if (!h.is_valid()) return -1; + + lt::torrent_status ts = h.status(); + + if (struct_size != sizeof(torrent_status)) return -1; + + s->state = (state_t)ts.state; + s->paused = ts.paused; + s->progress = ts.progress; + strncpy(s->error, ts.error.c_str(), sizeof(s->error)-1); + s->error[sizeof(s->error)-1] = '\0'; + s->next_announce = lt::total_seconds(ts.next_announce); + s->announce_interval = lt::total_seconds(ts.announce_interval); + strncpy(s->current_tracker, ts.current_tracker.c_str(), sizeof(s->current_tracker)-1); + s->current_tracker[sizeof(s->current_tracker)-1] = '\0'; + s->total_download = ts.total_download = ts.total_download = ts.total_download; + s->total_upload = ts.total_upload = ts.total_upload = ts.total_upload; + s->total_payload_download = ts.total_payload_download; + s->total_payload_upload = ts.total_payload_upload; + s->total_failed_bytes = ts.total_failed_bytes; + s->total_redundant_bytes = ts.total_redundant_bytes; + s->download_rate = ts.download_rate; + s->upload_rate = ts.upload_rate; + s->download_payload_rate = ts.download_payload_rate; + s->upload_payload_rate = ts.upload_payload_rate; + s->num_seeds = ts.num_seeds; + s->num_peers = ts.num_peers; + s->num_complete = ts.num_complete; + s->num_incomplete = ts.num_incomplete; + s->list_seeds = ts.list_seeds; + s->list_peers = ts.list_peers; + s->connect_candidates = ts.connect_candidates; + s->num_pieces = ts.num_pieces; + s->total_done = ts.total_done; + s->total_wanted_done = ts.total_wanted_done; + s->total_wanted = ts.total_wanted; + s->distributed_copies = ts.distributed_copies; + s->block_size = ts.block_size; + s->num_uploads = ts.num_uploads; + s->num_connections = ts.num_connections; + s->uploads_limit = ts.uploads_limit; + s->connections_limit = ts.connections_limit; +// s->storage_mode = (storage_mode_t)ts.storage_mode; + s->up_bandwidth_queue = ts.up_bandwidth_queue; + s->down_bandwidth_queue = ts.down_bandwidth_queue; + s->all_time_upload = ts.all_time_upload; + s->all_time_download = ts.all_time_download; + s->active_time = ts.active_time; + s->seeding_time = ts.seeding_time; + s->seed_rank = ts.seed_rank; + s->last_scrape = ts.last_scrape; + s->has_incoming = ts.has_incoming; + s->seed_mode = ts.seed_mode; + return 0; +} + +TORRENT_EXPORT int torrent_set_settings(int tor, int tag, ...) +{ + using namespace lt; + torrent_handle h = get_handle(tor); + if (!h.is_valid()) return -1; + + va_list lp; + va_start(lp, tag); + + while (tag != TAG_END) + { + switch (tag) + { + case SET_UPLOAD_RATE_LIMIT: + h.set_upload_limit(va_arg(lp, int)); + break; + case SET_DOWNLOAD_RATE_LIMIT: + h.set_download_limit(va_arg(lp, int)); + break; + case SET_MAX_UPLOAD_SLOTS: + h.set_max_uploads(va_arg(lp, int)); + break; + case SET_MAX_CONNECTIONS: + h.set_max_connections(va_arg(lp, int)); + break; + case SET_SEQUENTIAL_DOWNLOAD: + h.set_sequential_download(va_arg(lp, int) != 0); + break; + case SET_SUPER_SEEDING: + h.super_seeding(va_arg(lp, int) != 0); + break; + default: + // ignore unknown tags + va_arg(lp, void*); + break; + } + + tag = va_arg(lp, int); + } + va_end(lp); + return 0; +} + +TORRENT_EXPORT int torrent_get_setting(int tor, int tag, void* value, int* value_size) +{ + using namespace lt; + torrent_handle h = get_handle(tor); + if (!h.is_valid()) return -1; + + switch (tag) + { + case SET_UPLOAD_RATE_LIMIT: + return set_int_value(value, value_size, h.upload_limit()); + case SET_DOWNLOAD_RATE_LIMIT: + return set_int_value(value, value_size, h.download_limit()); + case SET_MAX_UPLOAD_SLOTS: + return set_int_value(value, value_size, h.max_uploads()); + case SET_MAX_CONNECTIONS: + return set_int_value(value, value_size, h.max_connections()); + case SET_SEQUENTIAL_DOWNLOAD: + return set_int_value(value, value_size, h.is_sequential_download()); + case SET_SUPER_SEEDING: + return set_int_value(value, value_size, h.super_seeding()); + default: + return -2; + } +} + +} // extern "C" + diff --git a/bindings/c/libtorrent.h b/bindings/c/libtorrent.h new file mode 100644 index 0000000..f761cae --- /dev/null +++ b/bindings/c/libtorrent.h @@ -0,0 +1,296 @@ +/* + +Copyright (c) 2009, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_H +#define LIBTORRENT_H + +enum tags +{ + TAG_END = 0, + + SES_FINGERPRINT, // char const*, 2 character string + SES_LISTENPORT, // int + SES_LISTENPORT_END, // int + SES_VERSION_MAJOR, // int + SES_VERSION_MINOR, // int + SES_VERSION_TINY, // int + SES_VERSION_TAG, // int + SES_FLAGS, // int + SES_ALERT_MASK, // int + SES_LISTEN_INTERFACE, // char const* + + // === add_torrent tags === + + // identifying the torrent to add + TOR_FILENAME = 0x100, // char const* + TOR_TORRENT, // char const*, specify size of buffer with TOR_TORRENT_SIZE + TOR_TORRENT_SIZE, // int + TOR_INFOHASH, // char const*, must point to a 20 byte array + TOR_INFOHASH_HEX, // char const*, must point to a 40 byte string + TOR_MAGNETLINK, // char const*, url + + TOR_TRACKER_URL, // char const* + TOR_RESUME_DATA, // char const* + TOR_RESUME_DATA_SIZE, // int + TOR_SAVE_PATH, // char const* + TOR_NAME, // char const* + TOR_PAUSED, // int + TOR_AUTO_MANAGED, // int + TOR_DUPLICATE_IS_ERROR, // int + TOR_USER_DATA, //void* + TOR_SEED_MODE, // int + TOR_OVERRIDE_RESUME_DATA, // int + TOR_STORAGE_MODE, // int + + SET_UPLOAD_RATE_LIMIT = 0x200, // int + SET_DOWNLOAD_RATE_LIMIT, // int + SET_LOCAL_UPLOAD_RATE_LIMIT, // int + SET_LOCAL_DOWNLOAD_RATE_LIMIT, // int + SET_MAX_UPLOAD_SLOTS, // int + SET_MAX_CONNECTIONS, // int + SET_SEQUENTIAL_DOWNLOAD, // int, torrent only + SET_SUPER_SEEDING, // int, torrent only + SET_HALF_OPEN_LIMIT, // int, session only + SET_PEER_PROXY, // proxy_setting const*, session_only + SET_WEB_SEED_PROXY, // proxy_setting const*, session_only + SET_TRACKER_PROXY, // proxy_setting const*, session_only + SET_DHT_PROXY, // proxy_setting const*, session_only + SET_PROXY, // proxy_setting const*, session_only + SET_ALERT_MASK, // int, session_only +}; + +struct proxy_setting +{ + char hostname[256]; + int port; + + char username[256]; + char password[256]; + + int type; +}; + +enum category_t +{ + cat_error = 0x1, + cat_peer = 0x2, + cat_port_mapping = 0x4, + cat_storage = 0x8, + cat_tracker = 0x10, + cat_debug = 0x20, + cat_status = 0x40, + cat_progress = 0x80, + cat_ip_block = 0x100, + cat_performance_warning = 0x200, + cat_dht = 0x400, + + cat_all_categories = 0xffffffff +}; + +enum proxy_type_t +{ + proxy_none, + proxy_socks4, + proxy_socks5, + proxy_socks5_pw, + proxy_http, + proxy_http_pw +}; + +enum storage_mode_t +{ + storage_mode_allocate = 0, + storage_mode_sparse +}; + +enum state_t +{ + queued_for_checking, + checking_files, + downloading_metadata, + downloading, + finished, + seeding, + allocating, + checking_resume_data +}; + +struct torrent_status +{ + enum state_t state; + int paused; + float progress; + char error[1024]; + int next_announce; + int announce_interval; + char current_tracker[512]; + long long total_download; + long long total_upload; + long long total_payload_download; + long long total_payload_upload; + long long total_failed_bytes; + long long total_redundant_bytes; + float download_rate; + float upload_rate; + float download_payload_rate; + float upload_payload_rate; + int num_seeds; + int num_peers; + int num_complete; + int num_incomplete; + int list_seeds; + int list_peers; + int connect_candidates; + + // what to do? +// bitfield pieces; + + int num_pieces; + long long total_done; + long long total_wanted_done; + long long total_wanted; + float distributed_copies; + int block_size; + int num_uploads; + int num_connections; + int uploads_limit; + int connections_limit; +// enum storage_mode_t storage_mode; + int up_bandwidth_queue; + int down_bandwidth_queue; + long long all_time_upload; + long long all_time_download; + int active_time; + int seeding_time; + int seed_rank; + int last_scrape; + int has_incoming; + int seed_mode; +}; + +struct session_status +{ + int has_incoming_connections; + + float upload_rate; + float download_rate; + long long total_download; + long long total_upload; + + float payload_upload_rate; + float payload_download_rate; + long long total_payload_download; + long long total_payload_upload; + + float ip_overhead_upload_rate; + float ip_overhead_download_rate; + long long total_ip_overhead_download; + long long total_ip_overhead_upload; + + float dht_upload_rate; + float dht_download_rate; + long long total_dht_download; + long long total_dht_upload; + + float tracker_upload_rate; + float tracker_download_rate; + long long total_tracker_download; + long long total_tracker_upload; + + long long total_redundant_bytes; + long long total_failed_bytes; + + int num_peers; + int num_unchoked; + int allowed_upload_slots; + + int up_bandwidth_queue; + int down_bandwidth_queue; + + int up_bandwidth_bytes_queue; + int down_bandwidth_bytes_queue; + + int optimistic_unchoke_counter; + int unchoke_counter; + + int dht_nodes; + int dht_node_cache; + int dht_torrents; + long long dht_global_nodes; +// std::vector active_requests; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +// the functions whose signature ends with: +// , int first_tag, ...); +// takes a tag list. The tag list is a series +// of tag-value pairs. The tags are constants +// identifying which property the value controls. +// The type of the value varies between tags. +// The enumeration above specifies which type +// it expects. All tag lists must always be +// terminated by TAG_END. + +// use SES_* tags in tag list +void* session_create(int first_tag, ...); +void session_close(void* ses); + +// use TOR_* tags in tag list +int session_add_torrent(void* ses, int first_tag, ...); +void session_remove_torrent(void* ses, int tor, int flags); + +// return < 0 if there are no alerts. Otherwise returns the +// type of alert that was returned +int session_pop_alert(void* ses, char* dest, int len, int* category); + +int session_get_status(void* ses, struct session_status* s, int struct_size); + +// use SET_* tags in tag list +int session_set_settings(void* ses, int first_tag, ...); +int session_get_setting(void* ses, int tag, void* value, int* value_size); + +int torrent_get_status(int tor, struct torrent_status* s, int struct_size); + +// use SET_* tags in tag list +int torrent_set_settings(int tor, int first_tag, ...); +int torrent_get_setting(int tor, int tag, void* value, int* value_size); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/bindings/c/simple_client.c b/bindings/c/simple_client.c new file mode 100644 index 0000000..3c1d7a2 --- /dev/null +++ b/bindings/c/simple_client.c @@ -0,0 +1,123 @@ +/* + +Copyright (c) 2009, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include + +int quit = 0; + +void stop(int signal) +{ + quit = 1; +} + +int main(int argc, char* argv[]) +{ + if (argc != 2) + { + fprintf(stderr, "usage: ./simple_client torrent-file\n"); + return 1; + } + + int ret = 0; + void* ses = session_create( + SES_LISTENPORT, 6881, + SES_LISTENPORT_END, 6889, + SES_ALERT_MASK, ~(cat_progress | cat_port_mapping | cat_debug | cat_performance_warning | cat_peer), + TAG_END); + + int t = session_add_torrent(ses, + TOR_FILENAME, argv[1], + TOR_SAVE_PATH, "./", + TAG_END); + + if (t < 0) + { + fprintf(stderr, "Failed to add torrent\n"); + ret = 1; + goto exit; + } + + struct torrent_status st; + + printf("press ctrl-C to stop\n"); + + signal(SIGINT, &stop); + signal(SIGABRT, &stop); + signal(SIGQUIT, &stop); + + while (quit == 0) + { + char const* message = ""; + + char const* state[] = {"queued", "checking", "downloading metadata" + , "downloading", "finished", "seeding", "allocating" + , "checking_resume_data"}; + + if (torrent_get_status(t, &st, sizeof(st)) < 0) break; + printf("\r%3.f%% %d kB (%5.f kB/s) up: %d kB (%5.f kB/s) peers: %d '%s' %s " + , (double)st.progress * 100. + , (int)(st.total_payload_download / 1000) + , (double)st.download_payload_rate / 1000. + , (int)(st.total_payload_upload / 1000) + , (double)st.upload_payload_rate / 1000. + , st.num_peers + , state[st.state] + , message); + + + char msg[400]; + while (session_pop_alert(ses, msg, sizeof(msg), 0) >= 0) + { + printf("%s\n", msg); + } + + if (strlen(st.error) > 0) + { + fprintf(stderr, "\nERROR: %s\n", st.error); + break; + } + + fflush(stdout); + usleep(1000000); + } + printf("\nclosing\n"); + +exit: + + session_close(ses); + return ret; +} + diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt new file mode 100644 index 0000000..2f4de48 --- /dev/null +++ b/bindings/python/CMakeLists.txt @@ -0,0 +1,128 @@ +cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR) # Configurable policies: <= CMP0102 + +# To build python bindings we need a python executable and boost python module. Unfortunately, +# their names might not be interlinked and we can not implement a general solution. +# The code below assumes default boost installation, when the module for python 3 is named 'python3'. +# To customize that one can provide a name for the Boost::python module via +# 'boost-python-module-name' variable when invoking cmake. +# E.g. on Gentoo with python 3.7 and Boost::python library name 'libboost_python-3.7.so' +# the parameter would be -Dboost-python-module-name="python-3.7". + +# The extension module and the cpython executable have to use the same C runtime library. On Windows +# Python is compiled with MSVC and we will test MSVC version to make sure that it is the same for +# the given Python version and our extension module. See https://wiki.python.org/moin/WindowsCompilers +# for details. We provide a flag to skip this test for whatever reason (pass -Dskip-python-runtime-test=True) + +# Sets _ret to a list of python versions (major.minor) that use the same MSVC runtime as this build does +# assumes MSVC was detected already +# See https://en.wikipedia.org/wiki/Microsoft_Visual_C++#Internal_version_numbering +# See https://devguide.python.org/#status-of-python-branches for supported python versions +function(_get_compatible_python_versions _ret) + if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19 AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 20) + list(APPEND _tmp 3.6 3.7 3.8 3.9 3.10 3.11) + endif() + set(${_ret} ${_tmp} PARENT_SCOPE) +endfunction() + + +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" AND NOT skip-python-runtime-test) + _get_compatible_python_versions(Python_ADDITIONAL_VERSIONS) +endif() + +find_package(Python3 COMPONENTS Interpreter Development REQUIRED) +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" AND NOT skip-python-runtime-test) + message(STATUS "Testing found python version. Requested: ${Python_ADDITIONAL_VERSIONS}, found: ${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}") + if (NOT "${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}" IN_LIST Python_ADDITIONAL_VERSIONS) + message(FATAL_ERROR "Incompatible Python and C runtime: MSVC ${CMAKE_CXX_COMPILER_VERSION} and Python ${Python3_VERSION}") + endif() +endif() + +set(boost-python-module-name "python${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR}" CACHE STRING "Boost::python module name, e.g. 'python-3.7'") + +find_package(Boost REQUIRED COMPONENTS ${boost-python-module-name}) + +Python3_add_library(python-libtorrent MODULE WITH_SOABI + src/alert.cpp + src/converters.cpp + src/create_torrent.cpp + src/datetime.cpp + src/entry.cpp + src/error_code.cpp + src/fingerprint.cpp + src/info_hash.cpp + src/ip_filter.cpp + src/load_torrent.cpp + src/magnet_uri.cpp + src/module.cpp + src/peer_info.cpp + src/session.cpp + src/session_settings.cpp + src/sha1_hash.cpp + src/sha256_hash.cpp + src/string.cpp + src/torrent_handle.cpp + src/torrent_info.cpp + src/torrent_status.cpp + src/utility.cpp + src/version.cpp +) + +set_target_properties(python-libtorrent + PROPERTIES + OUTPUT_NAME libtorrent +) + +if (MSVC) + target_compile_options(python-libtorrent PRIVATE /bigobj) +endif() + +target_link_libraries(python-libtorrent + PRIVATE + torrent-rasterbar + "Boost::${boost-python-module-name}" +) + +# Bindings module uses deprecated libtorrent features, thus we disable these warnings +if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + check_cxx_compiler_flag("-Wno-deprecated-declarations" _WNO_DEPRECATED_DECLARATIONS) + if (_WNO_DEPRECATED_DECLARATIONS) + target_compile_options(python-libtorrent PRIVATE -Wno-deprecated-declarations) + endif() +endif() + +if (python-install-system-dir) + set(_PYTHON3_SITE_ARCH "${Python3_SITEARCH}") +else() + execute_process( + COMMAND "${Python3_EXECUTABLE}" -c [=[ +import distutils.sysconfig +print(distutils.sysconfig.get_python_lib(prefix='', plat_specific=True)) +]=] + OUTPUT_VARIABLE _PYTHON3_SITE_ARCH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) +endif() + +message(STATUS "Python 3 site packages: ${_PYTHON3_SITE_ARCH}") +message(STATUS "Python 3 extension suffix: ${Python3_SOABI}") + +install(TARGETS python-libtorrent DESTINATION "${_PYTHON3_SITE_ARCH}") + +if (python-egg-info) + set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.cmake.in") + set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py") + set(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/timestamp") + set(DEPS python-libtorrent "${SETUP_PY}") + + configure_file(${SETUP_PY_IN} ${SETUP_PY} @ONLY) + + add_custom_command(OUTPUT ${OUTPUT} + COMMAND ${Python3_EXECUTABLE} ${SETUP_PY} egg_info + COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT} + DEPENDS ${DEPS} + ) + + add_custom_target(python_bindings ALL DEPENDS ${OUTPUT}) + + install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/libtorrent.egg-info" DESTINATION "${_PYTHON3_SITE_ARCH}") +endif() diff --git a/bindings/python/Jamfile b/bindings/python/Jamfile new file mode 100644 index 0000000..7714201 --- /dev/null +++ b/bindings/python/Jamfile @@ -0,0 +1,364 @@ +import python ; +import feature ; +import feature : feature ; +import project ; +import targets ; +import "class" : new ; +import modules ; + +use-project /torrent : ../.. ; + +BOOST_ROOT = [ modules.peek : BOOST_ROOT ] ; + +CXXFLAGS = [ modules.peek : CXXFLAGS ] ; +LDFLAGS = [ modules.peek : LDFLAGS ] ; + +ECHO "CXXFLAGS =" $(CXXFLAGS) ; +ECHO "LDFLAGS =" $(LDFLAGS) ; + +# this is used to make bjam use the same version of python which is executing setup.py + +feature libtorrent-link : shared static prebuilt : composite propagated ; + +feature libtorrent-python-pic : off on : composite propagated link-incompatible ; +feature.compose on : -fPIC ; + +# when invoking the install_module target, this feature can be specified to +# install the python module to a specific directory +feature python-install-path : : free path ; + +# when not specifying a custom install path, this controls whether to install +# the python module in the system directory or user-specific directory +feature python-install-scope : user system : ; + +# copied from boost 1.63's boost python jamfile +rule find-py3-version +{ + local BOOST_VERSION_TAG = [ modules.peek boostcpp : BOOST_VERSION_TAG ] ; + if $(BOOST_VERSION_TAG) >= 1_67 + { + # starting with boost 1.67.0 boost python no longer define a separate + # target for python3 (boost_python3) so then we just use the regular + # boost_python target + return ; + } + local versions = [ feature.values python ] ; + local py3ver ; + for local v in $(versions) + { + if $(v) >= 3.0 + { + py3ver = $(v) ; + } + } + return $(py3ver) ; +} + +if $(BOOST_ROOT) +{ + use-project /boost : $(BOOST_ROOT) ; + alias boost_python : /boost/python//boost_python : : : $(BOOST_ROOT) ; + if [ find-py3-version ] + { + alias boost_python3 : /boost/python//boost_python3 : : : $(BOOST_ROOT) ; + } + else + { + alias boost_python3 : boost_python ; + } +} +else +{ + local boost-lib-search-path = + /opt/local/lib + /usr/lib + /usr/local/lib + /sw/lib + /usr/g++/lib + ; + + local boost-include-path = + /opt/local/include + /opt/homebrew/include + /usr/local/include + /usr/sfw/include + ; + + import version ; + + rule boost_python_version ( name : type ? : properties * ) + { + # examples of names for the boost_python library: + # ubuntu bionic (1.65.1): libboost_python3-py36.so.1.65.1 + # ubuntu bionic (1.65): libboost_python-py36.so + # libboost_python3-py36.so + # libboost_python3.so + # ubuntu bionic (1.62.0): libboost_python-py36.so.1.62.0 + # ubuntu focal (1.67.0): libboost_python38.so.1.67.0 + # ubuntu focal (1.71.0): libboost_python38.so + # ubuntu groovy (1.71.0): libboost_python38.so + # ubuntu hirsute(1.71.0): libboost_python39.so + # debian buster (1.67.0): libboost_python37.so + # libboost_python3.so + # libboost_python3-py37.so + # debian sid (1.74.0): libboost_python39.so + # debian sid (1.71.0): libboost_python39.so + # debian bullseye (1.71): libboost_python39.so + # devian buster-backports (1.71): libboost_python37.so + # debian buster (1.67): libboost_python37.so.1.67.0 + # debian stretch (1.62.0): libboost_python-py35.so.1.62.0 + # debian stretch (1.62): libboost_python-py35.so + # debian jessie (1.55.0): libboost_python-py34.so.1.55.0 + # boost Jamfile: libboost_python38.so.1.73.0 + + local py-version-str = [ $(properties).get ] ; + local py-version = "" ; + local infix = "" ; + if $(py-version-str) { + py-version = [ SPLIT_BY_CHARACTERS $(py-version-str) : "." ] ; + + if [ version.version-less $(py-version) : 3 7 ] && [ $(properties).get ] = linux + { + infix = "-py" ; + } + } + local boost-python-lib = "boost_python" $(infix) $(py-version) ; + + if $(type) in SEARCHED_LIB + { + return $(boost-python-lib:J) ; + } + return ; + } + + lib boost_python : : @boost_python_version : : $(boost-include-path) ; + alias boost_python3 : boost_python ; +} + +lib prebuilt_libtorrent : : torrent-rasterbar : : ../../include ; +lib prebuilt_libtorrent : : windows torrent : : ../../include ; + +rule libtorrent_linking ( properties * ) +{ + local result ; + + # allow larger .obj files (with more sections) + if msvc in $(properties) || intel-win in $(properties) + { + # allow larger .obj files (with more sections) + result += /bigobj ; + } + + if gcc in $(properties) && windows in $(properties) + { + # allow larger .obj files (with more sections) + result += -Wa,-mbig-obj ; + } + + if ! windows in $(properties) + && gcc in $(properties) + && static in $(properties) + { + result += on ; + } + + if gcc in $(properties) + || darwin in $(properties) + || clang in $(properties) + || clang-darwin in $(properties) + { + # hide non-external symbols + result += -fvisibility=hidden ; + result += -fvisibility-inlines-hidden ; + + if ( gcc in $(properties) ) + { + result += -Wl,-Bsymbolic ; + } + } + + if static in $(properties) + { + ECHO "WARNING: you probably want to specify libtorrent-link=static rather than link=static" ; + } + + local BOOST_VERSION_TAG = [ modules.peek boostcpp : BOOST_VERSION_TAG ] ; + if static in $(properties) && $(BOOST_VERSION_TAG) < 1_74 && linux in $(properties) + { + ECHO "WARNING: you cannot link statically against boost-python on linux before version 1.74.0, because it links against pthread statically in that case, which is not allowed" ; + } + + local boost_python_lib ; + + for local prop in $(properties) + { + switch $(prop) + { + case 2.* : boost_python_lib = boost_python ; + case 3.* : boost_python_lib = boost_python3 ; + } + } + + if ! $(boost_python_lib) + { + ECHO "WARNING: unknown python version" ; + boost_python_lib = boost_python ; + } + + # linux must link dynamically against boost python because it pulls + # in libpthread, which must be linked dynamically since we're building a .so + # (the static build of libpthread is not position independent) + if shared in $(properties) || ( linux in $(properties) && $(BOOST_VERSION_TAG) < 1_74 ) + { + result += $(boost_python_lib)/shared/off ; + } + else + { + result += $(boost_python_lib)/static/off ; + } + + if shared in $(properties) + { + result += /torrent//torrent/shared ; + } + else if static in $(properties) + { + result += /torrent//torrent/static ; + } + else + { + result += prebuilt_libtorrent ; + } + + return $(result) ; +} + +# this is a copy of the rule from boost-build's python-extension, but without +# specifying no as a mandatory property. That property +# would otherwise cause build failures because it suppresses linking against the +# runtime library and kernel32 on windows + +rule my-python-extension ( name : sources * : requirements * : default-build * : + usage-requirements * ) +{ + requirements += /python//python_for_extensions ; + + local project = [ project.current ] ; + + targets.main-target-alternative + [ new typed-target $(name) : $(project) : PYTHON_EXTENSION + : [ targets.main-target-sources $(sources) : $(name) ] + : [ targets.main-target-requirements $(requirements) : $(project) ] + : [ targets.main-target-default-build $(default-build) : $(project) ] + ] ; +} + +my-python-extension libtorrent + : # sources + src/module.cpp + src/sha1_hash.cpp + src/sha256_hash.cpp + src/info_hash.cpp + src/converters.cpp + src/create_torrent.cpp + src/fingerprint.cpp + src/utility.cpp + src/session.cpp + src/entry.cpp + src/torrent_info.cpp + src/string.cpp + src/torrent_handle.cpp + src/torrent_status.cpp + src/session_settings.cpp + src/version.cpp + src/alert.cpp + src/datetime.cpp + src/peer_info.cpp + src/ip_filter.cpp + src/magnet_uri.cpp + src/error_code.cpp + src/load_torrent.cpp + : # requirements + src + gcc:-Wno-deprecated-declarations + darwin:-Wno-deprecated-declarations + darwin:-Wno-unused-command-line-argument + @libtorrent_linking + openssl:/torrent//ssl + openssl:/torrent//crypto + "$(CXXFLAGS:J= )" + "$(LDFLAGS:J= )" + # C4268: 'identifier' : 'const' static/global data initialized + # with compiler generated default constructor fills the object with zeros + msvc:/wd4268 + : # default-build + all + 14 + : # usage-requirements + false + ; + +rule python-install-dir ( properties * ) +{ + local install-dir = [ feature.get-values python-install-path : $(properties) ] ; + if ( $(install-dir) != "" ) + { + # if the user has provided an install location, use that one + return $(install-dir) ; + } + + local python-interpreter = [ feature.get-values python.interpreter : $(properties) ] ; + if ( $(python-interpreter) = "" ) + { + return . ; + } + + # sys.path are defined differently between python2 and python3 + + local python-path ; + if system in $(properties) + { + python-path = [ SHELL "$(python-interpreter) -c \"import distutils.sysconfig; import sys; sys.stdout.write(distutils.sysconfig.get_python_lib())\"" ] ; + } + else + { + python-path = [ SHELL "$(python-interpreter) -c \"import site; import sys; sys.stdout.write(site.USER_SITE)\"" ] ; + } + + if $(python-path) = "" + { + return . ; + } + + ECHO "python install directory:" $(python-path) ; + return $(python-path) ; +} + +install install_module + : libtorrent + : @python-install-dir + PYTHON_EXTENSION + ; + +explicit install_module ; + +install stage_module + : libtorrent + : . + PYTHON_EXTENSION + : 14 + ; + +install stage_dependencies + : /torrent//torrent + boost_python + boost_python3 + : dependencies + on + SHARED_LIB + : 14 + ; + +explicit stage_module ; +explicit stage_dependencies ; + diff --git a/bindings/python/client.py b/bindings/python/client.py new file mode 100755 index 0000000..300572f --- /dev/null +++ b/bindings/python/client.py @@ -0,0 +1,381 @@ +#!/usr/bin/env python3 + +# Copyright Daniel Wallin 2006. Use, modification and distribution is +# subject to the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + +import sys +import atexit +import libtorrent as lt +import time +import os.path + + +class WindowsConsole: + def __init__(self): + self.console = Console.getconsole() + + def clear(self): + self.console.page() + + def write(self, str): + self.console.write(str) + + def sleep_and_input(self, seconds): + time.sleep(seconds) + if msvcrt.kbhit(): + return msvcrt.getch() + return None + + +class UnixConsole: + def __init__(self): + self.fd = sys.stdin + self.old = termios.tcgetattr(self.fd.fileno()) + new = termios.tcgetattr(self.fd.fileno()) + new[3] = new[3] & ~termios.ICANON + new[6][termios.VTIME] = 0 + new[6][termios.VMIN] = 1 + termios.tcsetattr(self.fd.fileno(), termios.TCSADRAIN, new) + + atexit.register(self._onexit) + + def _onexit(self): + termios.tcsetattr(self.fd.fileno(), termios.TCSADRAIN, self.old) + + def clear(self): + sys.stdout.write('\033[2J\033[0;0H') + sys.stdout.flush() + + def write(self, str): + sys.stdout.write(str) + sys.stdout.flush() + + def sleep_and_input(self, seconds): + read, __, __ = select.select( + [self.fd.fileno()], [], [], seconds) + if len(read) > 0: + return self.fd.read(1) + return None + + +if os.name == 'nt': + import Console + import msvcrt +else: + import termios + import select + + +def write_line(console, line): + console.write(line) + + +def add_suffix(val): + prefix = ['B', 'kB', 'MB', 'GB', 'TB'] + for i in range(len(prefix)): + if abs(val) < 1000: + if i == 0: + return '%5.3g%s' % (val, prefix[i]) + else: + return '%4.3g%s' % (val, prefix[i]) + val /= 1000 + + return '%6.3gPB' % val + + +def progress_bar(progress, width): + assert(progress <= 1) + progress_chars = int(progress * width + 0.5) + return progress_chars * '#' + (width - progress_chars) * '-' + + +def print_peer_info(console, peers): + + out = (' down (total ) up (total )' + ' q r flags block progress client\n') + + for p in peers: + + out += '%s/s ' % add_suffix(p.down_speed) + out += '(%s) ' % add_suffix(p.total_download) + out += '%s/s ' % add_suffix(p.up_speed) + out += '(%s) ' % add_suffix(p.total_upload) + out += '%2d ' % p.download_queue_length + out += '%2d ' % p.upload_queue_length + + out += 'I' if p.flags & lt.peer_info.interesting else '.' + out += 'C' if p.flags & lt.peer_info.choked else '.' + out += 'i' if p.flags & lt.peer_info.remote_interested else '.' + out += 'c' if p.flags & lt.peer_info.remote_choked else '.' + out += 'e' if p.flags & lt.peer_info.supports_extensions else '.' + out += 'l' if p.flags & lt.peer_info.local_connection else 'r' + out += ' ' + + if p.downloading_piece_index >= 0: + assert(p.downloading_progress <= p.downloading_total) + out += progress_bar(float(p.downloading_progress) / + p.downloading_total, 15) + else: + out += progress_bar(0, 15) + out += ' ' + + if p.flags & lt.peer_info.handshake: + id = 'waiting for handshake' + elif p.flags & lt.peer_info.connecting: + id = 'connecting to peer' + else: + id = p.client + + out += '%s\n' % id[:10] + + write_line(console, out) + + +def print_download_queue(console, download_queue): + + out = "" + + for e in download_queue: + out += '%4d: [' % e['piece_index'] + for b in e['blocks']: + s = b['state'] + if s == 3: + out += '#' + elif s == 2: + out += '=' + elif s == 1: + out += '-' + else: + out += ' ' + out += ']\n' + + write_line(console, out) + + +def add_torrent(ses, filename, options): + atp = lt.add_torrent_params() + if filename.startswith('magnet:'): + atp = lt.parse_magnet_uri(filename) + else: + ti = lt.torrent_info(filename) + resume_file = os.path.join(options.save_path, ti.name() + '.fastresume') + try: + atp = lt.read_resume_data(open(resume_file, 'rb').read()) + except Exception as e: + print('failed to open resume file "%s": %s' % (resume_file, e)) + atp.ti = ti + + atp.save_path = options.save_path + atp.storage_mode = lt.storage_mode_t.storage_mode_sparse + atp.flags |= lt.torrent_flags.duplicate_is_error \ + | lt.torrent_flags.auto_managed \ + | lt.torrent_flags.duplicate_is_error + ses.async_add_torrent(atp) + + +def main(): + from optparse import OptionParser + + parser = OptionParser() + + parser.add_option('-p', '--port', type='int', help='set listening port') + + parser.add_option( + '-i', '--listen-interface', type='string', + help='set interface for incoming connections', ) + + parser.add_option( + '-o', '--outgoing-interface', type='string', + help='set interface for outgoing connections') + + parser.add_option( + '-d', '--max-download-rate', type='float', + help='the maximum download rate given in kB/s. 0 means infinite.') + + parser.add_option( + '-u', '--max-upload-rate', type='float', + help='the maximum upload rate given in kB/s. 0 means infinite.') + + parser.add_option( + '-s', '--save-path', type='string', + help='the path where the downloaded file/folder should be placed.') + + parser.add_option( + '-r', '--proxy-host', type='string', + help='sets HTTP proxy host and port (separated by \':\')') + + parser.set_defaults( + port=6881, + listen_interface='0.0.0.0', + outgoing_interface='', + max_download_rate=0, + max_upload_rate=0, + save_path='.', + proxy_host='' + ) + + (options, args) = parser.parse_args() + + if options.port < 0 or options.port > 65525: + options.port = 6881 + + options.max_upload_rate *= 1000 + options.max_download_rate *= 1000 + + if options.max_upload_rate <= 0: + options.max_upload_rate = -1 + if options.max_download_rate <= 0: + options.max_download_rate = -1 + + settings = { + 'user_agent': 'python_client/' + lt.__version__, + 'listen_interfaces': '%s:%d' % (options.listen_interface, options.port), + 'download_rate_limit': int(options.max_download_rate), + 'upload_rate_limit': int(options.max_upload_rate), + 'alert_mask': lt.alert.category_t.all_categories, + 'outgoing_interfaces': options.outgoing_interface, + } + + if options.proxy_host != '': + settings['proxy_hostname'] = options.proxy_host.split(':')[0] + settings['proxy_type'] = lt.proxy_type_t.http + settings['proxy_port'] = options.proxy_host.split(':')[1] + + ses = lt.session(settings) + + # map torrent_handle to torrent_status + torrents = {} + alerts_log = [] + + for f in args: + add_torrent(ses, f, options) + + if os.name == 'nt': + console = WindowsConsole() + else: + console = UnixConsole() + + alive = True + while alive: + console.clear() + + out = '' + + for h, t in torrents.items(): + out += 'name: %-40s\n' % t.name[:40] + + if t.state != lt.torrent_status.seeding: + state_str = ['queued', 'checking', 'downloading metadata', + 'downloading', 'finished', 'seeding', + '', 'checking fastresume'] + out += state_str[t.state] + ' ' + + out += '%5.4f%% ' % (t.progress * 100) + out += progress_bar(t.progress, 49) + out += '\n' + + out += 'total downloaded: %d Bytes\n' % t.total_done + out += 'peers: %d seeds: %d distributed copies: %d\n' % \ + (t.num_peers, t.num_seeds, t.distributed_copies) + out += '\n' + + out += 'download: %s/s (%s) ' \ + % (add_suffix(t.download_rate), add_suffix(t.total_download)) + out += 'upload: %s/s (%s) ' \ + % (add_suffix(t.upload_rate), add_suffix(t.total_upload)) + + if t.state != lt.torrent_status.seeding: + out += 'info-hash: %s\n' % t.info_hashes + out += 'next announce: %s\n' % t.next_announce + out += 'tracker: %s\n' % t.current_tracker + + write_line(console, out) + + print_peer_info(console, t.handle.get_peer_info()) + print_download_queue(console, t.handle.get_download_queue()) + + if t.state != lt.torrent_status.seeding: + try: + out = '\n' + fp = h.file_progress() + ti = t.torrent_file + for idx, p in enumerate(fp): + out += progress_bar(p / float(ti.files().file_size(idx)), 20) + out += ' ' + ti.files().file_path(idx) + '\n' + write_line(console, out) + except Exception: + pass + + write_line(console, 76 * '-' + '\n') + write_line(console, '(q)uit), (p)ause), (u)npause), (r)eannounce\n') + write_line(console, 76 * '-' + '\n') + + alerts = ses.pop_alerts() + for a in alerts: + alerts_log.append(a.message()) + + # add new torrents to our list of torrent_status + if isinstance(a, lt.add_torrent_alert): + h = a.handle + h.set_max_connections(60) + h.set_max_uploads(-1) + torrents[h] = h.status() + + # update our torrent_status array for torrents that have + # changed some of their state + if isinstance(a, lt.state_update_alert): + for s in a.status: + torrents[s.handle] = s + + if len(alerts_log) > 20: + alerts_log = alerts_log[-20:] + + for a in alerts_log: + write_line(console, a + '\n') + + c = console.sleep_and_input(0.5) + + ses.post_torrent_updates() + if not c: + continue + + if c == 'r': + for h in torrents: + h.force_reannounce() + elif c == 'q': + alive = False + elif c == 'p': + for h in torrents: + h.pause() + elif c == 'u': + for h in torrents: + h.resume() + + ses.pause() + for h, t in torrents.items(): + if not h.is_valid() or not t.has_metadata: + continue + h.save_resume_data() + + while len(torrents) > 0: + alerts = ses.pop_alerts() + for a in alerts: + if isinstance(a, lt.save_resume_data_alert): + print(a) + data = lt.write_resume_data_buf(a.params) + h = a.handle + if h in torrents: + open(os.path.join(options.save_path, torrents[h].name + '.fastresume'), 'wb').write(data) + del torrents[h] + + if isinstance(a, lt.save_resume_data_failed_alert): + h = a.handle + if h in torrents: + print('failed to save resume data for ', torrents[h].name) + del torrents[h] + time.sleep(0.5) + + +main() diff --git a/bindings/python/dummy_data.py b/bindings/python/dummy_data.py new file mode 100644 index 0000000..9803163 --- /dev/null +++ b/bindings/python/dummy_data.py @@ -0,0 +1,34 @@ +import libtorrent as lt + +import hashlib +import random + +PIECE_LENGTH = 16384 +NAME = b"test.txt" +LEN = PIECE_LENGTH * 9 + 1000 +# Use 7-bit data so we can test piece data as either bytes or str +DATA = bytes(random.getrandbits(7) for _ in range(LEN)) +PIECES = [DATA[i:i + PIECE_LENGTH] for i in range(0, LEN, PIECE_LENGTH)] + +INFO_DICT = { + b"name": NAME, + b"piece length": PIECE_LENGTH, + b"length": len(DATA), + b"pieces": b"".join(hashlib.sha1(p).digest() for p in PIECES), + } + +DICT = { + b"info": INFO_DICT, +} + + +def get_infohash_bytes(): + return hashlib.sha1(lt.bencode(INFO_DICT)).digest() + + +def get_infohash(): + return get_infohash_bytes().hex() + + +def get_sha1_hash(): + return lt.sha1_hash(get_infohash_bytes()) diff --git a/bindings/python/make_torrent.py b/bindings/python/make_torrent.py new file mode 100755 index 0000000..85a2229 --- /dev/null +++ b/bindings/python/make_torrent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 + + +import sys +import os +import libtorrent + +if len(sys.argv) < 3: + print('usage make_torrent.py file tracker-url') + sys.exit(1) + +input = os.path.abspath(sys.argv[1]) + +fs = libtorrent.file_storage() + +# def predicate(f): +# print f +# return True +# libtorrent.add_files(fs, input, predicate) + +parent_input = os.path.split(input)[0] + +# if we have a single file, use it because os.walk does not work on a single files +if os.path.isfile(input): + size = os.path.getsize(input) + fs.add_file(input, size) + +for root, dirs, files in os.walk(input): + # skip directories starting with . + if os.path.split(root)[1][0] == '.': + continue + + for f in files: + # skip files starting with . + if f[0] == '.': + continue + + # skip thumbs.db on windows + if f == 'Thumbs.db': + continue + + fname = os.path.join(root[len(parent_input) + 1:], f) + size = os.path.getsize(os.path.join(parent_input, fname)) + print('%10d kiB %s' % (size / 1024, fname)) + fs.add_file(fname, size) + +if fs.num_files() == 0: + print('no files added') + sys.exit(1) + +t = libtorrent.create_torrent(fs, 0, 4 * 1024 * 1024) + +t.add_tracker(sys.argv[2]) +t.set_creator('libtorrent %s' % libtorrent.__version__) + +libtorrent.set_piece_hashes(t, parent_input, lambda x: sys.stdout.write('.')) +sys.stdout.write('\n') + +f = open('out.torrent', 'wb+') +f.write(libtorrent.bencode(t.generate())) +f.close() diff --git a/bindings/python/setup.cfg b/bindings/python/setup.cfg new file mode 100644 index 0000000..a6bb68d --- /dev/null +++ b/bindings/python/setup.cfg @@ -0,0 +1,5 @@ +[metadata] +version = 2.0.11 + +[build_ext] +cxxstd = 14 diff --git a/bindings/python/setup.py b/bindings/python/setup.py new file mode 100644 index 0000000..7880f9f --- /dev/null +++ b/bindings/python/setup.py @@ -0,0 +1,521 @@ +#!/usr/bin/env python3 + +import contextlib +import functools +import itertools +import logging as log +import os +import pathlib +import re +import shlex +import subprocess +import sys +import sysconfig +import tempfile +from typing import Callable +from typing import cast +from typing import IO +from typing import Iterator +from typing import List +from typing import Optional +from typing import Sequence +from typing import Set +from typing import Tuple +import warnings + +import setuptools +import setuptools.command.build_ext as build_ext_lib + + +def b2_bool(value: bool) -> str: + if value: + return "on" + return "off" + + +def b2_version() -> Tuple[int, ...]: + # NB: b2 --version returns exit status 1 + proc = subprocess.run( + ["b2", "--version"], stdout=subprocess.PIPE, universal_newlines=True + ) + # Expected output examples: + # Boost.Build 2015.07-git + # B2 4.3-git + m = re.match(r".*\s([\d\.]+).*", proc.stdout) + assert m is not None, f"{proc.stdout} doesn't match expected output" + result = tuple(int(part) for part in re.split(r"\.", m.group(1))) + # Boost 1.71 changed from YYYY.MM to version 4.0. Return an "epoch" as the first + # part of the tuple to distinguish these version patterns. + if result[0] > 1999: + return (0, *result) + else: + return (1, *result) + + +# Frustratingly, the "bdist_*" unconditionally (re-)run "build" without +# args, even ignoring "build_*" earlier on the same command line. This +# means "build_*" must be a no-op if some build output exists, even if that +# output might have been generated with different args (like +# "--define=FOO"). b2 does not know how to be "naively idempotent" like +# this; it will only generate outputs that exactly match the build request. +# +# It doesn't work to short-circuit initialize_options() / finalize_options(), +# as this doesn't play well with the way options are externally manipulated by +# distutils. +# +# It DOES work to short-circuit Distribution.reinitialize_command(), so we do +# that here. + + +class B2Distribution(setuptools.Distribution): + def reinitialize_command( + self, command: str, reinit_subcommands: int = 0 + ) -> setuptools.Command: + if command == "build_ext": + return cast(setuptools.Command, self.get_command_obj("build_ext")) + return cast( + setuptools.Command, + super().reinitialize_command( + command, reinit_subcommands=reinit_subcommands + ), + ) + + +# Various setuptools logic expects us to provide Extension instances for each +# extension in the distro. +class StubExtension(setuptools.Extension): + def __init__(self, name: str): + # An empty sources list ensures the base build_ext command won't build + # anything + super().__init__(name, sources=[]) + + +def b2_escape(value: str) -> str: + value = value.replace("\\", "\\\\") + value = value.replace('"', '\\"') + return f'"{value}"' + + +def write_b2_python_config( + include_dirs: Sequence[str], + library_dirs: Sequence[str], + ext_suffix: str, + config: IO[str], +) -> None: + write = config.write + # b2 keys python environments by X.Y version, breaking ties by matching + # a property list, called the "condition" of the environment. To ensure + # b2 always picks the environment we define here, we define a special + # feature for the condition and include that in the build request. + + # Note that we might try to reuse a property we know will be set, like + # TORRENT_FOO. But python.jam actually modifies the build request + # in this case, so that TORRENT_FOO becomes something like + # TORRENT_FOO,3.7,linux:... which causes chaos. + # We should always define a custom feature for the condition. + write("import feature ;\n") + write("feature.feature libtorrent-python : on ;\n") + + # python.jam's autodetection of library paths and include paths has various + # bugs, and has very poor support of non-system python environments, + # such as pyenv or virtualenvs. distutils' autodetection is much more + # robust, and we trust it more. In case distutils gives empty results, + # feed garbage values to boost to block its autodetection. + + write("using python") + write(f" : {sysconfig.get_python_version()}") + write(f" : {b2_escape(sys.executable)}") + write(" : ") + if include_dirs: + write(" ".join(b2_escape(path) for path in include_dirs)) + else: + write("__BLOCK_AUTODETECTION__") + write(" : ") + if library_dirs: + # Note that python.jam only accepts one library dir! We depend on + # passing other library dirs by other means. Not sure if we should + # do something smarter here, like pass the first directory that exists. + write(b2_escape(library_dirs[0])) + else: + write("__BLOCK_AUTODETECTION__") + write(" : on") + + # Note that all else being equal, we'd like to exactly control the output + # filename, so distutils can find it. However: + # 1. We can only control part of the filename; the prefix is controlled by + # our Jamfile and the final suffix is controlled by python.jam. + # 2. Debian patched python.jam to disregard the configured ext_suffix + # anyway; they always override it with the same sysconfig var we use, + # found by invoking the executable. + + # So instead of applying an arbitrary name, we just try to guarantee that + # b2 produces a name that distutils would expect, on all platforms. In + # other words we apply debian's override everywhere, and hope no other + # overrides ever disagree with us. + + # python.jam appends the platform-specific final suffix on its own. I can't + # find a consistent value from sysconfig for this. + for plat_suffix in (".pyd", ".dll", ".so", ".sl"): + if ext_suffix.endswith(plat_suffix): + ext_suffix = ext_suffix[: -len(plat_suffix)] + break + write(f" : {b2_escape(ext_suffix)}") + write(" ;\n") + + +PYTHON_BINDING_DIR = pathlib.Path(__file__).parent.absolute() + + +class LibtorrentBuildExt(build_ext_lib.build_ext): + CONFIG_MODE_DISTUTILS = "distutils" + CONFIG_MODE_B2 = "b2" + CONFIG_MODES = (CONFIG_MODE_DISTUTILS, CONFIG_MODE_B2) + + user_options = build_ext_lib.build_ext.user_options + [ + ( + "config-mode=", + None, + "'b2' or 'distutils' (default). " + "In b2 mode, setup.py will just invoke b2 using --b2-args. " + "It will not attempt to auto-configure b2 or override any " + "args. " + "In distutils mode, setup.py will attempt to configure " + "and invoke b2 to match the expectations and behavior of " + "distutils (libtorrent will be built against the invoking " + "python, etc; note not all behaviors are currently supported). " + "The feature set will match the version found on pypi. " + "You can selectively override the auto-configuration in " + "this mode with --no-autoconf or --b2-args. For example " + "--b2-args=python=x.y or --no-autoconf=python will prevent " + "python from being auto-configured. " + "Note that --b2-args doesn't currently understand implicit features. " + "Be sure to include their names, e.g. --b2-args=variant=debug", + ), + ( + "b2-args=", + None, + "The full argument string to pass to b2. This is parsed with shlex, to " + "support arguments with spaces. For example: --b2-args 'variant=debug " + '"my-feature=a value with spaces"\'', + ), + ( + "no-autoconf=", + None, + "Space-separated list of b2 arguments that should not be " + "auto-configured in distutils mode.", + ), + ( + "libtorrent-link=", + None, + "(DEPRECATED; use --b2-args=libtorrent-link=...) ", + ), + ("boost-link=", None, "(DEPRECATED; use --b2-args=boost-link=...) "), + ("toolset=", None, "(DEPRECATED; use --b2-args=toolset=...) b2 toolset"), + ( + "pic", + None, + "(DEPRECATED; use --b2-args=libtorrent-python-pic=on) " + "whether to compile with -fPIC", + ), + ( + "optimization=", + None, + "(DEPRECATED; use --b2-args=optimization=...) " "b2 optimization mode", + ), + ( + "hash", + None, + "(DEPRECATED; use --b2-args=--hash) " + "use a property hash for the build directory, rather than " + "property subdirectories", + ), + ( + "cxxstd=", + None, + "boost cxxstd value (14, 17, 20, etc.)", + ), + ] + + boolean_options = build_ext_lib.build_ext.boolean_options + ["pic", "hash"] + + def initialize_options(self) -> None: + self.libtorrent_link: Optional[str] = None + self.boost_link: Optional[str] = None + self.toolset: Optional[str] = None + self.pic: Optional[bool] = None + self.optimization: Optional[str] = None + self.hash: Optional[bool] = None + self.cxxstd: Optional[str] = None + + self.config_mode = self.CONFIG_MODE_DISTUTILS + self.b2_args = "" + self.no_autoconf = "" + + self._b2_args_split: List[str] = [] + self._b2_args_configured: Set[str] = set() + + self._b2_version = b2_version() + + log.info("b2 version: %s", self._b2_version) + + super().initialize_options() + + def finalize_options(self) -> None: + super().finalize_options() + + if self.config_mode not in self.CONFIG_MODES: + raise setuptools.errors.DistutilsOptionError( + f"--config-mode must be one of {self.CONFIG_MODES}" + ) + + # shlex the args here to warn early on bad config + self._b2_args_split = shlex.split(self.b2_args or "") + self._b2_args_configured.update(shlex.split(self.no_autoconf or "")) + + # In b2's arg system only single-character args can consume the next + # arg, but it may also be concatenated. So we may have "-x", + # "-x value", or "-xvalue". All --long args which take a value must + # appear as "--long=value" + i = 0 + while i < len(self._b2_args_split): + arg = self._b2_args_split[i] + m = re.match(r"(-[dfjlmopst])(.*)", arg) + if m: + name = m.group(1) + # An arg that takes a value but wasn't concatenated. Treat the + # next option as the value + if not m.group(2): + i += 1 + else: + name = arg.split("=", 1)[0] + self._b2_args_configured.add(name) + i += 1 + + # Add deprecated args + if self.libtorrent_link: + warnings.warn( + "--libtorrent-link is deprecated; use --b2-args=libtorrent-link=..." + ) + self._maybe_add_arg(f"libtorrent-link={self.libtorrent_link}") + self._b2_args_configured.add("libtorrent-link") + if self.boost_link: + warnings.warn("--boost-link is deprecated; use --b2-args=boost-link=...") + self._maybe_add_arg(f"boost-link={self.boost_link}") + self._b2_args_configured.add("boost-link") + if self.toolset: + warnings.warn("--toolset is deprecated; use --b2-args=toolset=...") + self._maybe_add_arg(f"toolset={self.toolset}") + self._b2_args_configured.add("toolset") + if self.pic: + warnings.warn("--pic is deprecated; use --b2-args=libtorrent-python-pic=on") + self._maybe_add_arg("libtorrent-python-pic=on") + self._b2_args_configured.add("libtorrent-python-pic") + if self.optimization: + warnings.warn( + "--optimization is deprecated; use --b2-args=optimization=..." + ) + self._maybe_add_arg(f"optimization={self.optimization}") + self._b2_args_configured.add("optimization") + if self.hash: + warnings.warn("--hash is deprecated; use --b2-args=--hash") + self._maybe_add_arg("--hash") + if self.cxxstd: + # the cxxstd feature was introduced in boost 1.66. However the output of + # b2 --version is 2015.07 for both 1.65 and 1.66. + if self._b2_version > (0, 2015, 7): + self._maybe_add_arg(f"cxxstd={self.cxxstd}") + else: + warnings.warn( + f"--cxxstd supplied, but b2 is too old ({self._b2_version}). " + ) + + def _should_add_arg(self, arg: str) -> bool: + m = re.match(r"(-\w).*", arg) + if m: + name = m.group(1) + else: + name = arg.split("=", 1)[0] + return name not in self._b2_args_configured + + def _maybe_add_arg(self, arg: str) -> bool: + if self._should_add_arg(arg): + self._b2_args_split.append(arg) + return True + return False + + def run(self) -> None: + # The current jamfile layout just supports one extension + self._build_extension_with_b2() + super().run() + + def _build_extension_with_b2(self) -> None: + with self._configure_b2(): + command = ["b2"] + self._b2_args_split + log.info(" ".join(command)) + subprocess.run(command, cwd=PYTHON_BINDING_DIR, check=True) + # The jamfile only builds "libtorrent.so", but we want + # "libtorrent/__init__.so" + src = self.get_ext_fullpath("libtorrent") + dst = self.get_ext_fullpath(self.extensions[0].name) + os.makedirs(os.path.dirname(dst), exist_ok=True) + log.info("rename %s -> %s", src, dst) + os.rename(src, dst) + + @contextlib.contextmanager + def _configure_b2(self) -> Iterator[None]: + if self.config_mode == self.CONFIG_MODE_DISTUTILS: + # If we're using distutils mode, we'll auto-configure a lot of args + # and write temporary config. + yield from self._configure_b2_with_distutils() + else: + # If we're using b2 mode, no configuration needed + yield + + def _configure_b2_with_distutils(self) -> Iterator[None]: + if os.name == "nt": + self._maybe_add_arg("--abbreviate-paths") + + # if distutils.debug.DEBUG: + # self._maybe_add_arg("--debug-configuration") + # self._maybe_add_arg("--debug-building") + # self._maybe_add_arg("--debug-generators") + + if sys.platform == "darwin": + # boost.build defaults to toolset=clang on mac. However python.jam + # on boost 1.77+ breaks with toolset=clang if using a framework-type + # python installation, such as installed by homebrew. + self._maybe_add_arg("toolset=darwin") + + # Default feature configuration + self._maybe_add_arg("deprecated-functions=on") + self._maybe_add_arg("boost-link=static") + self._maybe_add_arg("libtorrent-link=static") + + self._maybe_add_arg("crypto=openssl") + + variant = "debug" if self.debug else "release" + self._maybe_add_arg(f"variant={variant}") + bits = 64 if sys.maxsize > 2**32 else 32 + self._maybe_add_arg(f"address-model={bits}") + + # Cross-compiling logic: tricky, because autodetection is usually + # better than our matching + if sys.platform == "darwin": + # macOS uses multi-arch binaries. Attempt to match the + # configuration of the running python by translating distutils + # platform modes to b2 architecture modes + machine = sysconfig.get_platform().split("-")[-1] + if machine == "arm64": + self._maybe_add_arg("architecture=arm") + elif machine in ("ppc", "ppc64"): + self._maybe_add_arg("architecture=power") + elif machine in ("i386", "x86_64", "intel"): + self._maybe_add_arg("architecture=x86") + elif machine in ("universal", "fat", "fat3", "fat64"): + self._maybe_add_arg("architecture=combined") + # NB: as of boost 1.75.0, b2 doesn't have a straightforward way to + # build a "universal2" (arm64 + x86_64) binary + + if self.parallel: + self._maybe_add_arg(f"-j{self.parallel}") + + # We use a "project-config.jam" to instantiate a python environment + # to exactly match the running one. + # Don't create project-config.jam if the user specified + # --b2-args=--project-config=..., or has an existing project-config.jam. + config_writers: List[Callable[[IO[str]], None]] = [] + if self._should_add_arg("--project-config") or self._find_project_config(): + if self._maybe_add_arg(f"python={sysconfig.get_python_version()}"): + config_writers.append( + functools.partial( + write_b2_python_config, + self.include_dirs, + self.library_dirs, + os.path.basename(self.get_ext_fullpath("")), + ) + ) + + # Jamfile hacks to ensure we select the python environment defined in + # our project-config.jam + self._maybe_add_arg("libtorrent-python=on") + + # python.jam only allows ONE library dir! distutils may autodetect + # multiple, and finding the "right" one isn't straightforward. We just + # pass them all here and hopefully the right thing happens. + for path in self.library_dirs: + self._b2_args_split.append(f"library-path={b2_escape(path)}") + + # Our goal is to produce an artifact at this path. If we do this, the + # distutils build system will skip trying to build it. + target = pathlib.Path(self.get_ext_fullpath("libtorrent")).absolute() + self.announce(f"target: {target}") + + # b2 doesn't provide a way to signal the name or paths of its outputs. + # We try to convince python.jam to name its output file like our target + # and copy it to our target directory. See comments in + # write_b2_python_config for limitations on controlling the filename. + + # Jamfile hack to copy the module to our target directory + self._maybe_add_arg(f"python-install-path={target.parent}") + self._maybe_add_arg("install_module") + + # Two paths depending on whether or not we use a generated + # project-config.jam or not. + if config_writers: + # We prefer to use a temporary file, and pass it with --project-config=... + # This option was introduced in boost 1.68. Otherwise, we just write to + # project-config.jam in the bindings directory. + if self._b2_version >= (0, 2018, 2): + temp_config = tempfile.NamedTemporaryFile(mode="w+", delete=True) + temp_config.close() + config_path = pathlib.Path(temp_config.name) + self._b2_args_split.append(f"--project-config={temp_config.name}") + else: + config_path = PYTHON_BINDING_DIR / "project-config.jam" + try: + with config_path.open(mode="w+") as config: + for writer in config_writers: + writer(config) + config.seek(0) + log.info("project-config.jam contents:") + log.info(config.read()) + yield + finally: + with contextlib.suppress(FileNotFoundError): + config_path.unlink() + else: + yield + + def _find_project_config(self) -> Optional[pathlib.Path]: + for directory in itertools.chain( + (PYTHON_BINDING_DIR,), PYTHON_BINDING_DIR.parents + ): + path = directory / "project-config.jam" + if path.exists(): + return path + return None + + +def find_all_files(path: str) -> Iterator[str]: + for dirpath, _, filenames in os.walk(path): + for filename in filenames: + yield os.path.join(dirpath, filename) + + +setuptools.setup( + name="libtorrent", + author="Arvid Norberg", + author_email="arvid@libtorrent.org", + description="Python bindings for libtorrent-rasterbar", + long_description="Python bindings for libtorrent-rasterbar", + url="http://libtorrent.org", + license="BSD", + ext_modules=[StubExtension("libtorrent.__init__")], + cmdclass={ + "build_ext": LibtorrentBuildExt, + }, + distclass=B2Distribution, + data_files=[ + ("libtorrent", list(find_all_files("install_data"))), + ], +) diff --git a/bindings/python/setup.py.cmake.in b/bindings/python/setup.py.cmake.in new file mode 100644 index 0000000..7fdcfcf --- /dev/null +++ b/bindings/python/setup.py.cmake.in @@ -0,0 +1,15 @@ +from setuptools import setup +import platform + +setup( + name='libtorrent', + version='@libtorrent_VERSION@', + author='Arvid Norberg', + author_email='arvid@libtorrent.org', + description='Python bindings for libtorrent-rasterbar', + long_description='Python bindings for libtorrent-rasterbar', + url='http://libtorrent.org', + platforms=[platform.system() + '-' + platform.machine()], + license='BSD', + package_dir = {'': '@CMAKE_CURRENT_BINARY_DIR@'} +) diff --git a/bindings/python/simple_client.py b/bindings/python/simple_client.py new file mode 100755 index 0000000..df32c9a --- /dev/null +++ b/bindings/python/simple_client.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# Copyright Arvid Norberg 2008. Use, modification and distribution is +# subject to the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +from __future__ import print_function + +import libtorrent as lt +import time +import sys + +ses = lt.session({'listen_interfaces': '0.0.0.0:6881'}) + +info = lt.torrent_info(sys.argv[1]) +h = ses.add_torrent({'ti': info, 'save_path': '.'}) +s = h.status() +print('starting', s.name) + +while (not s.is_seeding): + s = h.status() + + print('\r%.2f%% complete (down: %.1f kB/s up: %.1f kB/s peers: %d) %s' % ( + s.progress * 100, s.download_rate / 1000, s.upload_rate / 1000, + s.num_peers, s.state), end=' ') + + alerts = ses.pop_alerts() + for a in alerts: + if a.category() & lt.alert.category_t.error_notification: + print(a) + + sys.stdout.flush() + + time.sleep(1) + +print(h.status().name, 'complete') diff --git a/bindings/python/src/alert.cpp b/bindings/python/src/alert.cpp new file mode 100644 index 0000000..e1b6fa0 --- /dev/null +++ b/bindings/python/src/alert.cpp @@ -0,0 +1,1184 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include +#include // for piece_block +#include +#include +#include +#include "bytes.hpp" +#include "gil.hpp" + +#include + +using namespace boost::python; +using namespace lt; + +#ifdef _MSC_VER +#pragma warning(push) +// warning c4996: x: was declared deprecated +#pragma warning( disable : 4996 ) +#endif + +bytes get_buffer(read_piece_alert const& rpa) +{ + return rpa.buffer ? bytes(rpa.buffer.get(), rpa.size) + : bytes(); +} + +#if TORRENT_ABI_VERSION <= 2 +list stats_alert_transferred(stats_alert const& alert) +{ + list result; + for (int i = 0; i < alert.num_channels; ++i) { + result.append(alert.transferred[i]); + } + return result; +} +#endif + +list get_status_from_update_alert(state_update_alert const& alert) +{ + list result; + + for (std::vector::const_iterator i = alert.status.begin(); i != alert.status.end(); ++i) + { + result.append(*i); + } + return result; +} + +list dht_stats_active_requests(dht_stats_alert const& a) +{ + list result; + + for (std::vector::const_iterator i = a.active_requests.begin(); + i != a.active_requests.end(); ++i) + { + dict d; + + d["type"] = i->type; + d["outstanding_requests"] = i->outstanding_requests; + d["timeouts"] = i->timeouts; + d["responses"] = i->responses; + d["branch_factor"] = i->branch_factor; + d["nodes_left"] = i->nodes_left; + d["last_sent"] = i->last_sent; + d["first_timeout"] = i->first_timeout; + + result.append(d); + } + return result; +} + +list dht_stats_routing_table(dht_stats_alert const& a) +{ + list result; + + for (std::vector::const_iterator i = a.routing_table.begin(); + i != a.routing_table.end(); ++i) + { + dict d; + + d["num_nodes"] = i->num_nodes; + d["num_replacements"] = i->num_replacements; + + result.append(d); + } + return result; +} + +dict dht_immutable_item(dht_immutable_item_alert const& alert) +{ + dict d; + d["key"] = alert.target; + d["value"] = bytes(alert.item.string()); + return d; +} + +dict dht_mutable_item(dht_mutable_item_alert const& alert) +{ + dict d; + d["key"] = bytes(alert.key.data(), alert.key.size()); + d["value"] = bytes(alert.item.string()); + d["signature"] = bytes(alert.signature.data(), alert.signature.size()); + d["seq"] = alert.seq; + d["salt"] = bytes(alert.salt); + d["authoritative"] = alert.authoritative; + return d; +} + +dict dht_put_item(dht_put_alert const& alert) +{ + dict d; + if (alert.target.is_all_zeros()) { + d["public_key"] = bytes(alert.public_key.data(), alert.public_key.size()); + d["signature"] = bytes(alert.signature.data(), alert.signature.size()); + d["seq"] = alert.seq; + d["salt"] = bytes(alert.salt); + } else { + d["target"] = alert.target; + } + return d; +} + +dict session_stats_values(session_stats_alert const& alert) +{ + std::vector map = session_stats_metrics(); + dict d; + auto counters = alert.counters(); + + for (stats_metric const& m : map) + { + d[m.name] = counters[m.value_index]; + } + return d; +} + +list dht_live_nodes_nodes(dht_live_nodes_alert const& alert) +{ + list result; + std::vector> const nodes = alert.nodes(); + for (std::pair const& node : nodes) + { + dict d; + d["nid"] = node.first; + d["endpoint"] = node.second; + result.append(d); + } + return result; +} + +list dht_sample_infohashes_nodes(dht_sample_infohashes_alert const& alert) +{ + list result; + std::vector> const nodes = alert.nodes(); + for (std::pair const& node : nodes) + { + dict d; + d["nid"] = node.first; + d["endpoint"] = node.second; + result.append(d); + } + return result; +} + +#if TORRENT_ABI_VERSION == 1 +entry const& get_resume_data_entry(save_resume_data_alert const& self) +{ + python_deprecated("resume_data is deprecated"); + return *self.resume_data; +} +#endif + +namespace boost +{ + // some older compilers (like msvc-12.0) end up using + // boost::is_polymorphic inside boost.python applied + // to alert types. This is problematic, since it appears + // to be implemented by deriving from the type, which + // yields a compiler error since most alerts are final. + // this just short-cuts the query to say that all these + // types are indeed polymorphic, no need to derive from + // them. +#define POLY(x) template<> \ + struct is_polymorphic : boost::mpl::true_ {}; + + POLY(torrent_alert) + POLY(tracker_alert) + POLY(torrent_removed_alert) + POLY(read_piece_alert) + POLY(peer_alert) + POLY(tracker_error_alert) + POLY(tracker_warning_alert) + POLY(tracker_reply_alert) + POLY(tracker_announce_alert) + POLY(hash_failed_alert) + POLY(peer_ban_alert) + POLY(peer_error_alert) + POLY(invalid_request_alert) + POLY(torrent_error_alert) + POLY(torrent_finished_alert) + POLY(piece_finished_alert) + POLY(block_finished_alert) + POLY(block_downloading_alert) + POLY(storage_moved_alert) + POLY(storage_moved_failed_alert) + POLY(torrent_deleted_alert) + POLY(torrent_paused_alert) + POLY(torrent_checked_alert) + POLY(url_seed_alert) + POLY(file_error_alert) + POLY(metadata_failed_alert) + POLY(metadata_received_alert) + POLY(listen_failed_alert) + POLY(listen_succeeded_alert) + POLY(portmap_error_alert) + POLY(portmap_alert) + POLY(fastresume_rejected_alert) + POLY(peer_blocked_alert) + POLY(scrape_reply_alert) + POLY(scrape_failed_alert) + POLY(udp_error_alert) + POLY(external_ip_alert) + POLY(save_resume_data_alert) + POLY(file_completed_alert) + POLY(file_renamed_alert) + POLY(file_rename_failed_alert) + POLY(torrent_resumed_alert) + POLY(state_changed_alert) + POLY(state_update_alert) + POLY(i2p_alert) + POLY(dht_immutable_item_alert) + POLY(dht_mutable_item_alert) + POLY(dht_put_alert) + POLY(dht_reply_alert) + POLY(dht_announce_alert) + POLY(dht_get_peers_alert) + POLY(peer_unsnubbed_alert) + POLY(peer_snubbed_alert) + POLY(peer_connect_alert) + POLY(peer_disconnected_alert) + POLY(request_dropped_alert) + POLY(block_timeout_alert) + POLY(unwanted_block_alert) + POLY(torrent_delete_failed_alert) + POLY(save_resume_data_failed_alert) + POLY(performance_alert) +#if TORRENT_ABI_VERSION <= 2 + POLY(stats_alert) +#endif + POLY(cache_flushed_alert) + POLY(incoming_connection_alert) + POLY(torrent_need_cert_alert) + POLY(add_torrent_alert) + POLY(dht_outgoing_get_peers_alert) + POLY(lsd_error_alert) + POLY(dht_stats_alert) + POLY(incoming_request_alert) + POLY(dht_log_alert) + POLY(dht_pkt_alert) + POLY(dht_get_peers_reply_alert) + POLY(dht_direct_response_alert) + POLY(session_error_alert) + POLY(dht_live_nodes_alert) + POLY(session_stats_header_alert) + POLY(dht_sample_infohashes_alert) + POLY(block_uploaded_alert) + POLY(alerts_dropped_alert) + POLY(session_stats_alert) + POLY(socks5_alert) + POLY(file_prio_alert) + POLY(oversized_file_alert) + POLY(torrent_conflict_alert) + +#if TORRENT_ABI_VERSION == 1 + POLY(anonymous_mode_alert) + POLY(torrent_added_alert) +#endif + +#ifndef TORRENT_DISABLE_LOGGING + POLY(portmap_log_alert) + POLY(log_alert) + POLY(torrent_log_alert) + POLY(peer_log_alert) + POLY(picker_log_alert) +#endif // TORRENT_DISABLE_LOGGING + +#undef POLY +} + +struct dummy3 {}; +struct dummy12 {}; + +bytes get_pkt_buf(dht_pkt_alert const& alert) +{ + return {alert.pkt_buf().data(), static_cast(alert.pkt_buf().size())}; +} + +list get_dropped_alerts(alerts_dropped_alert const& alert) +{ + list ret; + for (int i = 0; i < int(alert.dropped_alerts.size()); ++i) + ret.append(bool(alert.dropped_alerts[i])); + return ret; +} + +void bind_alert() +{ + using boost::noncopyable; + + using by_value = return_value_policy; + + { + scope alert_scope = class_("alert", no_init) + .def("message", &alert::message) + .def("what", &alert::what) + .def("category", &alert::category) + .def("__str__", &alert::message) + ; + + scope s = class_("category_t"); + s.attr("error_notification") = alert::error_notification; + s.attr("peer_notification") = alert::peer_notification; + s.attr("port_mapping_notification") = alert::port_mapping_notification; + s.attr("storage_notification") = alert::storage_notification; + s.attr("tracker_notification") = alert::tracker_notification; + s.attr("connect_notification") = alert::connect_notification; + s.attr("status_notification") = alert::status_notification; +#if TORRENT_ABI_VERSION == 1 + s.attr("debug_notification") = alert::debug_notification; + s.attr("progress_notification") = alert::progress_notification; +#endif + s.attr("ip_block_notification") = alert::ip_block_notification; + s.attr("performance_warning") = alert::performance_warning; + s.attr("dht_notification") = alert::dht_notification; +#if TORRENT_ABI_VERSION <= 2 + s.attr("stats_notification") = alert::stats_notification; +#endif + s.attr("session_log_notification") = alert::session_log_notification; + s.attr("torrent_log_notification") = alert::torrent_log_notification; + s.attr("peer_log_notification") = alert::peer_log_notification; + s.attr("incoming_request_notification") = alert::incoming_request_notification; + s.attr("dht_log_notification") = alert::dht_log_notification; + s.attr("dht_operation_notification") = alert::dht_operation_notification; + s.attr("port_mapping_log_notification") = alert::port_mapping_log_notification; + s.attr("picker_log_notification") = alert::picker_log_notification; + s.attr("file_progress_notification") = alert::file_progress_notification; + s.attr("piece_progress_notification") = alert::piece_progress_notification; + s.attr("upload_notification") = alert::upload_notification; + s.attr("block_progress_notification") = alert::block_progress_notification; + s.attr("all_categories") = alert::all_categories; + } + + { + scope s = class_("alert_category"); + s.attr("error") = alert_category::error; + s.attr("peer") = alert_category::peer; + s.attr("port_mapping") = alert_category::port_mapping; + s.attr("storage") = alert_category::storage; + s.attr("tracker") = alert_category::tracker; + s.attr("connect") = alert_category::connect; + s.attr("status") = alert_category::status; + s.attr("ip_block") = alert_category::ip_block; + s.attr("performance_warning") = alert_category::performance_warning; + s.attr("dht") = alert_category::dht; + s.attr("stats") = alert_category::stats; + s.attr("session_log") = alert_category::session_log; + s.attr("torrent_log") = alert_category::torrent_log; + s.attr("peer_log") = alert_category::peer_log; + s.attr("incoming_request") = alert_category::incoming_request; + s.attr("dht_log") = alert_category::dht_log; + s.attr("dht_operation") = alert_category::dht_operation; + s.attr("port_mapping_log") = alert_category::port_mapping_log; + s.attr("picker_log") = alert_category::picker_log; + s.attr("file_progress") = alert_category::file_progress; + s.attr("piece_progress") = alert_category::piece_progress; + s.attr("upload") = alert_category::upload; + s.attr("block_progress") = alert_category::block_progress; + s.attr("all") = alert_category::all; + } + + enum_("operation_t") + .value("unknown", operation_t::unknown) + .value("bittorrent", operation_t::bittorrent) + .value("iocontrol", operation_t::iocontrol) + .value("getpeername", operation_t::getpeername) + .value("getname", operation_t::getname) + .value("alloc_recvbuf", operation_t::alloc_recvbuf) + .value("alloc_sndbuf", operation_t::alloc_sndbuf) + .value("file_write", operation_t::file_write) + .value("file_read", operation_t::file_read) + .value("file", operation_t::file) + .value("sock_write", operation_t::sock_write) + .value("sock_read", operation_t::sock_read) + .value("sock_open", operation_t::sock_open) + .value("sock_bind", operation_t::sock_bind) + .value("available", operation_t::available) + .value("encryption", operation_t::encryption) + .value("connect", operation_t::connect) + .value("ssl_handshake", operation_t::ssl_handshake) + .value("get_interface", operation_t::get_interface) + .value("sock_listen", operation_t::sock_listen) + .value("sock_bind_to_device", operation_t::sock_bind_to_device) + .value("sock_accept", operation_t::sock_accept) + .value("parse_address", operation_t::parse_address) + .value("enum_if", operation_t::enum_if) + .value("file_stat", operation_t::file_stat) + .value("file_copy", operation_t::file_copy) + .value("file_fallocate", operation_t::file_fallocate) + .value("file_hard_link", operation_t::file_hard_link) + .value("file_remove", operation_t::file_remove) + .value("file_rename", operation_t::file_rename) + .value("file_open", operation_t::file_open) + .value("mkdir", operation_t::mkdir) + .value("check_resume", operation_t::check_resume) + .value("exception", operation_t::exception) + .value("alloc_cache_piece", operation_t::alloc_cache_piece) + .value("partfile_move", operation_t::partfile_move) + .value("partfile_read", operation_t::partfile_read) + .value("partfile_write", operation_t::partfile_write) + .value("hostname_lookup", operation_t::hostname_lookup) + .value("symlink", operation_t::symlink) + .value("handshake", operation_t::handshake) + .value("sock_option", operation_t::sock_option) + ; + + def("operation_name", static_cast(<::operation_name)); + + class_, noncopyable>( + "torrent_alert", no_init) + .add_property("handle", make_getter(&torrent_alert::handle, by_value())) + .add_property("torrent_name", &torrent_alert::torrent_name) + ; + + class_, noncopyable>( + "tracker_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("url", &tracker_alert::url) +#endif + .add_property("local_endpoint", make_getter(&tracker_alert::local_endpoint, by_value())) + .def("tracker_url", &tracker_alert::tracker_url) + ; + +#if TORRENT_ABI_VERSION == 1 + class_, noncopyable>( + "torrent_added_alert", no_init) + ; +#endif + + class_, noncopyable>( + "torrent_removed_alert", no_init) +#if TORRENT_ABI_VERSION < 3 + .def_readonly("info_hash", &torrent_removed_alert::info_hash) +#endif + .def_readonly("info_hashes", &torrent_removed_alert::info_hashes) + ; + + class_, noncopyable>( + "read_piece_alert", nullptr, no_init) + .def_readonly("error", &read_piece_alert::error) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("ec", &read_piece_alert::ec) +#endif + .add_property("buffer", get_buffer) + .add_property("piece", make_getter(&read_piece_alert::piece, by_value())) + .def_readonly("size", &read_piece_alert::size) + ; + + class_, noncopyable>( + "peer_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .add_property("ip", make_getter(&peer_alert::ip, by_value())) +#endif + .add_property("endpoint", make_getter(&peer_alert::endpoint, by_value())) + .def_readonly("pid", &peer_alert::pid) + ; + class_, noncopyable>( + "tracker_error_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("msg", &tracker_error_alert::msg) + .def_readonly("status_code", &tracker_error_alert::status_code) +#endif + .def("error_message", &tracker_error_alert::error_message) + .def("failure_reason", &tracker_error_alert::failure_reason) + .def_readonly("times_in_row", &tracker_error_alert::times_in_row) + .def_readonly("error", &tracker_error_alert::error) + // TODO: move this to tracker_alert + .def_readonly("version", &tracker_error_alert::version) + ; + + class_, noncopyable>( + "tracker_warning_alert", no_init) + // TODO: move this to tracker_alert + .def_readonly("version", &tracker_warning_alert::version) + ; + + class_, noncopyable>( + "tracker_reply_alert", no_init) + .def_readonly("num_peers", &tracker_reply_alert::num_peers) + // TODO: move this to tracker_alert + .def_readonly("version", &tracker_reply_alert::version) + ; + + class_, noncopyable>( + "tracker_announce_alert", no_init) + .def_readonly("event", &tracker_announce_alert::event) + // TODO: move this to tracker_alert + .def_readonly("version", &tracker_announce_alert::version) + ; + + class_, noncopyable>( + "hash_failed_alert", no_init) + .add_property("piece_index", make_getter(&hash_failed_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "peer_ban_alert", no_init); + + class_, noncopyable>( + "peer_error_alert", no_init) + .def_readonly("error", &peer_error_alert::error) + .def_readonly("op", &peer_error_alert::op) + ; + + class_, noncopyable>( + "invalid_request_alert", no_init) + .def_readonly("request", &invalid_request_alert::request) + ; + + class_("peer_request") + .add_property("piece", make_getter(&peer_request::piece, by_value())) + .def_readonly("start", &peer_request::start) + .def_readonly("length", &peer_request::length) + .def(self == self) + ; + + class_, noncopyable>( + "torrent_error_alert", no_init) + .def_readonly("error", &torrent_error_alert::error) + ; + + class_, noncopyable>( + "torrent_finished_alert", no_init); + + class_, noncopyable>( + "piece_finished_alert", no_init) + .add_property("piece_index", make_getter(&piece_finished_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "block_finished_alert", no_init) + .add_property("block_index", make_getter(&block_finished_alert::block_index, by_value())) + .add_property("piece_index", make_getter(&block_finished_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "block_downloading_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("peer_speedmsg", &block_downloading_alert::peer_speedmsg) +#endif + .add_property("block_index", make_getter(&block_downloading_alert::block_index, by_value())) + .add_property("piece_index", make_getter(&block_downloading_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "storage_moved_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("path", &storage_moved_alert::path) +#endif + .def("storage_path", &storage_moved_alert::storage_path) + .def("old_path", &storage_moved_alert::old_path) + ; + + class_, noncopyable>( + "storage_moved_failed_alert", no_init) + .def_readonly("error", &storage_moved_failed_alert::error) + .def("file_path", &storage_moved_failed_alert::file_path) + .def_readonly("op", &storage_moved_failed_alert::op) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("operation", &storage_moved_failed_alert::operation) +#endif + ; + + class_, noncopyable>( + "torrent_deleted_alert", no_init) +#if TORRENT_ABI_VERSION < 3 + .def_readonly("info_hash", &torrent_deleted_alert::info_hash) +#endif + .def_readonly("info_hashes", &torrent_deleted_alert::info_hashes) + ; + + class_, noncopyable>( + "torrent_paused_alert", no_init); + + class_, noncopyable>( + "torrent_checked_alert", no_init); + + class_, noncopyable>( + "url_seed_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("url", &url_seed_alert::url) + .def_readonly("msg", &url_seed_alert::msg) +#endif + .def_readonly("error", &url_seed_alert::error) + .def("server_url", &url_seed_alert::server_url) + .def("error_message", &url_seed_alert::error_message) + ; + + class_, noncopyable>( + "file_error_alert", no_init) + .def_readonly("error", &file_error_alert::error) + .def("filename", &file_error_alert::filename) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("file", &file_error_alert::file) + .def_readonly("msg", &file_error_alert::msg) +#endif + ; + + class_, noncopyable>( + "metadata_failed_alert", no_init) + .def_readonly("error", &metadata_failed_alert::error) + ; + + class_, noncopyable>( + "metadata_received_alert", no_init); + + class_, noncopyable>( + "listen_failed_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .add_property("endpoint", make_getter(&listen_failed_alert::endpoint, by_value())) +#endif + .add_property("address", make_getter(&listen_failed_alert::address, by_value())) + .def_readonly("port", &listen_failed_alert::port) + .def("listen_interface", &listen_failed_alert::listen_interface) + .def_readonly("error", &listen_failed_alert::error) + .def_readonly("op", &listen_failed_alert::op) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("operation", &listen_failed_alert::operation) + .def_readonly("sock_type", &listen_failed_alert::sock_type) +#endif + .def_readonly("socket_type", &listen_failed_alert::socket_type) + ; + + class_, noncopyable>( + "listen_succeeded_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .add_property("endpoint", make_getter(&listen_succeeded_alert::endpoint, by_value())) +#endif + .add_property("address", make_getter(&listen_succeeded_alert::address, by_value())) + .def_readonly("port", &listen_succeeded_alert::port) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("sock_type", &listen_succeeded_alert::sock_type) +#endif + .def_readonly("socket_type", &listen_succeeded_alert::socket_type) + ; + +#if TORRENT_ABI_VERSION == 1 + enum_("listen_succeeded_alert_socket_type_t") + .value("tcp", listen_succeeded_alert::socket_type_t::tcp) + .value("tcp_ssl", listen_succeeded_alert::socket_type_t::tcp_ssl) + .value("udp", listen_succeeded_alert::socket_type_t::udp) + .value("i2p", listen_succeeded_alert::socket_type_t::i2p) + .value("socks5", listen_succeeded_alert::socket_type_t::socks5) + .value("utp_ssl", listen_succeeded_alert::socket_type_t::utp_ssl) + ; + + enum_("listen_failed_alert_socket_type_t") + .value("tcp", listen_failed_alert::socket_type_t::tcp) + .value("tcp_ssl", listen_failed_alert::socket_type_t::tcp_ssl) + .value("udp", listen_failed_alert::socket_type_t::udp) + .value("i2p", listen_failed_alert::socket_type_t::i2p) + .value("socks5", listen_failed_alert::socket_type_t::socks5) + .value("utp_ssl", listen_failed_alert::socket_type_t::utp_ssl) + ; +#endif + + enum_("socket_type_t") + .value("tcp", socket_type_t::tcp) + .value("socks5", socket_type_t::socks5) + .value("http", socket_type_t::http) + .value("utp", socket_type_t::utp) +#if TORRENT_ABI_VERSION <= 2 + .value("udp", socket_type_t::udp) +#endif + .value("i2p", socket_type_t::i2p) + .value("tcp_ssl", socket_type_t::tcp_ssl) + .value("socks5_ssl", socket_type_t::socks5_ssl) + .value("http_ssl", socket_type_t::http_ssl) + .value("utp_ssl", socket_type_t::utp_ssl) + ; + + class_, noncopyable>( + "portmap_error_alert", no_init) + .add_property("mapping", make_getter(&portmap_error_alert::mapping, by_value())) + .def_readonly("error", &portmap_error_alert::error) + .def_readonly("map_transport", &portmap_error_alert::map_transport) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("map_type", &portmap_error_alert::map_type) + .def_readonly("type", &portmap_error_alert::map_type) + .def_readonly("msg", &portmap_error_alert::msg) +#endif + ; + + class_, noncopyable>("portmap_alert", no_init) + .add_property("mapping", make_getter(&portmap_alert::mapping, by_value())) + .def_readonly("external_port", &portmap_alert::external_port) + .def_readonly("map_protocol", &portmap_alert::map_protocol) + .def_readonly("map_transport", &portmap_alert::map_transport) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("type", &portmap_alert::map_type) + .def_readonly("map_type", &portmap_alert::map_type) +#endif + ; + +#ifndef TORRENT_DISABLE_LOGGING + + class_, noncopyable>("portmap_log_alert", no_init) + .def_readonly("map_transport", &portmap_log_alert::map_transport) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("type", &portmap_log_alert::map_type) + .def_readonly("msg", &portmap_log_alert::msg) + .def_readonly("map_type", &portmap_log_alert::map_type) +#endif + ; + +#endif // TORRENT_DISABLE_LOGGING + + class_, noncopyable>( + "fastresume_rejected_alert", no_init) + .def_readonly("error", &fastresume_rejected_alert::error) + .def("file_path", &fastresume_rejected_alert::file_path) + .def_readonly("op", &fastresume_rejected_alert::op) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("operation", &fastresume_rejected_alert::operation) + .def_readonly("msg", &fastresume_rejected_alert::msg) +#endif + ; + + class_, noncopyable>( + "peer_blocked_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .add_property("ip", make_getter(&peer_blocked_alert::ip, by_value())) +#endif + .add_property("reason", &peer_blocked_alert::reason) + ; + + enum_("reason_t") + .value("ip_filter", peer_blocked_alert::reason_t::ip_filter) + .value("port_filter", peer_blocked_alert::reason_t::port_filter) + .value("i2p_mixed", peer_blocked_alert::reason_t::i2p_mixed) + .value("privileged_ports", peer_blocked_alert::reason_t::privileged_ports) + .value("utp_disabled", peer_blocked_alert::reason_t::utp_disabled) + .value("tcp_disabled", peer_blocked_alert::reason_t::tcp_disabled) + .value("invalid_local_interface", peer_blocked_alert::reason_t::invalid_local_interface) + ; + + class_, noncopyable>( + "scrape_reply_alert", no_init) + .def_readonly("incomplete", &scrape_reply_alert::incomplete) + .def_readonly("complete", &scrape_reply_alert::complete) + ; + + class_, noncopyable>( + "scrape_failed_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("msg", &scrape_failed_alert::msg) +#endif + .def("error_message", &scrape_failed_alert::error_message) + .def_readonly("error", &scrape_failed_alert::error) + ; + + class_, noncopyable>( + "udp_error_alert", no_init) + .add_property("endpoint", make_getter(&udp_error_alert::endpoint, by_value())) + .def_readonly("error", &udp_error_alert::error) + ; + + class_, noncopyable>( + "external_ip_alert", no_init) + .add_property("external_address", make_getter(&external_ip_alert::external_address, by_value())) + ; + + class_, noncopyable>( + "save_resume_data_alert", no_init) + .def_readonly("params", &save_resume_data_alert::params) +#if TORRENT_ABI_VERSION == 1 + .add_property("resume_data", make_function(get_resume_data_entry, by_value())) +#endif + ; + + class_, noncopyable>( + "file_completed_alert", no_init) + .add_property("index", make_getter(&file_completed_alert::index, by_value())) + ; + + class_, noncopyable>( + "file_renamed_alert", no_init) + .add_property("index", make_getter(&file_renamed_alert::index, by_value())) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("name", &file_renamed_alert::name) +#endif + .def("new_name", &file_renamed_alert::new_name) + .def("old_name", &file_renamed_alert::old_name) + ; + + class_, noncopyable>( + "file_rename_failed_alert", no_init) + .add_property("index", make_getter(&file_rename_failed_alert::index, by_value())) + .def_readonly("error", &file_rename_failed_alert::error) + ; + + class_, noncopyable>( + "torrent_resumed_alert", no_init + ); + + class_, noncopyable>( + "state_changed_alert", no_init) + .def_readonly("state", &state_changed_alert::state) + .def_readonly("prev_state", &state_changed_alert::prev_state) + ; + + class_, noncopyable>( + "state_update_alert", no_init) + .add_property("status", &get_status_from_update_alert) + ; + + class_, noncopyable>( + "i2p_alert", no_init) + .add_property("error", &i2p_alert::error) + ; + + class_, noncopyable>( + "dht_reply_alert", no_init) + .def_readonly("num_peers", &dht_reply_alert::num_peers) + ; + + class_, noncopyable>( + "dht_announce_alert", no_init) + .add_property("ip", make_getter(&dht_announce_alert::ip, by_value())) + .def_readonly("port", &dht_announce_alert::port) + .def_readonly("info_hash", &dht_announce_alert::info_hash) + ; + + class_, noncopyable>( + "dht_get_peers_alert", no_init + ) + .def_readonly("info_hash", &dht_get_peers_alert::info_hash) + ; + + class_, noncopyable>( + "peer_unsnubbed_alert", no_init + ); + + class_, noncopyable>( + "peer_snubbed_alert", no_init + ); + + class_, noncopyable>( + "peer_connect_alert", no_init + ); + + class_, noncopyable>( + "peer_disconnected_alert", no_init) + .def_readonly("socket_type", &peer_disconnected_alert::socket_type) + .def_readonly("op", &peer_disconnected_alert::op) + .def_readonly("error", &peer_disconnected_alert::error) + .def_readonly("reason", &peer_disconnected_alert::reason) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("msg", &peer_disconnected_alert::msg) +#endif + ; + + class_, noncopyable>( + "request_dropped_alert", no_init) + .add_property("block_index", make_getter(&request_dropped_alert::block_index, by_value())) + .add_property("piece_index", make_getter(&request_dropped_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "block_timeout_alert", no_init) + .add_property("block_index", make_getter(&block_timeout_alert::block_index, by_value())) + .add_property("piece_index", make_getter(&block_timeout_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "unwanted_block_alert", no_init) + .add_property("block_index", make_getter(&unwanted_block_alert::block_index, by_value())) + .add_property("piece_index", make_getter(&unwanted_block_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "torrent_delete_failed_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("msg", &torrent_delete_failed_alert::msg) +#endif + .def_readonly("error", &torrent_delete_failed_alert::error) +#if TORRENT_ABI_VERSION < 3 + .def_readonly("info_hash", &torrent_delete_failed_alert::info_hash) +#endif + .def_readonly("info_hashes", &torrent_delete_failed_alert::info_hashes) + ; + + class_, noncopyable>( + "save_resume_data_failed_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("msg", &save_resume_data_failed_alert::msg) +#endif + .def_readonly("error", &save_resume_data_failed_alert::error) + ; + + class_, noncopyable>( + "performance_alert", no_init) + .def_readonly("warning_code", &performance_alert::warning_code) + ; + enum_("performance_warning_t") + .value("outstanding_disk_buffer_limit_reached", performance_alert::outstanding_disk_buffer_limit_reached) + .value("outstanding_request_limit_reached", performance_alert::outstanding_request_limit_reached) + .value("upload_limit_too_low", performance_alert::upload_limit_too_low) + .value("download_limit_too_low", performance_alert::download_limit_too_low) + .value("send_buffer_watermark_too_low", performance_alert::send_buffer_watermark_too_low) + .value("too_many_optimistic_unchoke_slots", performance_alert::too_many_optimistic_unchoke_slots) +#if TORRENT_ABI_VERSION == 1 + .value("bittyrant_with_no_uplimit", performance_alert::bittyrant_with_no_uplimit) +#endif + .value("too_high_disk_queue_limit", performance_alert::too_high_disk_queue_limit) + .value("too_few_outgoing_ports", performance_alert::too_few_outgoing_ports) + .value("too_few_file_descriptors", performance_alert::too_few_file_descriptors) + ; + +#if TORRENT_ABI_VERSION <= 2 + class_, noncopyable>( + "stats_alert", no_init) + .add_property("transferred", &stats_alert_transferred) + .def_readonly("interval", &stats_alert::interval) + ; + + enum_("stats_channel") + .value("upload_payload", stats_alert::upload_payload) + .value("upload_protocol", stats_alert::upload_protocol) + .value("upload_ip_protocol", stats_alert::upload_ip_protocol) +#if TORRENT_ABI_VERSION == 1 + .value("upload_dht_protocol", stats_alert::upload_dht_protocol) + .value("upload_tracker_protocol", stats_alert::upload_tracker_protocol) +#endif + .value("download_payload", stats_alert::download_payload) + .value("download_protocol", stats_alert::download_protocol) + .value("download_ip_protocol", stats_alert::download_ip_protocol) +#if TORRENT_ABI_VERSION == 1 + .value("download_dht_protocol", stats_alert::download_dht_protocol) + .value("download_tracker_protocol", stats_alert::download_tracker_protocol) +#endif + ; +#endif // TORRENT_ABI_VERSION + + class_, noncopyable>( + "cache_flushed_alert", no_init) + ; + +#if TORRENT_ABI_VERSION == 1 + class_, noncopyable>( + "anonymous_mode_alert", no_init) + .def_readonly("kind", &anonymous_mode_alert::kind) + .def_readonly("str", &anonymous_mode_alert::str) + ; + + enum_("kind") + .value("tracker_no_anonymous", anonymous_mode_alert::tracker_not_anonymous) + ; +#endif // TORRENT_ABI_VERSION + + class_, noncopyable>( + "incoming_connection_alert", no_init) + .def_readonly("socket_type", &incoming_connection_alert::socket_type) +#if TORRENT_ABI_VERSION == 1 + .add_property("ip", make_getter(&incoming_connection_alert::ip, by_value())) +#endif + .add_property("endpoint", make_getter(&incoming_connection_alert::endpoint, by_value())) + ; + class_, noncopyable>( + "torrent_need_cert_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("error", &torrent_need_cert_alert::error) +#endif + ; + + class_, noncopyable>( + "add_torrent_alert", no_init) + .def_readonly("error", &add_torrent_alert::error) + .add_property("params", &add_torrent_alert::params) + ; + + class_, noncopyable>( + "dht_outgoing_get_peers_alert", no_init) + .def_readonly("info_hash", &dht_outgoing_get_peers_alert::info_hash) + .def_readonly("obfuscated_info_hash", &dht_outgoing_get_peers_alert::obfuscated_info_hash) +#if TORRENT_ABI_VERSION == 1 + .add_property("ip", make_getter(&dht_outgoing_get_peers_alert::ip, by_value())) +#endif + .add_property("endpoint", make_getter(&dht_outgoing_get_peers_alert::endpoint, by_value())) + ; + + class_, noncopyable>( + "log_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def("msg", depr(&log_alert::msg)) +#endif + .def("log_message", &log_alert::log_message) + ; + + class_, noncopyable>( + "torrent_log_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def("msg", depr(&torrent_log_alert::msg)) +#endif + .def("log_message", &torrent_log_alert::log_message) + ; + + class_, noncopyable>( + "peer_log_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def("msg", depr(&peer_log_alert::msg)) +#endif + .def("log_message", &peer_log_alert::log_message) + ; + + class_, noncopyable>( + "picker_log_alert", no_init) + .add_property("picker_flags", &picker_log_alert::picker_flags) + .def("blocks", &picker_log_alert::blocks) + ; + + class_, noncopyable>( + "lsd_error_alert", no_init) + .def_readonly("error", &lsd_error_alert::error) + ; + + class_, noncopyable>( + "dht_stats_alert", no_init) + .add_property("active_requests", &dht_stats_active_requests) + .add_property("routing_table", &dht_stats_routing_table) + ; + + class_, noncopyable>("dht_log_alert", no_init) + .add_property("module", make_getter(&dht_log_alert::module, by_value())) + .def("log_message", &dht_log_alert::log_message) + ; + + class_, noncopyable>( + "dht_pkt_alert", no_init) + .add_property("pkt_buf", &get_pkt_buf) + ; + + class_, noncopyable>( + "dht_immutable_item_alert", no_init) + .add_property("target", make_getter(&dht_immutable_item_alert::target, by_value())) + .add_property("item", &dht_immutable_item) + ; + + class_, noncopyable>( + "dht_mutable_item_alert", no_init) + .add_property("key", make_getter(&dht_mutable_item_alert::key, by_value())) + .add_property("signature", make_getter(&dht_mutable_item_alert::signature, by_value())) + .def_readonly("seq", &dht_mutable_item_alert::seq) + .def_readonly("salt", &dht_mutable_item_alert::salt) + .add_property("item", &dht_mutable_item) + .def_readonly("authoritative", &dht_mutable_item_alert::authoritative) + ; + + class_, noncopyable>( + "dht_put_alert", no_init) + .add_property("target", make_getter(&dht_put_alert::target, by_value())) + .add_property("public_key", make_getter(&dht_put_alert::public_key, by_value())) + .add_property("signature", make_getter(&dht_put_alert::signature, by_value())) + .def_readonly("salt", &dht_put_alert::salt) + .def_readonly("seq", &dht_put_alert::seq) + .def_readonly("num_success", &dht_put_alert::num_success) + ; + + class_, noncopyable>( + "session_stats_alert", no_init) + .add_property("values", &session_stats_values) + ; + + class_, noncopyable>( + "session_stats_header_alert", no_init) + ; + + std::vector (dht_get_peers_reply_alert::*peers)() const = &dht_get_peers_reply_alert::peers; + + class_, noncopyable>( + "dht_get_peers_reply_alert", no_init) + .def_readonly("info_hash", &dht_get_peers_reply_alert::info_hash) + .def("num_peers", &dht_get_peers_reply_alert::num_peers) + .def("peers", peers) + ; + + class_, noncopyable>( + "block_uploaded_alert", no_init) + .add_property("block_index", &block_uploaded_alert::block_index) + .add_property("piece_index", make_getter(&block_uploaded_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "alerts_dropped_alert", no_init) + .add_property("dropped_alerts", &get_dropped_alerts) + ; + + class_, noncopyable>( + "socks5_alert", no_init) + .def_readonly("error", &socks5_alert::error) + .def_readonly("op", &socks5_alert::op) + .add_property("ip", make_getter(&socks5_alert::ip, by_value())) + ; + + class_, noncopyable>( + "file_prio_alert", no_init) + ; + + class_, noncopyable>( + "dht_live_nodes_alert", no_init) + .add_property("node_id", &dht_live_nodes_alert::node_id) + .add_property("num_nodes", &dht_live_nodes_alert::num_nodes) + .add_property("nodes", &dht_live_nodes_nodes) + ; + + std::vector (dht_sample_infohashes_alert::*samples)() const = &dht_sample_infohashes_alert::samples; + + class_, noncopyable>( + "dht_sample_infohashes_alert", no_init) + .add_property("endpoint", make_getter(&dht_sample_infohashes_alert::endpoint, by_value())) + .add_property("interval", make_getter(&dht_sample_infohashes_alert::interval, by_value())) + .add_property("num_infohashes", &dht_sample_infohashes_alert::num_infohashes) + .add_property("num_samples", &dht_sample_infohashes_alert::num_samples) + .add_property("samples", samples) + .add_property("num_nodes", &dht_sample_infohashes_alert::num_nodes) + .add_property("nodes", &dht_sample_infohashes_nodes) + ; + + class_, noncopyable>( + "dht_bootstrap_alert", no_init) + ; + + class_, noncopyable>( + "oversized_file_alert", no_init) + ; + + class_, noncopyable>( + "torrent_conflict_alert", no_init) + .add_property("conflicting_torrent", make_getter(&torrent_conflict_alert::conflicting_torrent, by_value())) + .add_property("metadata", make_getter(&torrent_conflict_alert::metadata, by_value())) + ; + + class_, noncopyable>( + "peer_info_alert", no_init) + .add_property("peer_info", make_getter(&peer_info_alert::peer_info, by_value())) + ; + + class_, noncopyable>( + "file_progress_alert", no_init) + .add_property("files", make_getter(&file_progress_alert::files, by_value())) + ; + + class_, noncopyable>( + "piece_info_alert", no_init) + .add_property("piece_info", make_getter(&piece_info_alert::piece_info, by_value())) + ; + + class_, noncopyable>( + "piece_availability_alert", no_init) + .add_property("piece_availability", make_getter(&piece_availability_alert::piece_availability, by_value())) + ; + + class_, noncopyable>( + "tracker_list_alert", no_init) + .add_property("trackers", make_getter(&tracker_list_alert::trackers, by_value())) + ; +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/bindings/python/src/boost_python.hpp b/bindings/python/src/boost_python.hpp new file mode 100644 index 0000000..9b287b2 --- /dev/null +++ b/bindings/python/src/boost_python.hpp @@ -0,0 +1,45 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_PYTHON_HPP +#define BOOST_PYTHON_HPP + +#include +#include +// https://github.com/boostorg/system/issues/32#issuecomment-462912013 +#define HAVE_SNPRINTF +#include + +#include + +// in boost 1.60, placeholders moved into a namespace, just like std +#if BOOST_VERSION >= 106000 +using namespace boost::placeholders; +#endif + +#include +#include + +#include + +#include + +// something in here creates a define for this, presumably to make older +// versions of msvc appear to support snprintf +#ifdef snprintf +#undef snprintf +#endif + +#ifdef vsnprintf +#undef vsnprintf +#endif + +inline void python_deprecated(char const* msg) +{ + if (PyErr_WarnEx(PyExc_DeprecationWarning, msg, 1) == -1) + boost::python::throw_error_already_set(); +} + +#endif + diff --git a/bindings/python/src/bytes.hpp b/bindings/python/src/bytes.hpp new file mode 100644 index 0000000..27611c0 --- /dev/null +++ b/bindings/python/src/bytes.hpp @@ -0,0 +1,22 @@ +// Copyright Arvid Norberg 2006-2013. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BYTES_HPP +#define BYTES_HPP + +#include + +struct bytes +{ + bytes(char const* s, std::size_t len): arr(s, len) {} + bytes(std::string const& s): arr(s) {} + bytes(std::string&& s): arr(std::move(s)) {} + bytes(bytes const&) = default; + bytes(bytes&&) noexcept = default; + bytes& operator=(bytes&&) & noexcept = default; + bytes() {} + std::string arr; +}; + +#endif diff --git a/bindings/python/src/converters.cpp b/bindings/python/src/converters.cpp new file mode 100644 index 0000000..d989329 --- /dev/null +++ b/bindings/python/src/converters.cpp @@ -0,0 +1,559 @@ +// Copyright Andrew Resch 2009. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/session_stats.hpp" // for stats_metric +#include "libtorrent/time.hpp" +#include "libtorrent/torrent_flags.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/disk_interface.hpp" // for open_file_state +#include "libtorrent/aux_/noexcept_movable.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/alert_types.hpp" // for picker_flags_t +#include "libtorrent/session_types.hpp" // for save_state_flags_t +#include "libtorrent/file_storage.hpp" // for file_flags_t +#include "libtorrent/alert.hpp" +#include "libtorrent/create_torrent.hpp" // for create_flags_t +#include "libtorrent/portmap.hpp" // for port_mapping_t +#include "libtorrent/peer_class.hpp" +#include "libtorrent/pex_flags.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/kademlia/announce_flags.hpp" +#include "libtorrent/write_resume_data.hpp" +#include +#include + +using namespace boost::python; +namespace bp = boost::python; + +template +struct endpoint_to_tuple +{ + static PyObject* convert(T const& ep) + { + return incref(bp::make_tuple(ep.address().to_string(), ep.port()).ptr()); + } +}; + +template +struct tuple_to_endpoint +{ + tuple_to_endpoint() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + if (!PyTuple_Check(x)) return nullptr; + if (PyTuple_Size(x) != 2) return nullptr; + extract ip(object(borrowed(PyTuple_GetItem(x, 0)))); + if (!ip.check()) return nullptr; + extract port(object(borrowed(PyTuple_GetItem(x, 1)))); + if (!port.check()) return nullptr; + lt::error_code ec; + lt::make_address(ip, ec); + if (ec) return nullptr; + return x; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data) + ->storage.bytes; + + object o(borrowed(x)); + data->convertible = new (storage) T(lt::make_address( + static_cast(extract(o[0]))), extract(o[1])); + } +}; + +template +struct pair_to_tuple +{ + static PyObject* convert(const std::pair& p) + { + return incref(bp::make_tuple(p.first, p.second).ptr()); + } +}; + +template +struct address_to_tuple +{ + static PyObject* convert(Addr const& addr) + { + return incref(bp::object(addr.to_string()).ptr()); + } +}; + +template +struct tuple_to_pair +{ + tuple_to_pair() + { + converter::registry::push_back( + &convertible, &construct, type_id>() + ); + } + + static void* convertible(PyObject* x) + { + return (PyTuple_Check(x) && PyTuple_Size(x) == 2) ? x: nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage< + std::pair>*)data)->storage.bytes; + + object o(borrowed(x)); + std::pair p; + p.first = extract(o[0]); + p.second = extract(o[1]); + data->convertible = new (storage) std::pair(p); + } +}; + +struct from_string_view +{ + static PyObject* convert(lt::string_view v) + { + str ret(v.data(), v.size()); + return incref(ret.ptr()); + } +}; + +struct to_string_view +{ + to_string_view() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + return +#if PY_VERSION_HEX < 0x03020000 + PyString_Check(x) ? x : +#endif + PyUnicode_Check(x) ? x : nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage< + lt::string_view>*)data)->storage.bytes; + +#if PY_VERSION_HEX < 0x03020000 + if (PyString_Check(x)) + { + data->convertible = new (storage) lt::string_view( + PyString_AsString(x), PyString_Size(x)); + } + else +#endif + { + Py_ssize_t size = 0; + char const* unicode = PyUnicode_AsUTF8AndSize(x, &size); + data->convertible = new (storage) lt::string_view(unicode, size); + } + } +}; + +template +struct map_to_dict +{ + static PyObject* convert(Map const& m) + { + dict ret; + for (auto const& e : m) + ret[e.first] = e.second; + return incref(ret.ptr()); + } +}; + +template> +struct dict_to_map +{ + dict_to_map() + { + converter::registry::push_back(&convertible, &construct, type_id()); + } + + static void* convertible(PyObject* x) + { + return PyDict_Check(x) ? x: nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + + dict o(borrowed(x)); + Map m; + + stl_input_iterator i(o.keys()), end; + for (; i != end; ++i) + { + T1 const& key = *i; + m[key] = extract(o[key]); + } + data->convertible = new (storage) Map(m); + } +}; + +template +struct vector_to_list +{ + static PyObject* convert(T const& v) + { + list l; + for (int i = 0; i < int(v.size()); ++i) + { + l.append(v[i]); + } + return incref(l.ptr()); + } +}; + +template +struct list_to_vector +{ + list_to_vector() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + return PyList_Check(x) ? x: nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage< + T>*)data)->storage.bytes; + + T p; + int const size = int(PyList_Size(x)); + p.reserve(size); + for (int i = 0; i < size; ++i) + { + object o(borrowed(PyList_GetItem(x, i))); + p.push_back(extract(o)); + } + data->convertible = new (storage) T(std::move(p)); + } +}; + +template +struct list_to_bitfield +{ + list_to_bitfield() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + return PyList_Check(x) ? x : nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage< + T>*)data)->storage.bytes; + + T p; + int const size = int(PyList_Size(x)); + p.resize(size); + for (int i = 0; i < size; ++i) + { + object o(borrowed(PyList_GetItem(x, i))); + if (extract(o)) p.set_bit(IndexType{i}); + else p.clear_bit(IndexType{i}); + } + data->convertible = new (storage) T(std::move(p)); + } +}; + +template +struct bitfield_to_list +{ + static PyObject* convert(T const& v) + { + list ret; + for (auto const i : v) + ret.append(i); + return incref(ret.ptr()); + } +}; + +template +struct from_strong_typedef +{ + using underlying_type = typename T::underlying_type; + + static PyObject* convert(const T& v) + { + object o(static_cast(v)); + return incref(o.ptr()); + } +}; + +template +struct to_strong_typedef +{ + using underlying_type = typename T::underlying_type; + + to_strong_typedef() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + return PyNumber_Check(x) ? x : nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + data->convertible = new (storage) T(extract(object(borrowed(x)))); + } +}; + +template +struct to_enum_class +{ + using underlying_type = typename std::underlying_type::type; + + to_enum_class() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + return PyNumber_Check(x) ? x : nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + data->convertible = new (storage) T(static_cast(static_cast(extract(object(borrowed(x)))))); + } +}; + +template +struct from_bitfield_flag +{ + using underlying_type = typename T::underlying_type; + + static PyObject* convert(T const v) + { + // this is because python uses "long int" to represent integral values + // internally, it cannot hold large unsigned values + auto const val = static_cast(v) + & static_cast(std::numeric_limits::max()); + object o(val); + return incref(o.ptr()); + } +}; + +template +struct to_bitfield_flag +{ + using underlying_type = typename T::underlying_type; + + to_bitfield_flag() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + return PyNumber_Check(x) ? x : nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + data->convertible = new (storage) T(extract(object(borrowed(x)))); + } +}; + +void bind_converters() +{ + // C++ -> python conversions + to_python_converter, pair_to_tuple>(); + to_python_converter, pair_to_tuple>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter, pair_to_tuple>(); + to_python_converter, pair_to_tuple>(); + + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + + to_python_converter, bitfield_to_list>>(); + to_python_converter>(); + + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter(); + + // work-around types + to_python_converter, address_to_tuple< + lt::aux::noexcept_movable>>(); + to_python_converter, endpoint_to_tuple< + lt::aux::noexcept_movable>>(); + to_python_converter, endpoint_to_tuple< + lt::aux::noexcept_movable>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>>, vector_to_list>>>>(); + to_python_converter>, map_to_dict>>>(); + to_python_converter>, map_to_dict>>>(); + to_python_converter, map_to_dict>>(); + to_python_converter>(); + +#if TORRENT_ABI_VERSION == 1 + to_python_converter>, vector_to_list>>>(); + list_to_vector>>(); + +#ifndef TORRENT_DISABLE_DHT + to_python_converter, vector_to_list>>(); +#endif +#endif + + // python -> C++ conversions + tuple_to_pair(); + tuple_to_pair(); + tuple_to_endpoint(); + tuple_to_endpoint(); + tuple_to_pair(); + dict_to_map(); + list_to_vector>(); + list_to_vector>(); + list_to_vector>(); + list_to_vector>(); + list_to_vector>(); + list_to_vector>>(); + list_to_vector>>(); + + // work-around types + list_to_vector>>(); + list_to_vector>>(); + list_to_vector>>(); + list_to_vector>>(); + list_to_vector>>(); + list_to_vector>>>(); + list_to_vector>>(); + dict_to_map>>(); + dict_to_map>>(); + + // bitfield types + list_to_bitfield, lt::piece_index_t>(); + list_to_bitfield(); + + bitfield_to_list>(); + bitfield_to_list(); + + to_strong_typedef(); + to_strong_typedef(); + to_strong_typedef(); + to_strong_typedef(); + to_strong_typedef(); + to_strong_typedef(); + to_enum_class(); +#if TORRENT_ABI_VERSION <= 2 + to_enum_class(); +#endif + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_string_view(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); +} diff --git a/bindings/python/src/create_torrent.cpp b/bindings/python/src/create_torrent.cpp new file mode 100644 index 0000000..cf8a84b --- /dev/null +++ b/bindings/python/src/create_torrent.cpp @@ -0,0 +1,279 @@ +// Copyright Daniel Wallin & Arvid Norberg 2009. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include +#include "libtorrent/torrent_info.hpp" +#include +#include "bytes.hpp" +#include "gil.hpp" + +using namespace boost::python; +using namespace lt; + +#ifdef _MSC_VER +#pragma warning(push) +// warning c4996: x: was declared deprecated +#pragma warning( disable : 4996 ) +#endif + +namespace +{ + void set_hash(create_torrent& c, piece_index_t p, bytes const& b) + { + c.set_hash(p, sha1_hash(b.arr)); + } + +#if TORRENT_ABI_VERSION < 3 + void set_file_hash(create_torrent& c, file_index_t f, bytes const& b) + { + c.set_file_hash(f, sha1_hash(b.arr)); + } +#endif + +#ifndef BOOST_NO_EXCEPTIONS + void set_piece_hashes_callback(create_torrent& c, std::string const& p + , boost::python::object cb) + { + set_piece_hashes(c, p, std::function( + [&](piece_index_t const i) { cb(i); })); + } +#else + void set_piece_hashes_callback(create_torrent& c, std::string const& p + , boost::python::object cb) + { + error_code ec; + set_piece_hashes(c, p, [&](piece_index_t const i) { cb(i); }, ec); + } + + void set_piece_hashes0(create_torrent& c, std::string const & s) + { + error_code ec; + set_piece_hashes(c, s, ec); + } +#endif + + void add_node(create_torrent& ct, std::string const& addr, int port) + { + ct.add_node(std::make_pair(addr, port)); + } + +#if TORRENT_ABI_VERSION == 1 + void add_file_deprecated(file_storage& ct, file_entry const& fe) + { + python_deprecated("this overload of add_file() is deprecated"); + ct.add_file(fe); + } + + struct FileIter + { + using value_type = lt::file_entry; + using reference = lt::file_entry; + using pointer = lt::file_entry*; + using difference_type = int; + using iterator_category = std::forward_iterator_tag; + + FileIter(file_storage const& fs, file_index_t i) : m_fs(&fs), m_i(i) {} + FileIter(FileIter const&) = default; + FileIter() : m_fs(nullptr), m_i(0) {} + lt::file_entry operator*() const + { return m_fs->at(m_i); } + + FileIter operator++() { m_i++; return *this; } + FileIter operator++(int) { return FileIter(*m_fs, m_i++); } + + bool operator==(FileIter const& rhs) const + { return m_fs == rhs.m_fs && m_i == rhs.m_i; } + + int operator-(FileIter const& rhs) const + { + assert(rhs.m_fs == m_fs); + return m_i - rhs.m_i; + } + + FileIter& operator=(FileIter const&) = default; + + file_storage const* m_fs; + file_index_t m_i; + }; + + FileIter begin_files(file_storage const& self) + { + python_deprecated("__iter__ is deprecated"); + return FileIter(self, file_index_t(0)); + } + + FileIter end_files(file_storage const& self) + { return FileIter(self, self.end_file()); } +#endif // TORRENT_ABI_VERSION + + void add_files_callback(file_storage& fs, std::string const& file + , boost::python::object cb, create_flags_t const flags) + { + add_files(fs, file, [&](std::string const& i) { return cb(i); }, flags); + } + + void add_file(file_storage& fs, std::string const& file, std::int64_t size + , file_flags_t const flags, std::time_t md, std::string link) + { + fs.add_file(file, size, flags, md, link); + } + + void add_tracker(create_torrent& ct, std::string url, int tier) + { + ct.add_tracker(url, tier); + } + + struct dummy13 {}; + struct dummy14 {}; +} + +void bind_create_torrent() +{ + void (file_storage::*set_name0)(std::string const&) = &file_storage::set_name; + void (file_storage::*rename_file0)(file_index_t, std::string const&) = &file_storage::rename_file; + +#ifndef BOOST_NO_EXCEPTIONS + void (*set_piece_hashes0)(create_torrent&, std::string const&) = &set_piece_hashes; +#endif + void (*add_files0)(file_storage&, std::string const&, create_flags_t) = add_files; + + std::string (file_storage::*file_storage_symlink)(file_index_t) const = &file_storage::symlink; + sha1_hash (file_storage::*file_storage_hash)(file_index_t) const = &file_storage::hash; + std::string (file_storage::*file_storage_file_path)(file_index_t, std::string const&) const = &file_storage::file_path; + string_view (file_storage::*file_storage_file_name)(file_index_t) const = &file_storage::file_name; + std::int64_t (file_storage::*file_storage_file_size)(file_index_t) const = &file_storage::file_size; + std::int64_t (file_storage::*file_storage_file_offset)(file_index_t) const = &file_storage::file_offset; + file_flags_t (file_storage::*file_storage_file_flags)(file_index_t) const = &file_storage::file_flags; + +#if TORRENT_ABI_VERSION == 1 + file_entry (file_storage::*at)(int) const = &file_storage::at; +#endif + + // TODO: 3 move this to its own file + { + scope s = class_("file_storage") + .def("is_valid", &file_storage::is_valid) + .def("add_file", add_file, (arg("path"), arg("size"), arg("flags") = 0, arg("mtime") = 0, arg("linkpath") = "")) + .def("num_files", &file_storage::num_files) +#if TORRENT_ABI_VERSION == 1 + .def("at", depr(at)) + .def("add_file", add_file_deprecated, arg("entry")) + .def("__iter__", boost::python::range(&begin_files, &end_files)) + .def("__len__", depr(&file_storage::num_files)) +#endif // TORRENT_ABI_VERSION + .def("hash", file_storage_hash) + .def("symlink", file_storage_symlink) + .def("file_path", file_storage_file_path, (arg("idx"), arg("save_path") = "")) + .def("file_name", file_storage_file_name) + .def("file_size", file_storage_file_size) + .def("root", &file_storage::root) + .def("file_offset", file_storage_file_offset) + .def("file_flags", file_storage_file_flags) + + .def("file_index_for_root", &file_storage::file_index_for_root) + .def("piece_index_at_file", &file_storage::piece_index_at_file) + .def("file_index_at_piece", &file_storage::file_index_at_piece) + .def("file_index_at_offset", &file_storage::file_index_at_offset) + .def("file_absolute_path", &file_storage::file_absolute_path) + + .def("v2", &file_storage::v2) + + .def("total_size", &file_storage::total_size) + .def("set_num_pieces", &file_storage::set_num_pieces) + .def("num_pieces", &file_storage::num_pieces) + .def("set_piece_length", &file_storage::set_piece_length) + .def("piece_length", &file_storage::piece_length) + .def("piece_size", &file_storage::piece_size) + .def("set_name", set_name0) + .def("rename_file", rename_file0) + .def("name", &file_storage::name, return_value_policy()) + ; + + s.attr("flag_pad_file") = file_storage::flag_pad_file; + s.attr("flag_hidden") = file_storage::flag_hidden; + s.attr("flag_executable") = file_storage::flag_executable; + s.attr("flag_symlink") = file_storage::flag_symlink; + } + + { + scope s = class_("file_flags_t"); + s.attr("flag_pad_file") = file_storage::flag_pad_file; + s.attr("flag_hidden") = file_storage::flag_hidden; + s.attr("flag_executable") = file_storage::flag_executable; + s.attr("flag_symlink") = file_storage::flag_symlink; + } + + { + scope s = class_("create_torrent", no_init) + .def(init()) + .def(init(arg("ti"))) + .def(init((arg("storage"), arg("piece_size") = 0 + , arg("flags") = create_flags_t{}))) + + .def("generate", &create_torrent::generate) + .def("generate_buf", &create_torrent::generate_buf) + + .def("files", &create_torrent::files, return_internal_reference<>()) + .def("set_comment", &create_torrent::set_comment) + .def("set_creator", &create_torrent::set_creator) + .def("set_hash", &set_hash) +#if TORRENT_ABI_VERSION < 3 + .def("set_file_hash", &set_file_hash) +#endif + .def("add_url_seed", &create_torrent::add_url_seed) + .def("add_http_seed", &create_torrent::add_http_seed) + .def("add_node", &add_node) + .def("add_tracker", add_tracker, (arg("announce_url"), arg("tier") = 0)) + .def("set_priv", &create_torrent::set_priv) + .def("num_pieces", &create_torrent::num_pieces) + .def("piece_length", &create_torrent::piece_length) + .def("piece_size", &create_torrent::piece_size) + .def("priv", &create_torrent::priv) + .def("set_root_cert", &create_torrent::set_root_cert, (arg("pem"))) + .def("add_collection", &create_torrent::add_collection) + .def("add_similar_torrent", &create_torrent::add_similar_torrent) + + ; + +#if TORRENT_ABI_VERSION <= 2 + s.attr("optimize_alignment") = create_torrent::optimize_alignment; + s.attr("merkle") = create_torrent::merkle; +#endif + s.attr("v2_only") = create_torrent::v2_only; + s.attr("v1_only") = create_torrent::v1_only; + s.attr("canonical_files") = create_torrent::canonical_files; + s.attr("modification_time") = create_torrent::modification_time; + s.attr("symlinks") = create_torrent::symlinks; + s.attr("no_attributes") = create_torrent::no_attributes; + s.attr("canonical_files_no_tail_padding") = create_torrent::canonical_files_no_tail_padding; + } + + { + scope s = class_("create_torrent_flags_t"); +#if TORRENT_ABI_VERSION == 1 + s.attr("optimize") = create_torrent::optimize; +#endif +#if TORRENT_ABI_VERSION <= 2 + s.attr("optimize_alignment") = create_torrent::optimize_alignment; + s.attr("merkle") = create_torrent::merkle; +#endif + s.attr("v2_only") = create_torrent::v2_only; + s.attr("modification_time") = create_torrent::modification_time; + s.attr("symlinks") = create_torrent::symlinks; + } + + def("add_files", add_files0, (arg("fs"), arg("path"), arg("flags") = 0)); + def("add_files", add_files_callback, (arg("fs"), arg("path") + , arg("predicate"), arg("flags") = 0)); + def("set_piece_hashes", set_piece_hashes0); + def("set_piece_hashes", set_piece_hashes_callback); + +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + diff --git a/bindings/python/src/datetime.cpp b/bindings/python/src/datetime.cpp new file mode 100644 index 0000000..454b22b --- /dev/null +++ b/bindings/python/src/datetime.cpp @@ -0,0 +1,144 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include "optional.hpp" +#include +#include "libtorrent/time.hpp" +#include + +using namespace boost::python; + +object datetime_timedelta; +object datetime_datetime; + +template +struct chrono_duration_to_python +{ + static PyObject* convert(Duration const& d) + { + std::int64_t const us = lt::total_microseconds(d); + object result = datetime_timedelta( + 0 // days + , us / 1000000 // seconds + , us % 1000000 // microseconds + ); + + return incref(result.ptr()); + } +}; + +struct time_duration_to_python +{ + static PyObject* convert(boost::posix_time::time_duration const& d) + { + object result = datetime_timedelta( + 0 // days + , 0 // seconds + , d.total_microseconds() + ); + + return incref(result.ptr()); + } +}; + +template struct tag {}; + +lt::time_point now(::tag) +{ return lt::clock_type::now(); } + +lt::time_point32 now(::tag) +{ return lt::time_point_cast(lt::clock_type::now()); } + +template +struct time_point_to_python +{ + static PyObject* convert(T const pt) + { + using std::chrono::system_clock; + using std::chrono::duration_cast; + object result; + if (pt > T()) + { + time_t const tm = system_clock::to_time_t(system_clock::now() + + duration_cast(pt - now(::tag()))); + +#ifdef TORRENT_WINDOWS + std::tm const* date = localtime(&tm); +#else + std::tm buf; + std::tm const* date = localtime_r(&tm, &buf); +#endif + + result = datetime_datetime( + (int)1900 + date->tm_year + // tm use 0-11 and we need 1-12 + , (int)date->tm_mon + 1 + , (int)date->tm_mday + , date->tm_hour + , date->tm_min + , date->tm_sec + ); + } + else + { + result = object(); + } + return incref(result.ptr()); + } +}; + +struct ptime_to_python +{ + static PyObject* convert(boost::posix_time::ptime const& pt) + { + boost::gregorian::date date = pt.date(); + boost::posix_time::time_duration td = pt.time_of_day(); + + object result = datetime_datetime( + (int)date.year() + , (int)date.month() + , (int)date.day() + , td.hours() + , td.minutes() + , td.seconds() + ); + + return incref(result.ptr()); + } +}; + +void bind_datetime() +{ + object datetime = import("datetime").attr("__dict__"); + + datetime_timedelta = datetime["timedelta"]; + datetime_datetime = datetime["datetime"]; + + to_python_converter(); + + to_python_converter(); + + to_python_converter>(); + + to_python_converter>(); + + to_python_converter>(); + + to_python_converter>(); + + to_python_converter>(); + + optional_to_python(); + optional_to_python(); +} + diff --git a/bindings/python/src/entry.cpp b/bindings/python/src/entry.cpp new file mode 100644 index 0000000..e4c55aa --- /dev/null +++ b/bindings/python/src/entry.cpp @@ -0,0 +1,185 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include "bytes.hpp" + +using namespace boost::python; +using namespace lt; + +struct entry_to_python +{ + static object convert(entry::list_type const& l) + { + list result; + + for (entry::list_type::const_iterator i(l.begin()), e(l.end()); i != e; ++i) + { + result.append(*i); + } + + return TORRENT_RVO(result); + } + + static object convert(entry::dictionary_type const& d) + { + dict result; + + for (entry::dictionary_type::const_iterator i(d.begin()), e(d.end()); i != e; ++i) + result[bytes(i->first)] = i->second; + + return TORRENT_RVO(result); + } + + static object convert0(entry const& e) + { + switch (e.type()) + { + case entry::int_t: + return object(e.integer()); + case entry::string_t: + return object(bytes(e.string())); + case entry::list_t: + return convert(e.list()); + case entry::dictionary_t: + return convert(e.dict()); + case entry::preformatted_t: + { + std::vector const& pre = e.preformatted(); + list l; + for (std::vector::const_iterator i = pre.begin() + , end(pre.end()); i != end; ++i) + l.append(int(*i)); + return tuple(l); + } + default: + return object(); + } + } + + static PyObject* convert(std::shared_ptr const& e) + { + if (!e) + return incref(Py_None); + return convert(*e); + } + + static PyObject* convert(entry const& e) + { + return incref(convert0(e).ptr()); + } +}; + +struct entry_from_python +{ + entry_from_python() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* e) + { + return e; + } + + static entry construct0(object e) + { + if (extract(e).check()) + { + dict d = extract(e); + list items(d.items()); + std::size_t length = extract(items.attr("__len__")()); + entry result(entry::dictionary_t); + + for (std::size_t i = 0; i < length; ++i) + { + if (extract(items[i][0]).check()) + { + result.dict().insert( + std::make_pair( + extract(items[i][0])().arr, + construct0(items[i][1]) + ) + ); + } + else + { + result.dict().insert( + std::make_pair( + extract(items[i][0])(), + construct0(items[i][1]) + ) + ); + } + } + + return result; + } + else if (extract(e).check()) + { + list l = extract(e); + + std::size_t length = extract(l.attr("__len__")()); + entry result(entry::list_t); + + for (std::size_t i = 0; i < length; ++i) + { + result.list().push_back(construct0(l[i])); + } + + return result; + } + else if (extract(e).check()) + { + return entry(extract(e)().arr); + } + else if (extract(e).check()) + { + return entry(extract(e)()); + } + else if (extract(e).check()) + { + return entry(extract(e)()); + } + else if (extract(e).check()) + { + tuple t = extract(e); + + std::size_t const length = extract(t.attr("__len__")()); + std::vector preformatted(length); + for (std::size_t i = 0; i < length; ++i) + { + preformatted[i] = char(extract(t[i])); + } + + return entry(preformatted); + } + else + { + // TODO: Throw a TypeError here in the future + python_deprecated("constructing a bencode entry from anything but " + "int, dict, list, string, bytes and int-tuple is deprecated"); + } + + return entry(); + } + + static void construct(PyObject* e, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + new (storage) entry(construct0(object(borrowed(e)))); + data->convertible = storage; + } +}; + +void bind_entry() +{ + to_python_converter, entry_to_python>(); + to_python_converter(); + entry_from_python(); +} + diff --git a/bindings/python/src/error_code.cpp b/bindings/python/src/error_code.cpp new file mode 100644 index 0000000..6e802b3 --- /dev/null +++ b/bindings/python/src/error_code.cpp @@ -0,0 +1,240 @@ +/* + +Copyright (c) 2011, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "boost_python.hpp" +#include +#include +#include +#include + +namespace boost +{ + // this fixes mysterious link error on msvc + template <> + inline boost::system::error_category const volatile* + get_pointer(class boost::system::error_category const volatile* p) + { + return p; + } +} + +#include +#if TORRENT_USE_SSL +#include +#include +#endif +#if TORRENT_USE_I2P +#include +#endif + +using namespace boost::python; +using namespace lt; +using boost::system::error_category; + +namespace { + + struct ec_pickle_suite : boost::python::pickle_suite + { + static boost::python::tuple + getinitargs(error_code const&) + { + return boost::python::tuple(); + } + + static boost::python::tuple + getstate(error_code const& ec) + { + return boost::python::make_tuple(ec.value(), ec.category().name()); + } + + static void + setstate(error_code& ec, boost::python::tuple state) + { + using namespace boost::python; + if (len(state) != 2) + { + PyErr_SetObject(PyExc_ValueError, + ("expected 2-item tuple in call to __setstate__; got %s" + % state).ptr()); + throw_error_already_set(); + } + + int const value = extract(state[0]); + std::string const category = extract(state[1]); + if (category == "system") + ec.assign(value, lt::system_category()); + else if (category == "generic") + ec.assign(value, lt::generic_category()); + else if (category == "libtorrent") + ec.assign(value, lt::libtorrent_category()); + else if (category == "http error") + ec.assign(value, lt::http_category()); + else if (category == "UPnP error") + ec.assign(value, lt::upnp_category()); + else if (category == "bdecode error") + ec.assign(value, lt::bdecode_category()); + else if (category == "asio.netdb") + ec.assign(value, boost::asio::error::get_netdb_category()); + else if (category == "asio.addinfo") + ec.assign(value, boost::asio::error::get_addrinfo_category()); + else if (category == "asio.misc") + ec.assign(value, boost::asio::error::get_misc_category()); +#if TORRENT_USE_SSL + else if (category == "asio.ssl") + ec.assign(value, boost::asio::error::get_ssl_category()); +#endif + else + { + PyErr_SetObject(PyExc_ValueError, + ("unexpected error_category passed to __setstate__; got '%s'" + % object(category)).ptr()); + throw_error_already_set(); + } + } + }; +} + +struct category_holder +{ + category_holder(boost::system::error_category const& cat) : m_cat(&cat) {} + char const* name() const { return m_cat->name(); } + std::string message(int const v) const { return m_cat->message(v); } + + friend bool operator==(category_holder const lhs, category_holder const rhs) + { return *lhs.m_cat == *rhs.m_cat; } + + friend bool operator!=(category_holder const lhs, category_holder const rhs) + { return *lhs.m_cat != *rhs.m_cat; } + + friend bool operator<(category_holder const lhs, category_holder const rhs) + { return *lhs.m_cat < *rhs.m_cat; } + + boost::system::error_category const& ref() const { return *m_cat; } + operator boost::system::error_category const&() const { return *m_cat; } +private: + boost::system::error_category const* m_cat; +}; + +void error_code_assign(boost::system::error_code& me, int const v, category_holder const cat) +{ + me.assign(v, cat.ref()); +} + +category_holder error_code_category(boost::system::error_code const& me) +{ + return category_holder(me.category()); +} + +#define WRAP_CAT(name) \ + category_holder wrap_ ##name## _category() { return category_holder(name## _category()); } + +WRAP_CAT(libtorrent) +WRAP_CAT(upnp) +WRAP_CAT(http) +WRAP_CAT(socks) +WRAP_CAT(bdecode) +#if TORRENT_USE_I2P +WRAP_CAT(i2p) +#endif +WRAP_CAT(generic) +WRAP_CAT(system) + +#undef WRAP_CAT + +#if TORRENT_ABI_VERSION == 1 + +#define WRAP_DEPR_CAT(name) \ + category_holder wrap_ ##name## _category_deprecated() { \ + python_deprecated(#name " is deprecated"); \ + return category_holder(name## _category()); \ + } + +WRAP_DEPR_CAT(libtorrent) +WRAP_DEPR_CAT(upnp) +WRAP_DEPR_CAT(http) +WRAP_DEPR_CAT(socks) +WRAP_DEPR_CAT(bdecode) +#if TORRENT_USE_I2P +WRAP_DEPR_CAT(i2p) +#endif +WRAP_DEPR_CAT(generic) +WRAP_DEPR_CAT(system) + +#undef WRAP_DEPR_CAT +#endif + +void bind_error_code() +{ + class_("error_category", no_init) + .def("name", &category_holder::name) + .def("message", &category_holder::message) + .def(self == self) + .def(self < self) + .def(self != self) + ; + + class_("error_code") + .def(init<>()) + .def(init()) + .def("message", static_cast(&error_code::message)) + .def("value", &error_code::value) + .def("clear", &error_code::clear) + .def("category", &error_code_category) + .def("assign", &error_code_assign) + .def_pickle(ec_pickle_suite()) + ; + + def("libtorrent_category", &wrap_libtorrent_category); + def("upnp_category", &wrap_upnp_category); + def("http_category", &wrap_http_category); + def("socks_category", &wrap_socks_category); + def("bdecode_category", &wrap_bdecode_category); +#if TORRENT_USE_I2P + def("i2p_category", &wrap_i2p_category); +#endif + +#if TORRENT_ABI_VERSION == 1 + def("get_libtorrent_category", &wrap_libtorrent_category_deprecated); + def("get_upnp_category", &wrap_upnp_category_deprecated); + def("get_http_category", &wrap_http_category_deprecated); + def("get_socks_category", &wrap_socks_category_deprecated); + def("get_bdecode_category", &wrap_bdecode_category_deprecated); +#if TORRENT_USE_I2P + def("get_i2p_category", &wrap_i2p_category_deprecated); +#endif +#endif // TORRENT_ABI_VERSION + + def("generic_category", &wrap_generic_category); + + def("system_category", &wrap_system_category); +} + diff --git a/bindings/python/src/fingerprint.cpp b/bindings/python/src/fingerprint.cpp new file mode 100644 index 0000000..dfe0707 --- /dev/null +++ b/bindings/python/src/fingerprint.cpp @@ -0,0 +1,34 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include "gil.hpp" +#include + +void bind_fingerprint() +{ + using namespace boost::python; + using namespace lt; + + def("generate_fingerprint", &generate_fingerprint); + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + class_("fingerprint", no_init) + .def( + init( + (arg("id"), "major", "minor", "revision", "tag") + ) + ) + .def("__str__", depr(&fingerprint::to_string)) + .def_readonly("major_version", depr(&fingerprint::major_version)) + .def_readonly("minor_version", depr(&fingerprint::minor_version)) + .def_readonly("revision_version", depr(&fingerprint::revision_version)) + .def_readonly("tag_version", depr(&fingerprint::tag_version)) + ; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // TORRENT_ABI_VERSION +} diff --git a/bindings/python/src/gil.hpp b/bindings/python/src/gil.hpp new file mode 100644 index 0000000..562ca3c --- /dev/null +++ b/bindings/python/src/gil.hpp @@ -0,0 +1,186 @@ +// Copyright Daniel Wallin 2007. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef GIL_070107_HPP +# define GIL_070107_HPP + +#include + +# include +# include +# include +# include +#include + +#include + +//namespace libtorrent { namespace python { + +// RAII helper to release GIL. +struct allow_threading_guard +{ + allow_threading_guard() : save(PyEval_SaveThread()) {} + ~allow_threading_guard() { PyEval_RestoreThread(save); } + PyThreadState* save; +}; + +struct lock_gil +{ + lock_gil() : state(PyGILState_Ensure()) {} + ~lock_gil() { PyGILState_Release(state); } + PyGILState_STATE state; +}; + +template +struct allow_threading +{ + allow_threading(F fn) : fn(fn) {} + template + R operator()(Self&& s, Args&&... args) + { + allow_threading_guard guard; + return (std::forward(s).*fn)(std::forward(args)...); + } + F fn; +}; + +template +struct visitor : boost::python::def_visitor> +{ + visitor(F fn) : fn(std::move(fn)) {} + + template + void visit_aux( + Class& cl, char const* name + , Options const& options, Signature const& signature) const + { + typedef typename boost::mpl::at_c::type return_type; + + cl.def( + name + , boost::python::make_function( + allow_threading(fn) + , options.policies() + , options.keywords() + , signature + ) + ); + } + + template + void visit(Class& cl, char const* name, Options const& options) const + { + this->visit_aux( + cl, name, options + , boost::python::detail::get_signature(fn, (typename Class::wrapped_type*)0) + ); + } + + F fn; +}; + +// Member function adaptor that releases and acquires the GIL +// around the function call. +template +visitor allow_threads(F fn) +{ + return visitor(fn); +} + +template::type>::value, int>::type = 0> +auto invoke(Fn&& fn, Self&& s, Args&&... args) -> +#if TORRENT_AUTO_RETURN_TYPES + decltype(auto) +#else + decltype((std::forward(s).*std::forward(fn))(std::forward(args)...)) +#endif +{ + return (std::forward(s).*std::forward(fn))(std::forward(args)...); +} + +template::type>::value, int>::type = 0> +auto invoke(Fn&& fn, Self&& s) -> +#if TORRENT_AUTO_RETURN_TYPES + decltype(auto) +#else + decltype((std::forward(s).*std::forward)(fn)) +#endif +{ + return (std::forward(s).*std::forward)(fn); +} + +template::type>::value, int>::type = 0> +auto invoke(Fn&& fn, Args&&... args) -> +#if TORRENT_AUTO_RETURN_TYPES + decltype(auto) +#else + decltype(std::forward(fn)(std::forward(args)...)) +#endif +{ + return std::forward(fn)(std::forward(args)...); +} + +template +struct deprecated_fun +{ + deprecated_fun(F fn, char const* name) : fn(fn), fn_name(name) {} + template + R operator()(Args&&... args) + { + std::string const msg = std::string(fn_name) + "() is deprecated"; + python_deprecated(msg.c_str()); + // TODO: in C++17 use std::invoke + return ::invoke(fn, std::forward(args)...); + } + F fn; + char const* fn_name; +}; + +template +struct deprecate_visitor : boost::python::def_visitor> +{ + deprecate_visitor(F fn) : fn(std::move(fn)) {} + + template + void visit_aux( + Class& cl, char const* name + , Options const& options, Signature const& signature) const + { + using return_type = typename boost::mpl::at_c::type; + + cl.def( + name + , boost::python::make_function( + deprecated_fun(fn, name) + , options.policies() + , options.keywords() + , signature + ) + ); + } + + template + void visit(Class& cl, char const* name, Options const& options) const + { + this->visit_aux( + cl, name, options + , boost::python::detail::get_signature(fn, (typename Class::wrapped_type*)0) + ); + } + + F fn; +}; + +template +deprecate_visitor depr(F fn) +{ + return deprecate_visitor(std::move(fn)); +} + +//}} // namespace libtorrent::python + +#endif // GIL_070107_HPP diff --git a/bindings/python/src/info_hash.cpp b/bindings/python/src/info_hash.cpp new file mode 100644 index 0000000..6ea4772 --- /dev/null +++ b/bindings/python/src/info_hash.cpp @@ -0,0 +1,41 @@ +// Copyright Arvid Norberg 2020. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include + +namespace { + +using namespace lt; + +long get_hash(info_hash_t const& ih) +{ + return std::hash{}(ih); +} + +} + +void bind_info_hash() +{ + using namespace boost::python; + using namespace lt; + + class_("info_hash_t") + .def(init(arg("sha1_hash"))) + .def(init(arg("sha256_hash"))) + .def(init((arg("sha1_hash"), arg("sha256_hash")))) + .def("__hash__", get_hash) + .def("has_v1", &info_hash_t::has_v1) + .def("has_v2", &info_hash_t::has_v2) + .def("has", &info_hash_t::has) + .def("get", &info_hash_t::get) + .def("get_best", &info_hash_t::get_best) + .add_property("v1", &info_hash_t::v1) + .add_property("v2", &info_hash_t::v2) + .def(self == self) + .def(self != self) + .def(self < self) + ; +} + diff --git a/bindings/python/src/ip_filter.cpp b/bindings/python/src/ip_filter.cpp new file mode 100644 index 0000000..766f844 --- /dev/null +++ b/bindings/python/src/ip_filter.cpp @@ -0,0 +1,49 @@ +// Copyright Andrew Resch 2008. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include "gil.hpp" + +using namespace boost::python; +using namespace lt; + +namespace +{ + void add_rule(ip_filter& filter, std::string start, std::string end, int flags) + { + return filter.add_rule(make_address(start), make_address(end), flags); + } + + int access0(ip_filter& filter, std::string addr) + { + return filter.access(make_address(addr)); + } + + template + list convert_range_list(std::vector> const& l) + { + list ret; + for (auto const& r : l) + ret.append(boost::python::make_tuple(r.first.to_string(), r.last.to_string())); + return ret; + } + + tuple export_filter(ip_filter const& f) + { + auto ret = f.export_filter(); + list ipv4 = convert_range_list(std::get<0>(ret)); + list ipv6 = convert_range_list(std::get<1>(ret)); + return boost::python::make_tuple(ipv4, ipv6); + } +} + +void bind_ip_filter() +{ + class_("ip_filter") + .def("add_rule", &add_rule) + .def("access", &access0) + .def("export_filter", &export_filter) + ; +} diff --git a/bindings/python/src/load_torrent.cpp b/bindings/python/src/load_torrent.cpp new file mode 100644 index 0000000..167e63a --- /dev/null +++ b/bindings/python/src/load_torrent.cpp @@ -0,0 +1,55 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +#include "boost_python.hpp" +#include "bytes.hpp" +#include "libtorrent/load_torrent.hpp" +#include "libtorrent/torrent_info.hpp" // for load_torrent_limits +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/bdecode.hpp" + +using namespace boost::python; + +// defined in torrent_info.cpp +lt::load_torrent_limits dict_to_limits(dict limits); + +namespace { + + lt::add_torrent_params load_torrent_file1(std::string filename, dict cfg) +{ + return lt::load_torrent_file(filename, dict_to_limits(cfg)); +} + +lt::add_torrent_params load_torrent_buffer0(bytes b) +{ + return lt::load_torrent_buffer(b.arr); +} + +lt::add_torrent_params load_torrent_buffer1(bytes b, dict cfg) +{ + return lt::load_torrent_buffer(b.arr, dict_to_limits(cfg)); +} + + +lt::add_torrent_params load_torrent_parsed1(lt::bdecode_node const& n, dict cfg) +{ + return lt::load_torrent_parsed(n, dict_to_limits(cfg)); +} + +} + +void bind_load_torrent() +{ + lt::add_torrent_params (*load_torrent_file0)(std::string const&) = <::load_torrent_file; + lt::add_torrent_params (*load_torrent_parsed0)(lt::bdecode_node const&) = <::load_torrent_parsed; + + def("load_torrent_file", load_torrent_file0); + def("load_torrent_file", load_torrent_file1); + def("load_torrent_buffer", load_torrent_buffer0); + def("load_torrent_buffer", load_torrent_buffer1); + def("load_torrent_parsed", load_torrent_parsed0); + def("load_torrent_parsed", load_torrent_parsed1); +} diff --git a/bindings/python/src/magnet_uri.cpp b/bindings/python/src/magnet_uri.cpp new file mode 100644 index 0000000..1cae4d5 --- /dev/null +++ b/bindings/python/src/magnet_uri.cpp @@ -0,0 +1,101 @@ +// Copyright Andrew Resch 2008. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt + +#include "boost_python.hpp" +#include "bytes.hpp" +#include +#include +#include +#include "gil.hpp" +#include "bytes.hpp" + +using namespace boost::python; +using namespace lt; + +extern void dict_to_add_torrent_params(dict params, add_torrent_params& p); + +namespace { + +#if TORRENT_ABI_VERSION == 1 + torrent_handle _add_magnet_uri(lt::session& s, std::string uri, dict params) + { + python_deprecated("add_magnet_uri() is deprecated"); + add_torrent_params p; + + dict_to_add_torrent_params(params, p); + + allow_threading_guard guard; + + p.url = uri; + +#ifndef BOOST_NO_EXCEPTIONS + return s.add_torrent(p); +#else + error_code ec; + return s.add_torrent(p, ec); +#endif + } +#endif + + dict parse_magnet_uri_dict(std::string const& uri) + { + error_code ec; + add_torrent_params p = parse_magnet_uri(uri, ec); + + if (ec) throw system_error(ec); + + dict ret; + + if (p.ti) ret["ti"] = p.ti; + list tracker_list; + for (std::vector::const_iterator i = p.trackers.begin() + , end(p.trackers.end()); i != end; ++i) + tracker_list.append(*i); + ret["trackers"] = tracker_list; + + list nodes_list; + for (auto const& i : p.dht_nodes) + nodes_list.append(boost::python::make_tuple(i.first, i.second)); + ret["dht_nodes"] = nodes_list; + if (p.info_hashes.has_v2()) + ret["info_hashes"] = bytes(p.info_hashes.v2.to_string()); + else + ret["info_hashes"] = bytes(p.info_hashes.v1.to_string()); +#if TORRENT_ABI_VERSION < 3 + ret["info_hash"] = bytes(p.info_hashes.get_best().to_string()); +#endif + ret["name"] = p.name; + ret["save_path"] = p.save_path; + ret["storage_mode"] = p.storage_mode; +#if TORRENT_ABI_VERSION == 1 + ret["url"] = p.url; +#endif + ret["flags"] = p.flags; + return ret; + } + + add_torrent_params parse_magnet_uri_wrap(std::string const& uri) + { + error_code ec; + add_torrent_params p = parse_magnet_uri(uri, ec); + if (ec) throw system_error(ec); + return p; + } + + std::string (*make_magnet_uri0)(torrent_handle const&) = make_magnet_uri; + std::string (*make_magnet_uri1)(torrent_info const&) = make_magnet_uri; + std::string (*make_magnet_uri2)(add_torrent_params const&) = make_magnet_uri; +} + +void bind_magnet_uri() +{ +#if TORRENT_ABI_VERSION == 1 + def("add_magnet_uri", &_add_magnet_uri); +#endif + def("make_magnet_uri", make_magnet_uri0); + def("make_magnet_uri", make_magnet_uri1); + def("make_magnet_uri", make_magnet_uri2); + def("parse_magnet_uri", parse_magnet_uri_wrap); + def("parse_magnet_uri_dict", parse_magnet_uri_dict); +} diff --git a/bindings/python/src/module.cpp b/bindings/python/src/module.cpp new file mode 100644 index 0000000..e0d3351 --- /dev/null +++ b/bindings/python/src/module.cpp @@ -0,0 +1,62 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifdef __GNUC__ +#define BOOST_PYTHON_USE_GCC_SYMBOL_VISIBILITY 1 +#endif + +#include +#include "libtorrent/config.hpp" + +void bind_utility(); +void bind_fingerprint(); +void bind_sha1_hash(); +void bind_sha256_hash(); +void bind_info_hash(); +void bind_session(); +void bind_entry(); +void bind_torrent_info(); +void bind_unicode_string_conversion(); +void bind_torrent_handle(); +void bind_torrent_status(); +void bind_session_settings(); +void bind_version(); +void bind_alert(); +void bind_datetime(); +void bind_peer_info(); +void bind_ip_filter(); +void bind_magnet_uri(); +void bind_converters(); +void bind_create_torrent(); +void bind_error_code(); +void bind_load_torrent(); + +BOOST_PYTHON_MODULE(libtorrent) +{ + Py_Initialize(); + PyEval_InitThreads(); + + bind_converters(); + bind_unicode_string_conversion(); + bind_error_code(); + bind_utility(); + bind_fingerprint(); + bind_sha1_hash(); + bind_sha256_hash(); + bind_info_hash(); + bind_entry(); + bind_torrent_handle(); + bind_session(); + bind_torrent_info(); + bind_torrent_status(); + bind_session_settings(); + bind_version(); + bind_alert(); + bind_datetime(); + bind_peer_info(); + bind_ip_filter(); + bind_magnet_uri(); + bind_create_torrent(); + bind_load_torrent(); +} diff --git a/bindings/python/src/optional.hpp b/bindings/python/src/optional.hpp new file mode 100644 index 0000000..2477924 --- /dev/null +++ b/bindings/python/src/optional.hpp @@ -0,0 +1,31 @@ +// Copyright Daniel Wallin 2007. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef OPTIONAL_070108_HPP +# define OPTIONAL_070108_HPP + +# include "boost_python.hpp" +# include + +template +struct optional_to_python +{ + optional_to_python() + { + boost::python::to_python_converter< + boost::optional, optional_to_python + >(); + } + + static PyObject* convert(boost::optional const& x) + { + if (!x) + return boost::python::incref(Py_None); + + return boost::python::incref(boost::python::object(*x).ptr()); + } +}; + +#endif // OPTIONAL_070108_HPP + diff --git a/bindings/python/src/peer_info.cpp b/bindings/python/src/peer_info.cpp new file mode 100644 index 0000000..a8bd773 --- /dev/null +++ b/bindings/python/src/peer_info.cpp @@ -0,0 +1,164 @@ +// Copyright Daniel Wallin 2007. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include "bytes.hpp" +#include +#include +#include + +using namespace boost::python; +using namespace lt; + +std::int64_t get_last_active(peer_info const& pi) +{ + return total_seconds(pi.last_active); +} + +std::int64_t get_last_request(peer_info const& pi) +{ + return total_seconds(pi.last_request); +} + +std::int64_t get_download_queue_time(peer_info const& pi) +{ + return total_seconds(pi.download_queue_time); +} + +tuple get_local_endpoint(peer_info const& pi) +{ + return boost::python::make_tuple(pi.local_endpoint.address().to_string(), pi.local_endpoint.port()); +} + +tuple get_ip(peer_info const& pi) +{ + return boost::python::make_tuple(pi.ip.address().to_string(), pi.ip.port()); +} + +list get_pieces(peer_info const& pi) +{ + list ret; + + for (bitfield::const_iterator i = pi.pieces.begin() + , end(pi.pieces.end()); i != end; ++i) + { + ret.append(*i); + } + return ret; +} + +bytes get_peer_info_client(peer_info const& pi) +{ + return pi.client; +} + +using by_value = return_value_policy; +void bind_peer_info() +{ + scope pi = class_("peer_info") + .add_property("flags", make_getter(&peer_info::flags, by_value())) + .add_property("source", make_getter(&peer_info::source, by_value())) + .add_property("read_state", make_getter(&peer_info::read_state, by_value())) + .add_property("write_state", make_getter(&peer_info::write_state, by_value())) + .add_property("ip", get_ip) + .def_readonly("up_speed", &peer_info::up_speed) + .def_readonly("down_speed", &peer_info::down_speed) + .def_readonly("payload_up_speed", &peer_info::payload_up_speed) + .def_readonly("payload_down_speed", &peer_info::payload_down_speed) + .def_readonly("total_download", &peer_info::total_download) + .def_readonly("total_upload", &peer_info::total_upload) + .def_readonly("pid", &peer_info::pid) + .add_property("pieces", get_pieces) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("upload_limit", &peer_info::upload_limit) + .def_readonly("download_limit", &peer_info::download_limit) + .def_readonly("load_balancing", &peer_info::load_balancing) + .def_readonly("remote_dl_rate", &peer_info::remote_dl_rate) +#endif + .add_property("last_request", get_last_request) + .add_property("last_active", get_last_active) + .add_property("download_queue_time", get_download_queue_time) + .def_readonly("queue_bytes", &peer_info::queue_bytes) + .def_readonly("request_timeout", &peer_info::request_timeout) + .def_readonly("send_buffer_size", &peer_info::send_buffer_size) + .def_readonly("used_send_buffer", &peer_info::used_send_buffer) + .def_readonly("receive_buffer_size", &peer_info::receive_buffer_size) + .def_readonly("used_receive_buffer", &peer_info::used_receive_buffer) + .def_readonly("num_hashfails", &peer_info::num_hashfails) + .def_readonly("download_queue_length", &peer_info::download_queue_length) + .def_readonly("upload_queue_length", &peer_info::upload_queue_length) + .def_readonly("failcount", &peer_info::failcount) + .add_property("downloading_piece_index", make_getter(&peer_info::downloading_piece_index, by_value())) + .add_property("downloading_block_index", make_getter(&peer_info::downloading_block_index, by_value())) + .def_readonly("downloading_progress", &peer_info::downloading_progress) + .def_readonly("downloading_total", &peer_info::downloading_total) + .add_property("client", get_peer_info_client) + .add_property("connection_type", make_getter(&peer_info::connection_type, by_value())) + .def_readonly("pending_disk_bytes", &peer_info::pending_disk_bytes) + .def_readonly("send_quota", &peer_info::send_quota) + .def_readonly("receive_quota", &peer_info::receive_quota) + .def_readonly("rtt", &peer_info::rtt) + .def_readonly("num_pieces", &peer_info::num_pieces) + .def_readonly("download_rate_peak", &peer_info::download_rate_peak) + .def_readonly("upload_rate_peak", &peer_info::upload_rate_peak) + .def_readonly("progress", &peer_info::progress) + .def_readonly("progress_ppm", &peer_info::progress_ppm) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("estimated_reciprocation_rate", &peer_info::estimated_reciprocation_rate) +#endif + .add_property("local_endpoint", get_local_endpoint) +#if TORRENT_USE_I2P + .def("i2p_destination", &peer_info::i2p_destination) +#endif + ; + + // flags + pi.attr("interesting") = peer_info::interesting; + pi.attr("choked") = peer_info::choked; + pi.attr("remote_interested") = peer_info::remote_interested; + pi.attr("remote_choked") = peer_info::remote_choked; + pi.attr("supports_extensions") = peer_info::supports_extensions; + pi.attr("local_connection") = peer_info::local_connection; + pi.attr("outgoing_connection") = peer_info::outgoing_connection; + pi.attr("handshake") = peer_info::handshake; + pi.attr("connecting") = peer_info::connecting; + pi.attr("i2p_socket") = peer_info::i2p_socket; +#if TORRENT_ABI_VERSION == 1 + pi.attr("queued") = peer_info::queued; +#endif + pi.attr("on_parole") = peer_info::on_parole; + pi.attr("seed") = peer_info::seed; + pi.attr("optimistic_unchoke") = peer_info::optimistic_unchoke; + pi.attr("snubbed") = peer_info::snubbed; + pi.attr("upload_only") = peer_info::upload_only; + pi.attr("endgame_mode") = peer_info::endgame_mode; + pi.attr("holepunched") = peer_info::holepunched; +#ifndef TORRENT_DISABLE_ENCRYPTION + pi.attr("rc4_encrypted") = peer_info::rc4_encrypted; + pi.attr("plaintext_encrypted") = peer_info::plaintext_encrypted; +#endif + + // connection_type + pi.attr("standard_bittorrent") = peer_info::standard_bittorrent; + pi.attr("web_seed") = peer_info::web_seed; + pi.attr("http_seed") = peer_info::http_seed; + + // source + pi.attr("tracker") = peer_info::tracker; + pi.attr("dht") = peer_info::dht; + pi.attr("pex") = peer_info::pex; + pi.attr("lsd") = peer_info::lsd; + pi.attr("resume_data") = peer_info::resume_data; + + // read/write state + pi.attr("bw_idle") = peer_info::bw_idle; +#if TORRENT_ABI_VERSION == 1 + pi.attr("bw_torrent") = peer_info::bw_torrent; + pi.attr("bw_global") = peer_info::bw_global; +#endif + pi.attr("bw_limit") = peer_info::bw_limit; + pi.attr("bw_network") = peer_info::bw_network; + pi.attr("bw_disk") = peer_info::bw_disk; +} + diff --git a/bindings/python/src/session.cpp b/bindings/python/src/session.cpp new file mode 100644 index 0000000..02a6fcb --- /dev/null +++ b/bindings/python/src/session.cpp @@ -0,0 +1,1377 @@ +// Copyright Daniel Wallin, Arvid Norberg 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for sign_mutable_item +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace boost +{ + // this fixes mysterious link error on msvc + template <> + inline lt::alert const volatile* + get_pointer(lt::alert const volatile* p) + { + return p; + } +} + +#include "gil.hpp" +#include "bytes.hpp" + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4996: X: was declared deprecated +#pragma warning( disable : 4996 ) +#endif + +using namespace boost::python; +using namespace lt; + +// defined in torrent_info.cpp +load_torrent_limits dict_to_limits(dict limits); + +namespace +{ +#if TORRENT_ABI_VERSION == 1 + struct dummy {}; + + void listen_on(lt::session& s, int min_, int max_, char const* interface, int flags) + { + allow_threading_guard guard; + error_code ec; + s.listen_on(std::make_pair(min_, max_), ec, interface, flags); +#ifndef BOOST_NO_EXCEPTIONS + if (ec) throw system_error(ec); +#endif + } + + void outgoing_ports(lt::session& s, int _min, int _max) + { + allow_threading_guard guard; + settings_pack p; + p.set_int(settings_pack::outgoing_port, _min); + p.set_int(settings_pack::num_outgoing_ports, _max - _min); + s.apply_settings(p); + return; + } +#endif // TORRENT_ABI_VERSION + +#ifndef TORRENT_DISABLE_DHT + void add_dht_node(lt::session& s, tuple n) + { + std::string ip = extract(n[0]); + int port = extract(n[1]); + allow_threading_guard guard; + s.add_dht_node(std::make_pair(ip, port)); + } + +#if TORRENT_ABI_VERSION == 1 + void add_dht_router(lt::session& s, std::string router_, int port_) + { + allow_threading_guard guard; + return s.add_dht_router(std::make_pair(router_, port_)); + } +#endif + +#endif // TORRENT_DISABLE_DHT + + void add_extension(lt::session& s, object const& e) + { +#ifndef TORRENT_DISABLE_EXTENSIONS + if (!extract(e).check()) return; + + std::string name = extract(e); + if (name == "ut_metadata") + s.add_extension(create_ut_metadata_plugin); + else if (name == "ut_pex") + s.add_extension(create_ut_pex_plugin); + else if (name == "smart_ban") + s.add_extension(create_smart_ban_plugin); + +#endif // TORRENT_DISABLE_EXTENSIONS + } + + void make_settings_pack(lt::settings_pack& p, dict const& sett_dict) + { + stl_input_iterator i(sett_dict.keys()), end; + for (; i != end; ++i) + { + std::string const key = *i; + + int sett = setting_by_name(key); + if (sett < 0) + { + PyErr_SetString(PyExc_KeyError, ("unknown name in settings_pack: " + key).c_str()); + throw_error_already_set(); + } + + try + { + // if the dictionary doesn't contain "key", it will throw, hence + // the try-catch here + object const value = sett_dict[key]; + switch (sett & settings_pack::type_mask) + { + case settings_pack::string_type_base: + p.set_str(sett, extract(value)); + break; + case settings_pack::int_type_base: + { + std::int64_t const val = extract(value); + // deliberately truncate and sign-convert here. If we + // extract an int directly, unsigned ints may throw + // an exception otherwise, if it doesn't fit. Notably for a + // flag-type with all bits set. + p.set_int(sett, static_cast(val)); + break; + } + case settings_pack::bool_type_base: + p.set_bool(sett, extract(value)); + break; + } + } + catch (...) {} + } + } + + dict make_dict(lt::settings_pack const& sett) + { + dict ret; + for (int i = settings_pack::string_type_base; + i < settings_pack::max_string_setting_internal; ++i) + { + // deprecated settings are still here, they just have empty names + char const* name = name_for_setting(i); + if (name[0] != '\0' && sett.has_val(i)) ret[name] = sett.get_str(i); + } + + for (int i = settings_pack::int_type_base; + i < settings_pack::max_int_setting_internal; ++i) + { + char const* name = name_for_setting(i); + if (name[0] != '\0' && sett.has_val(i)) ret[name] = sett.get_int(i); + } + + for (int i = settings_pack::bool_type_base; + i < settings_pack::max_bool_setting_internal; ++i) + { + char const* name = name_for_setting(i); + if (name[0] != '\0' && sett.has_val(i)) ret[name] = sett.get_bool(i); + } + return ret; + } + + std::shared_ptr make_session(boost::python::dict sett + , session_flags_t const flags) + { + settings_pack p; + make_settings_pack(p, sett); +#if TORRENT_ABI_VERSION <= 2 + if (flags & lt::session::add_default_plugins) + { + // TODO: this can't really be removed until there is a way to + // control plugins by exposing session_params. + // The simplest solution would probably be to make the default + // plugins not be plugins, but just bake in support for them +// python_deprecated("add_default_plugins flag is deprecated"); +#endif + session_params params(std::move(p)); + return std::make_shared(std::move(params), flags); +#if TORRENT_ABI_VERSION <= 2 + } + else + { + session_params params(std::move(p), {}); + return std::make_shared(std::move(params), flags); + } +#endif + } + + void session_apply_settings(lt::session& ses, dict const& sett_dict) + { + settings_pack p; + make_settings_pack(p, sett_dict); + allow_threading_guard guard; + ses.apply_settings(p); + } + + dict session_get_settings(lt::session const& ses) + { + settings_pack sett; + { + allow_threading_guard guard; + sett = ses.get_settings(); + } + return make_dict(sett); + } + + dict min_memory_usage_wrapper() + { + settings_pack ret = min_memory_usage(); + return make_dict(ret); + } + + dict default_settings_wrapper() + { + return make_dict(default_settings()); + } + + dict high_performance_seed_wrapper() + { + settings_pack ret = high_performance_seed(); + return make_dict(ret); + } + +#ifndef BOOST_NO_EXCEPTIONS +#if TORRENT_ABI_VERSION == 1 + torrent_handle add_torrent_depr(lt::session& s, torrent_info const& ti + , std::string const& save, entry const& resume + , storage_mode_t storage_mode, bool paused) + { + allow_threading_guard guard; + return s.add_torrent(ti, save, resume, storage_mode, paused); + } +#endif +#endif +} + + void dict_to_add_torrent_params(dict params, add_torrent_params& p) + { + list items = params.items(); + int const len = int(boost::python::len(items)); + for (int i = 0; i < len; i++) + { + boost::python::api::object_item item = items[i]; + std::string const key = extract(item[0]); + object const value = item[1]; + // torrent_info objects are always held by a shared_ptr in the + // python binding, skip it if it is a object + if (key == "ti" && value != boost::python::object()) + { + // make a copy here. We don't want to end up holding a python-owned + // object inside libtorrent. If the last reference goes out of scope + // on the C++ side, it will end up freeing the python object + // without holding the GIL and likely crash. + // https://mail.python.org/pipermail/cplusplus-sig/2007-June/012130.html + p.ti = std::make_shared( + extract(value)); + continue; + } +#if TORRENT_ABI_VERSION < 3 + else if (key == "info_hash") + { + if (boost::python::len(value) == sha1_hash::size()) + { + p.info_hash = sha1_hash(bytes(extract(value)).arr.data()); + } + } +#endif + else if (key == "info_hashes") + { + if (boost::python::len(value) == sha1_hash::size()) + { + p.info_hashes = info_hash_t(sha1_hash( + bytes(extract(value)).arr.data())); + } + else if (boost::python::len(value) == sha256_hash::size()) + { + p.info_hashes = info_hash_t(sha256_hash( + bytes(extract(value)).arr.data())); + } + else + { + p.info_hashes = boost::python::extract(value); + } + continue; + } + else if(key == "name") + { + p.name = extract(value); + continue; + } + else if(key == "save_path") + { + p.save_path = extract(value); + continue; + } +#if TORRENT_ABI_VERSION == 1 + else if(key == "resume_data") + { + python_deprecated("the resume_data member is deprecated"); + std::string resume = extract(value); + p.resume_data.assign(resume.begin(), resume.end()); + continue; + } +#endif + else if(key == "storage_mode") + { + p.storage_mode = extract(value); + continue; + } + else if(key == "trackers") + { + p.trackers = extract>(value); + continue; + } + else if(key == "url_seeds") + { + p.url_seeds = extract>(value); + continue; + } + else if(key == "http_seeds") + { + p.http_seeds = + extract(value); + continue; + } + else if(key == "dht_nodes") + { + p.dht_nodes = + extract>>(value); + continue; + } + else if(key == "banned_peers") + { + p.banned_peers = extract>(value); + continue; + } + else if(key == "peers") + { + p.peers = extract>(value); + continue; + } + else if(key == "flags") + { + p.flags = extract(value); + continue; + } + else if(key == "trackerid") + { + p.trackerid = extract(value); + continue; + } +#if TORRENT_ABI_VERSION == 1 + else if(key == "url") + { + python_deprecated("the url member is deprecated"); + p.url = extract(value); + continue; + } +#endif + else if(key == "renamed_files") + { + p.renamed_files = + extract>(value); + } + else if(key == "file_priorities") + { + p.file_priorities = extract>(value); + } + else + { + PyErr_SetString(PyExc_KeyError, + ("unknown name in torrent params: " + key).c_str()); + throw_error_already_set(); + } + } + } + +namespace +{ + + torrent_handle add_torrent(lt::session& s, dict params) + { + add_torrent_params p; + dict_to_add_torrent_params(params, p); + + if (p.save_path.empty()) + { + PyErr_SetString(PyExc_KeyError, + "save_path must be set in add_torrent_params"); + throw_error_already_set(); + } + + allow_threading_guard guard; + + return s.add_torrent(std::move(p)); + } + + void async_add_torrent(lt::session& s, dict params) + { + add_torrent_params p; + dict_to_add_torrent_params(params, p); + + if (p.save_path.empty()) + { + PyErr_SetString(PyExc_KeyError, + "save_path must be set in add_torrent_params"); + throw_error_already_set(); + } + + allow_threading_guard guard; + + s.async_add_torrent(std::move(p)); + } + + torrent_handle wrap_add_torrent(lt::session& s, lt::add_torrent_params const& p) + { + add_torrent_params atp = p; + if (p.ti) + atp.ti = std::make_shared(*p.ti); + + if (p.save_path.empty()) + { + PyErr_SetString(PyExc_KeyError, + "save_path must be set in add_torrent_params"); + throw_error_already_set(); + } + + allow_threading_guard guard; + + return s.add_torrent(std::move(p)); + } + + void wrap_async_add_torrent(lt::session& s, lt::add_torrent_params const& p) + { + add_torrent_params atp = p; + if (p.ti) + atp.ti = std::make_shared(*p.ti); + + if (p.save_path.empty()) + { + PyErr_SetString(PyExc_ValueError, + "save_path must be set in add_torrent_params"); + throw_error_already_set(); + } + + allow_threading_guard guard; + + s.async_add_torrent(std::move(p)); + } + +#if TORRENT_ABI_VERSION == 1 + void start_natpmp(lt::session& s) + { + allow_threading_guard guard; + s.start_natpmp(); + } + + void start_upnp(lt::session& s) + { + allow_threading_guard guard; + s.start_upnp(); + } +#endif // TORRENT_ABI_VERSION + + void alert_notify(object cb) try + { + lock_gil lock; + if (cb) + { + cb(); + } + } + catch (boost::python::error_already_set const&) + { + // this callback isn't supposed to throw an error. + // just swallow and ignore the exception + TORRENT_ASSERT_FAIL_VAL("python notify callback threw exception"); + } + + void set_alert_notify(lt::session& s, object cb) + { + s.set_alert_notify(std::bind(&alert_notify, cb)); + } + +#ifdef TORRENT_WINDOWS + void alert_socket_notify(SOCKET const fd) + { + std::uint8_t dummy = 0; + ::send(fd, reinterpret_cast(&dummy), 1, 0); + } +#endif + + void alert_fd_notify(int const fd) + { + std::uint8_t dummy = 0; + while (::write(fd, &dummy, 1) < 0 && errno == EINTR); + } + + void set_alert_fd(lt::session& s, std::intptr_t const fd) + { +#ifdef TORRENT_WINDOWS + auto const sock = static_cast(fd); + int res; + int res_size = sizeof(res); + if (sock != INVALID_SOCKET + && ::getsockopt(sock, SOL_SOCKET, SO_ERROR, + (char *)&res, &res_size) == 0) + { + s.set_alert_notify(std::bind(&alert_socket_notify, sock)); + } + else +#endif + { + s.set_alert_notify(std::bind(&alert_fd_notify, fd)); + } + } + + alert const* + wait_for_alert(lt::session& s, int ms) + { + alert const* a; + { + allow_threading_guard guard; + a = s.wait_for_alert(milliseconds(ms)); + } + return a; + } + + list get_torrents(lt::session& s) + { + std::vector torrents; + { + allow_threading_guard guard; + torrents = s.get_torrents(); + } + + list ret; + for (std::vector::iterator i = torrents.begin(); i != torrents.end(); ++i) + ret.append(*i); + return ret; + } + + bool wrap_pred(object pred, torrent_status const& st) + { + return pred(st); + } + + list get_torrent_status(lt::session& s, object pred, int const flags) + { + // keep a reference to the predicate here, in the python thread, to + // ensure it's freed in this thread at the end. If we move it into the + // libtorrent thread the python predicate will be freed from that + // thread, which won't work + auto wrapped_pred = std::bind(&wrap_pred, pred, std::placeholders::_1); + std::vector torrents + = s.get_torrent_status(std::ref(wrapped_pred), status_flags_t(flags)); + + list ret; + for (std::vector::iterator i = torrents.begin(); i != torrents.end(); ++i) + ret.append(*i); + return ret; + } + + list refresh_torrent_status(lt::session& s, list in_torrents, int const flags) + { + std::vector torrents; + int const n = int(boost::python::len(in_torrents)); + for (int i = 0; i < n; ++i) + torrents.push_back(extract(in_torrents[i])); + + { + allow_threading_guard guard; + s.refresh_torrent_status(&torrents, status_flags_t(flags)); + } + + list ret; + for (std::vector::iterator i = torrents.begin(); i != torrents.end(); ++i) + ret.append(*i); + return ret; + } + +#if TORRENT_ABI_VERSION == 1 + dict get_utp_stats(session_status const& st) + { + python_deprecated("session_status is deprecated"); + dict ret; + ret["num_idle"] = st.utp_stats.num_idle; + ret["num_syn_sent"] = st.utp_stats.num_syn_sent; + ret["num_connected"] = st.utp_stats.num_connected; + ret["num_fin_sent"] = st.utp_stats.num_fin_sent; + ret["num_close_wait"] = st.utp_stats.num_close_wait; + return ret; + } +#endif + + entry save_state(lt::session const& s, std::uint32_t const flags) + { + entry e; +#if TORRENT_ABI_VERSION <= 2 + allow_threading_guard guard; + s.save_state(e, save_state_flags_t(flags)); +#endif + return e; + } + + list pop_alerts(lt::session& ses) + { + std::vector alerts; + { + allow_threading_guard guard; + ses.pop_alerts(&alerts); + } + + list ret; + for (alert* a : alerts) + { + ret.append(boost::python::ptr(a)); + } + return ret; + } + + void load_state(lt::session& ses, entry const& st, std::uint32_t const flags) + { +#if TORRENT_ABI_VERSION <= 2 + allow_threading_guard guard; + + std::vector buf; + bencode(std::back_inserter(buf), st); + bdecode_node e; + error_code ec; + bdecode(&buf[0], &buf[0] + buf.size(), e, ec); + TORRENT_ASSERT(!ec); + ses.load_state(e, save_state_flags_t(flags)); +#endif + } + + dict get_peer_class(lt::session& ses, lt::peer_class_t const pc) + { + lt::peer_class_info pci; + { + allow_threading_guard guard; + pci = ses.get_peer_class(pc); + } + dict ret; + ret["ignore_unchoke_slots"] = pci.ignore_unchoke_slots; + ret["connection_limit_factor"] = pci.connection_limit_factor; + ret["label"] = pci.label; + ret["upload_limit"] = pci.upload_limit; + ret["download_limit"] = pci.download_limit; + ret["upload_priority"] = pci.upload_priority; + ret["download_priority"] = pci.download_priority; + return ret; + } + + void set_peer_class(lt::session& ses, peer_class_t const pc, dict info) + { + lt::peer_class_info pci; + stl_input_iterator i(info.keys()), end; + for (; i != end; ++i) + { + std::string const key = *i; + + object const value = info[key]; + if (key == "ignore_unchoke_slots") + { + pci.ignore_unchoke_slots = extract(value); + } + else if (key == "connection_limit_factor") + { + pci.connection_limit_factor = extract(value); + } + else if (key == "label") + { + pci.label = extract(value); + } + else if (key == "upload_limit") + { + pci.upload_limit = extract(value); + } + else if (key == "download_limit") + { + pci.download_limit = extract(value); + } + else if (key == "upload_priority") + { + pci.upload_priority = extract(value); + } + else if (key == "download_priority") + { + pci.download_priority = extract(value); + } + else + { + PyErr_SetString(PyExc_KeyError, ("unknown name in peer_class_info: " + key).c_str()); + throw_error_already_set(); + } + } + + allow_threading_guard guard; + ses.set_peer_class(pc, pci); + } + +#ifndef TORRENT_DISABLE_DHT + void dht_get_mutable_item(lt::session& ses, std::string key, std::string salt) + { + TORRENT_ASSERT(key.size() == 32); + std::array public_key; + std::copy(key.begin(), key.end(), public_key.begin()); + ses.dht_get_item(public_key, salt); + } + + void put_string(entry& e, std::array& sig, std::int64_t& seq + , std::string const& salt, std::string pk, std::string sk + , std::string data) + { + using lt::dht::sign_mutable_item; + + e = data; + std::vector buf; + bencode(std::back_inserter(buf), e); + ++seq; + dht::signature sign = sign_mutable_item(buf, salt + , dht::sequence_number(seq) + , dht::public_key(pk.data()) + , dht::secret_key(sk.data())); + sig = sign.bytes; + } + + void dht_put_mutable_item(lt::session& ses, std::string private_key, std::string public_key, + std::string data, std::string salt) + { + TORRENT_ASSERT(private_key.size() == 64); + TORRENT_ASSERT(public_key.size() == 32); + std::array key; + std::copy(public_key.begin(), public_key.end(), key.begin()); + ses.dht_put_item(key + , [pk=std::move(public_key), sk=std::move(private_key), d=std::move(data)] + (entry& e, std::array& sig, std::int64_t& seq, std::string const& salt) + { + put_string(e, sig, seq, salt, pk, sk, d); + } + , salt); + } +#endif + + add_torrent_params read_resume_data_wrapper0(bytes const& b) + { + return read_resume_data(b.arr); + } + + add_torrent_params read_resume_data_wrapper1(bytes const& b, dict cfg) + { + return read_resume_data(b.arr, dict_to_limits(cfg)); + } + + int find_metric_idx_wrap(char const* name) + { + return lt::find_metric_idx(name); + } + + bytes write_resume_data_buf_(add_torrent_params const& atp) + { + bytes ret; + auto buf = write_resume_data_buf(atp); + ret.arr.resize(buf.size()); + std::copy(buf.begin(), buf.end(), ret.arr.begin()); + return ret; + } + + session_params read_session_params_entry(dict e + , save_state_flags_t flags) + { + entry ent = extract(e); + std::vector buf; + bencode(std::back_inserter(buf), ent); + return lt::read_session_params(buf, flags); + } + + session_params read_session_params_buffer(bytes const& bytes + , save_state_flags_t flags) + { + return lt::read_session_params(bytes.arr, flags); + } + + bytes write_session_params_bytes(session_params const& sp + , save_state_flags_t flags) + { + auto buf = write_session_params_buf(sp, flags); + return bytes(buf.data(), buf.size()); + } + + struct dict_to_settings + { + dict_to_settings() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + return PyDict_Check(x) ? x: nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + + dict o(borrowed(x)); + auto p = new (storage) lt::settings_pack; + data->convertible = p; + make_settings_pack(*p, o); + } + }; + + struct settings_to_dict + { + static PyObject* convert(lt::settings_pack const& p) + { + dict ret = make_dict(p); + return incref(ret.ptr()); + } + }; + +} // anonymous namespace + +struct dummy1 {}; +#if TORRENT_ABI_VERSION == 1 +struct dummy2 {}; +#endif +struct dummy9 {}; +struct dummy10 {}; +struct dummy11 {}; +struct dummy17 {}; + +void bind_session() +{ + dict_to_settings(); + to_python_converter(); + +#ifndef TORRENT_DISABLE_DHT + void (lt::session::*dht_get_immutable_item)(sha1_hash const&) = <::session::dht_get_item; + sha1_hash (lt::session::*dht_put_immutable_item)(entry data) = <::session::dht_put_item; +#endif // TORRENT_DISABLE_DHT + +#if TORRENT_ABI_VERSION == 1 +#ifndef TORRENT_DISABLE_DHT + void (lt::session::*start_dht0)() = <::session::start_dht; + void (lt::session::*start_dht1)(entry const&) = <::session::start_dht; +#endif + + class_("session_status") + .def_readonly("has_incoming_connections", &session_status::has_incoming_connections) + + .def_readonly("upload_rate", &session_status::upload_rate) + .def_readonly("download_rate", &session_status::download_rate) + .def_readonly("total_download", &session_status::total_download) + .def_readonly("total_upload", &session_status::total_upload) + + .def_readonly("payload_upload_rate", &session_status::payload_upload_rate) + .def_readonly("payload_download_rate", &session_status::payload_download_rate) + .def_readonly("total_payload_download", &session_status::total_payload_download) + .def_readonly("total_payload_upload", &session_status::total_payload_upload) + + .def_readonly("ip_overhead_upload_rate", &session_status::ip_overhead_upload_rate) + .def_readonly("ip_overhead_download_rate", &session_status::ip_overhead_download_rate) + .def_readonly("total_ip_overhead_download", &session_status::total_ip_overhead_download) + .def_readonly("total_ip_overhead_upload", &session_status::total_ip_overhead_upload) + + .def_readonly("dht_upload_rate", &session_status::dht_upload_rate) + .def_readonly("dht_download_rate", &session_status::dht_download_rate) + .def_readonly("total_dht_download", &session_status::total_dht_download) + .def_readonly("total_dht_upload", &session_status::total_dht_upload) + + .def_readonly("tracker_upload_rate", &session_status::tracker_upload_rate) + .def_readonly("tracker_download_rate", &session_status::tracker_download_rate) + .def_readonly("total_tracker_download", &session_status::total_tracker_download) + .def_readonly("total_tracker_upload", &session_status::total_tracker_upload) + + .def_readonly("total_redundant_bytes", &session_status::total_redundant_bytes) + .def_readonly("total_failed_bytes", &session_status::total_failed_bytes) + + .def_readonly("num_peers", &session_status::num_peers) + .def_readonly("num_unchoked", &session_status::num_unchoked) + .def_readonly("allowed_upload_slots", &session_status::allowed_upload_slots) + + .def_readonly("up_bandwidth_queue", &session_status::up_bandwidth_queue) + .def_readonly("down_bandwidth_queue", &session_status::down_bandwidth_queue) + + .def_readonly("up_bandwidth_bytes_queue", &session_status::up_bandwidth_bytes_queue) + .def_readonly("down_bandwidth_bytes_queue", &session_status::down_bandwidth_bytes_queue) + + .def_readonly("optimistic_unchoke_counter", &session_status::optimistic_unchoke_counter) + .def_readonly("unchoke_counter", &session_status::unchoke_counter) + +#ifndef TORRENT_DISABLE_DHT + .def_readonly("dht_nodes", &session_status::dht_nodes) + .def_readonly("dht_node_cache", &session_status::dht_node_cache) + .def_readonly("dht_torrents", &session_status::dht_torrents) + .def_readonly("dht_global_nodes", &session_status::dht_global_nodes) + .add_property("active_requests", make_getter(&session_status::active_requests, return_value_policy())) + .def_readonly("dht_total_allocations", &session_status::dht_total_allocations) +#endif // TORRENT_DISABLE_DHT + .add_property("utp_stats", &get_utp_stats) + ; + +#ifndef TORRENT_DISABLE_DHT + class_("dht_lookup") + .def_readonly("type", &dht_lookup::type) + .def_readonly("outstanding_requests", &dht_lookup::outstanding_requests) + .def_readonly("timeouts", &dht_lookup::timeouts) + .def_readonly("response", &dht_lookup::responses) + .def_readonly("branch_factor", &dht_lookup::branch_factor) + ; +#endif // TORRENT_DISABLE_DHT +#endif // TORRENT_ABI_VERSION + +#define PROP(val) \ + make_getter(val, return_value_policy()), \ + make_setter(val, return_value_policy()) + + class_("add_torrent_params") + .def_readwrite("version", &add_torrent_params::version) + .def_readwrite("ti", &add_torrent_params::ti) + .add_property("trackers", PROP(&add_torrent_params::trackers)) + .add_property("tracker_tiers", PROP(&add_torrent_params::tracker_tiers)) + .add_property("dht_nodes", PROP(&add_torrent_params::dht_nodes)) + .def_readwrite("name", &add_torrent_params::name) + .def_readwrite("save_path", &add_torrent_params::save_path) + .def_readwrite("storage_mode", &add_torrent_params::storage_mode) +// .def_readwrite("storage", &add_torrent_params::storage) + .add_property("file_priorities", PROP(&add_torrent_params::file_priorities)) + .def_readwrite("trackerid", &add_torrent_params::trackerid) + .add_property("flags", PROP(&add_torrent_params::flags)) + .def_readwrite("max_uploads", &add_torrent_params::max_uploads) + .def_readwrite("max_connections", &add_torrent_params::max_connections) + .def_readwrite("upload_limit", &add_torrent_params::upload_limit) + .def_readwrite("download_limit", &add_torrent_params::download_limit) + .def_readwrite("total_uploaded", &add_torrent_params::total_uploaded) + .def_readwrite("total_downloaded", &add_torrent_params::total_downloaded) + .def_readwrite("active_time", &add_torrent_params::active_time) + .def_readwrite("finished_time", &add_torrent_params::finished_time) + .def_readwrite("seeding_time", &add_torrent_params::seeding_time) + .def_readwrite("added_time", &add_torrent_params::added_time) + .def_readwrite("completed_time", &add_torrent_params::completed_time) + .def_readwrite("last_seen_complete", &add_torrent_params::last_seen_complete) + .def_readwrite("last_download", &add_torrent_params::last_download) + .def_readwrite("last_upload", &add_torrent_params::last_upload) + .def_readwrite("num_complete", &add_torrent_params::num_complete) + .def_readwrite("num_incomplete", &add_torrent_params::num_incomplete) + .def_readwrite("num_downloaded", &add_torrent_params::num_downloaded) +#if TORRENT_ABI_VERSION < 3 + .def_readwrite("info_hash", &add_torrent_params::info_hash) +#endif + .def_readwrite("info_hashes", &add_torrent_params::info_hashes) + .add_property("http_seeds", PROP(&add_torrent_params::http_seeds)) + .add_property("url_seeds", PROP(&add_torrent_params::url_seeds)) + .add_property("peers", PROP(&add_torrent_params::peers)) + .add_property("banned_peers", PROP(&add_torrent_params::banned_peers)) + .add_property("unfinished_pieces", PROP(&add_torrent_params::unfinished_pieces)) + .add_property("have_pieces", PROP(&add_torrent_params::have_pieces)) + .add_property("verified_pieces", PROP(&add_torrent_params::verified_pieces)) + .add_property("piece_priorities", PROP(&add_torrent_params::piece_priorities)) +#if TORRENT_ABI_VERSION <= 2 + .add_property("merkle_tree", PROP(&add_torrent_params::merkle_tree)) +#endif + .add_property("renamed_files", PROP(&add_torrent_params::renamed_files)) + +#if TORRENT_ABI_VERSION == 1 + .def_readwrite("url", &add_torrent_params::url) + .add_property("resume_data", PROP(&add_torrent_params::resume_data)) +#endif + ; + +#ifndef TORRENT_DISABLE_DHT + class_("dht_state") + .add_property("nids", <::dht::dht_state::nids) + .add_property("nodes", <::dht::dht_state::nodes) + .add_property("nodes6", <::dht::dht_state::nodes6) + ; +#endif + + class_("session_params") + .def(init()) + .def(init<>()) + // TODO: since there's not binding for settings_pack, but they are just + // represented as dicts, this won't return a reference, but a copy of + // the settings + .add_property("settings", PROP(&session_params::settings)) +#ifndef TORRENT_DISABLE_DHT + .def_readwrite("dht_state", &session_params::dht_state) +#endif + .add_property("ext_state", PROP(&session_params::ext_state)) + .def_readwrite("ip_filter", &session_params::ip_filter) + ; + + def("read_session_params", &read_session_params_entry, (arg("dict"), arg("flags")=save_state_flags_t::all())); + def("read_session_params", &read_session_params_buffer, (arg("buffer"), arg("flags")=save_state_flags_t::all())); + def("write_session_params", <::write_session_params, (arg("entry"), arg("flags")=save_state_flags_t::all())); + def("write_session_params_buf", &write_session_params_bytes, (arg("buffer"), arg("flags")=save_state_flags_t::all())); + + enum_("storage_mode_t") + .value("storage_mode_allocate", storage_mode_allocate) + .value("storage_mode_sparse", storage_mode_sparse) + ; + + { + scope s = class_("options_t"); + s.attr("delete_files") = lt::session::delete_files; + } + + { + scope s = class_("session_flags_t"); + s.attr("paused") = lt::session::paused; +#if TORRENT_ABI_VERSION <= 2 + s.attr("add_default_plugins") = lt::session::add_default_plugins; +#endif +#if TORRENT_ABI_VERSION == 1 + s.attr("start_default_features") = lt::session::start_default_features; +#endif + } + + { + scope s = class_("torrent_flags"); + s.attr("seed_mode") = torrent_flags::seed_mode; + s.attr("upload_mode") = torrent_flags::upload_mode; + s.attr("share_mode") = torrent_flags::share_mode; + s.attr("apply_ip_filter") = torrent_flags::apply_ip_filter; + s.attr("paused") = torrent_flags::paused; + s.attr("auto_managed") = torrent_flags::auto_managed; + s.attr("duplicate_is_error") = torrent_flags::duplicate_is_error; + s.attr("update_subscribe") = torrent_flags::update_subscribe; + s.attr("super_seeding") = torrent_flags::super_seeding; + s.attr("sequential_download") = torrent_flags::sequential_download; + s.attr("stop_when_ready") = torrent_flags::stop_when_ready; + s.attr("override_trackers") = torrent_flags::override_trackers; + s.attr("override_web_seeds") = torrent_flags::override_web_seeds; + s.attr("disable_dht") = torrent_flags::disable_dht; + s.attr("disable_lsd") = torrent_flags::disable_lsd; + s.attr("disable_pex") = torrent_flags::disable_pex; + s.attr("no_verify_files") = torrent_flags::no_verify_files; + s.attr("default_dont_download") = torrent_flags::default_dont_download; + s.attr("default_flags") = torrent_flags::default_flags; + } + +#if TORRENT_ABI_VERSION == 1 + { + scope s = class_("add_torrent_params_flags_t"); + s.attr("flag_seed_mode") = add_torrent_params::flag_seed_mode; + s.attr("flag_upload_mode") = add_torrent_params::flag_upload_mode; + s.attr("flag_share_mode") = add_torrent_params::flag_share_mode; + s.attr("flag_apply_ip_filter") = add_torrent_params::flag_apply_ip_filter; + s.attr("flag_paused") = add_torrent_params::flag_paused; + s.attr("flag_auto_managed") = add_torrent_params::flag_auto_managed; + s.attr("flag_duplicate_is_error") = add_torrent_params::flag_duplicate_is_error; + s.attr("flag_update_subscribe") = add_torrent_params::flag_update_subscribe; + s.attr("flag_super_seeding") = add_torrent_params::flag_super_seeding; + s.attr("flag_sequential_download") = add_torrent_params::flag_sequential_download; + s.attr("flag_stop_when_ready") = add_torrent_params::flag_stop_when_ready; + s.attr("flag_override_trackers") = add_torrent_params::flag_override_trackers; + s.attr("flag_override_web_seeds") = add_torrent_params::flag_override_web_seeds; + s.attr("flag_pinned") = add_torrent_params::flag_pinned; + s.attr("flag_override_resume_data") = add_torrent_params::flag_override_resume_data; + s.attr("flag_merge_resume_trackers") = add_torrent_params::flag_merge_resume_trackers; + s.attr("flag_use_resume_save_path") = add_torrent_params::flag_use_resume_save_path; + s.attr("flag_merge_resume_http_seeds") = add_torrent_params::flag_merge_resume_http_seeds; + s.attr("default_flags") = add_torrent_params::flag_default_flags; + } +#endif + ; + + enum_("portmap_protocol") + .value("none", lt::portmap_protocol::none) + .value("udp", lt::portmap_protocol::udp) + .value("tcp", lt::portmap_protocol::tcp) + ; + + enum_("portmap_transport") + .value("natpmp", lt::portmap_transport::natpmp) + .value("upnp", lt::portmap_transport::upnp) + ; + + enum_("peer_class_type_filter_socket_type_t") + .value("tcp_socket", peer_class_type_filter::tcp_socket) + .value("utp_socket", peer_class_type_filter::utp_socket) + .value("ssl_tcp_socket", peer_class_type_filter::ssl_tcp_socket) + .value("ssl_utp_socket", peer_class_type_filter::ssl_utp_socket) + .value("i2p_socket", peer_class_type_filter::i2p_socket) + ; + + { + scope s = class_("peer_class_type_filter") + .def(init<>()) + .def("add", <::peer_class_type_filter::add) + .def("remove", <::peer_class_type_filter::remove) + .def("disallow", <::peer_class_type_filter::disallow) + .def("allow", <::peer_class_type_filter::allow) + .def("apply", <::peer_class_type_filter::apply) + ; + s.attr("tcp_socket") = peer_class_type_filter::tcp_socket; + s.attr("utp_socket") = peer_class_type_filter::utp_socket; + s.attr("ssl_tcp_socket") = peer_class_type_filter::ssl_tcp_socket; + s.attr("ssl_utp_socket") = peer_class_type_filter::ssl_utp_socket; + s.attr("i2p_socket") = peer_class_type_filter::i2p_socket; + } + + { + scope s = class_("session", no_init) + .def(init()) + .def(init<>()) + .def("__init__", boost::python::make_constructor(&make_session + , default_call_policies() + , (arg("settings"), arg("flags")= +#if TORRENT_ABI_VERSION <= 2 + lt::session::add_default_plugins +#else + lt::session_flags_t{} +#endif + )) + ) +#if TORRENT_ABI_VERSION == 1 + .def( + init(( + arg("fingerprint")=fingerprint("LT",0,1,0,0) + , arg("flags")=lt::session::start_default_features | lt::session::add_default_plugins + , arg("alert_mask")=alert::error_notification)) + ) + .def("outgoing_ports", depr(&outgoing_ports)) +#endif + .def("post_torrent_updates", allow_threads(<::session::post_torrent_updates), arg("flags") = 0xffffffff) + .def("post_dht_stats", allow_threads(<::session::post_dht_stats)) + .def("post_session_stats", allow_threads(<::session::post_session_stats)) + .def("is_listening", allow_threads(<::session::is_listening)) + .def("listen_port", allow_threads(<::session::listen_port)) + .def("ssl_listen_port", allow_threads(<::session::ssl_listen_port)) +#ifndef TORRENT_DISABLE_DHT + .def("add_dht_node", &add_dht_node) +#if TORRENT_ABI_VERSION == 1 + .def( + "add_dht_router", depr(&add_dht_router) + , (arg("router"), "port") + ) +#endif // TORRENT_ABI_VERSION + .def("is_dht_running", allow_threads(<::session::is_dht_running)) +#if TORRENT_ABI_VERSION <= 2 + .def("set_dht_settings", allow_threads(<::session::set_dht_settings)) + .def("get_dht_settings", allow_threads(<::session::get_dht_settings)) +#endif + .def("dht_get_immutable_item", allow_threads(dht_get_immutable_item)) + .def("dht_get_mutable_item", &dht_get_mutable_item) + .def("dht_put_immutable_item", allow_threads(dht_put_immutable_item)) + .def("dht_put_mutable_item", &dht_put_mutable_item) + .def("dht_get_peers", allow_threads(<::session::dht_get_peers)) + .def("dht_announce", allow_threads(<::session::dht_announce)) + .def("dht_live_nodes", allow_threads(<::session::dht_live_nodes)) + .def("dht_sample_infohashes", allow_threads(<::session::dht_sample_infohashes)) +#endif // TORRENT_DISABLE_DHT + .def("add_torrent", &add_torrent) + .def("async_add_torrent", &async_add_torrent) + .def("async_add_torrent", &wrap_async_add_torrent) + .def("add_torrent", &wrap_add_torrent) +#ifndef BOOST_NO_EXCEPTIONS +#if TORRENT_ABI_VERSION == 1 + .def( + "add_torrent", depr(&add_torrent_depr) + , ( + arg("resume_data") = entry(), + arg("storage_mode") = storage_mode_sparse, + arg("paused") = false + ) + ) +#endif // TORRENT_ABI_VERSION +#endif // BOOST_NO_EXCEPTIONS + .def("remove_torrent", allow_threads(<::session::remove_torrent), arg("option") = 0) +#if TORRENT_ABI_VERSION == 1 + .def("status", depr(<::session::status)) +#endif + .def("get_settings", &session_get_settings) + .def("apply_settings", &session_apply_settings) +#if TORRENT_ABI_VERSION == 1 +#ifndef TORRENT_DISABLE_ENCRYPTION + .def("set_pe_settings", depr(<::session::set_pe_settings)) + .def("get_pe_settings", depr(<::session::get_pe_settings)) +#endif +#endif + .def("load_state", &load_state, (arg("entry"), arg("flags") = 0xffffffff)) + .def("save_state", &save_state, (arg("entry"), arg("flags") = 0xffffffff)) + .def("pop_alerts", &pop_alerts) + .def("wait_for_alert", &wait_for_alert, return_internal_reference<>()) + .def("set_alert_notify", &set_alert_notify) + .def("set_alert_fd", &set_alert_fd) + .def("add_extension", &add_extension) +#if TORRENT_ABI_VERSION == 1 +#if TORRENT_USE_I2P + .def("set_i2p_proxy", depr(<::session::set_i2p_proxy)) + .def("i2p_proxy", depr(<::session::i2p_proxy)) +#endif +#endif + .def("set_ip_filter", allow_threads(<::session::set_ip_filter)) + .def("get_ip_filter", allow_threads(<::session::get_ip_filter)) + .def("find_torrent", allow_threads(<::session::find_torrent)) + .def("get_torrents", &get_torrents) + .def("get_torrent_status", &get_torrent_status, (arg("session"), arg("pred"), arg("flags") = 0)) + .def("refresh_torrent_status", &refresh_torrent_status, (arg("session"), arg("torrents"), arg("flags") = 0)) + .def("pause", allow_threads(<::session::pause)) + .def("resume", allow_threads(<::session::resume)) + .def("is_paused", allow_threads(<::session::is_paused)) + .def("add_port_mapping", allow_threads(<::session::add_port_mapping)) + .def("delete_port_mapping", allow_threads(<::session::delete_port_mapping)) + .def("reopen_network_sockets", allow_threads(<::session::reopen_network_sockets)) + .def("set_peer_class_filter", <::session::set_peer_class_filter) + .def("set_peer_class_type_filter", <::session::set_peer_class_type_filter) + .def("create_peer_class", <::session::create_peer_class) + .def("delete_peer_class", <::session::delete_peer_class) + .def("get_peer_class", &get_peer_class) + .def("set_peer_class", &set_peer_class) + +#if TORRENT_ABI_VERSION == 1 + .def("id", depr(<::session::id)) + .def( + "listen_on", depr(&listen_on) + , (arg("min"), "max", arg("interface") = (char const*)nullptr, arg("flags") = 0) + ) +#ifndef TORRENT_DISABLE_DHT + .def("start_dht", depr(start_dht0)) + .def("stop_dht", depr(<::session::stop_dht)) + .def("start_dht", depr(start_dht1)) + .def("dht_state", depr(<::session::dht_state)) + .def("set_dht_proxy", depr(<::session::set_dht_proxy)) + .def("dht_proxy", depr(<::session::dht_proxy)) +#endif + .def("set_local_download_rate_limit", depr(<::session::set_local_download_rate_limit)) + .def("local_download_rate_limit", depr(<::session::local_download_rate_limit)) + .def("set_local_upload_rate_limit", depr(<::session::set_local_upload_rate_limit)) + .def("local_upload_rate_limit", depr(<::session::local_upload_rate_limit)) + .def("set_download_rate_limit", depr(<::session::set_download_rate_limit)) + .def("download_rate_limit", depr(<::session::download_rate_limit)) + .def("set_upload_rate_limit", depr(<::session::set_upload_rate_limit)) + .def("upload_rate_limit", depr(<::session::upload_rate_limit)) + .def("set_max_uploads", depr(<::session::set_max_uploads)) + .def("set_max_connections", depr(<::session::set_max_connections)) + .def("max_connections", depr(<::session::max_connections)) + .def("num_connections", depr(<::session::num_connections)) + .def("set_max_half_open_connections", depr(<::session::set_max_half_open_connections)) + .def("set_alert_queue_size_limit", depr(<::session::set_alert_queue_size_limit)) + .def("set_alert_mask", depr(<::session::set_alert_mask)) + .def("set_peer_proxy", depr(<::session::set_peer_proxy)) + .def("set_tracker_proxy", depr(<::session::set_tracker_proxy)) + .def("set_web_seed_proxy", depr(<::session::set_web_seed_proxy)) + .def("peer_proxy", depr(<::session::peer_proxy)) + .def("tracker_proxy", depr(<::session::tracker_proxy)) + .def("web_seed_proxy", depr(<::session::web_seed_proxy)) + .def("set_proxy", depr(<::session::set_proxy)) + .def("proxy", depr(<::session::proxy)) + .def("start_upnp", depr(&start_upnp)) + .def("stop_upnp", depr(<::session::stop_upnp)) + .def("start_lsd", depr(<::session::start_lsd)) + .def("stop_lsd", depr(<::session::stop_lsd)) + .def("start_natpmp", depr(&start_natpmp)) + .def("stop_natpmp", depr(<::session::stop_natpmp)) + .def("set_peer_id", depr(<::session::set_peer_id)) +#endif // TORRENT_ABI_VERSION + ; + + s.attr("tcp") = lt::portmap_protocol::tcp; + s.attr("udp") = lt::portmap_protocol::udp; + + s.attr("global_peer_class_id") = session::global_peer_class_id; + s.attr("tcp_peer_class_id") = session::tcp_peer_class_id; + s.attr("local_peer_class_id") = session::local_peer_class_id; + + s.attr("reopen_map_ports") = lt::session::reopen_map_ports; + + s.attr("delete_files") = lt::session::delete_files; + s.attr("delete_partfile") = lt::session::delete_partfile; + } + +#if TORRENT_ABI_VERSION == 1 + { + scope s = class_("protocol_type"); + s.attr("udp") = lt::portmap_protocol::udp; + s.attr("tcp") = lt::portmap_protocol::tcp; + } +#endif + + { + scope s = class_("save_state_flags_t"); + s.attr("save_settings") = lt::session::save_settings; +#if TORRENT_ABI_VERSION <= 2 + s.attr("save_dht_settings") = lt::session::save_dht_settings; +#endif + s.attr("save_dht_state") = lt::session::save_dht_state; +#if TORRENT_ABI_VERSION == 1 + s.attr("save_encryption_settings") = lt::session:: save_encryption_settings; + s.attr("save_as_map") = lt::session::save_as_map; + s.attr("save_i2p_proxy") = lt::session::save_i2p_proxy; + s.attr("save_proxy") = lt::session::save_proxy; + s.attr("save_dht_proxy") = lt::session::save_dht_proxy; + s.attr("save_peer_proxy") = lt::session::save_peer_proxy; + s.attr("save_web_proxy") = lt::session::save_web_proxy; + s.attr("save_tracker_proxy") = lt::session::save_tracker_proxy; +#endif + } + +#if TORRENT_ABI_VERSION == 1 + enum_("listen_on_flags_t") + .value("listen_reuse_address", lt::session::listen_reuse_address) + .value("listen_no_system_port", lt::session::listen_no_system_port) + ; +#endif + + def("high_performance_seed", high_performance_seed_wrapper); + def("min_memory_usage", min_memory_usage_wrapper); + def("default_settings", default_settings_wrapper); + def("read_resume_data", read_resume_data_wrapper0); + def("read_resume_data", read_resume_data_wrapper1); + def("write_resume_data", write_resume_data); + def("write_resume_data_buf", write_resume_data_buf_); + + entry (*write_torrent_file0)(add_torrent_params const&, write_torrent_flags_t) = &write_torrent_file; + def("write_torrent_file", write_torrent_file0, (arg("atp"), arg("flags") = 0)); + def("write_torrent_file_buf", write_torrent_file_buf, (arg("atp"), arg("flags") = 0)); + + { + scope s = class_("write_flags"); + s.attr("allow_missing_piece_layer") = lt::write_flags::allow_missing_piece_layer; + s.attr("no_http_seeds") = lt::write_flags::no_http_seeds; + s.attr("include_dht_nodes") = lt::write_flags::include_dht_nodes; + } + + class_("stats_metric") + .def_readonly("name", &stats_metric::name) + .def_readonly("value_index", &stats_metric::value_index) + .def_readonly("type", &stats_metric::type) + ; + + enum_("metric_type_t") + .value("counter", metric_type_t::counter) + .value("gauge", metric_type_t::gauge) + ; + + def("session_stats_metrics", session_stats_metrics); + def("find_metric_idx", find_metric_idx_wrap); + + scope().attr("create_ut_metadata_plugin") = "ut_metadata"; + scope().attr("create_ut_pex_plugin") = "ut_pex"; + scope().attr("create_smart_ban_plugin") = "smart_ban"; +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/bindings/python/src/session_settings.cpp b/bindings/python/src/session_settings.cpp new file mode 100644 index 0000000..db93700 --- /dev/null +++ b/bindings/python/src/session_settings.cpp @@ -0,0 +1,137 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include + +using namespace boost::python; +using namespace lt; + +void bind_session_settings() +{ + enum_("choking_algorithm_t") + .value("fixed_slots_choker", settings_pack::fixed_slots_choker) +#if TORRENT_ABI_VERSION == 1 + .value("auto_expand_choker", settings_pack::rate_based_choker) +#endif + .value("rate_based_choker", settings_pack::rate_based_choker) +#if TORRENT_ABI_VERSION == 1 + .value("bittyrant_choker", settings_pack::bittyrant_choker) +#endif + ; + + enum_("seed_choking_algorithm_t") + .value("round_robin", settings_pack::round_robin) + .value("fastest_upload", settings_pack::fastest_upload) + .value("anti_leech", settings_pack::anti_leech) + ; + + enum_("mmap_write_mode_t") + .value("always_pwrite", settings_pack::always_pwrite) + .value("always_mmap_write", settings_pack::always_mmap_write) + .value("auto_mmap_write", settings_pack::auto_mmap_write) + ; + + enum_("suggest_mode_t") + .value("no_piece_suggestions", settings_pack::no_piece_suggestions) + .value("suggest_read_cache", settings_pack::suggest_read_cache) + ; + + enum_("io_buffer_mode_t") + .value("enable_os_cache", settings_pack::enable_os_cache) +#if TORRENT_ABI_VERSION == 1 + .value("disable_os_cache_for_aligned_files", settings_pack::disable_os_cache_for_aligned_files) +#endif + .value("disable_os_cache", settings_pack::disable_os_cache) + .value("write_through", settings_pack::write_through) + ; + + enum_("bandwidth_mixed_algo_t") + .value("prefer_tcp", settings_pack::prefer_tcp) + .value("peer_proportional", settings_pack::peer_proportional) + ; + + enum_("enc_policy") + .value("pe_forced", settings_pack::pe_forced) + .value("pe_enabled", settings_pack::pe_enabled) + .value("pe_disabled", settings_pack::pe_disabled) +#if TORRENT_ABI_VERSION == 1 + .value("forced", settings_pack::pe_forced) + .value("enabled", settings_pack::pe_enabled) + .value("disabled", settings_pack::pe_disabled) +#endif + ; + + enum_("enc_level") + .value("pe_rc4", settings_pack::pe_rc4) + .value("pe_plaintext", settings_pack::pe_plaintext) + .value("pe_both", settings_pack::pe_both) +#if TORRENT_ABI_VERSION == 1 + .value("rc4", settings_pack::pe_rc4) + .value("plaintext", settings_pack::pe_plaintext) + .value("both", settings_pack::pe_both) +#endif + ; + + { + scope s = enum_("proxy_type_t") + .value("none", settings_pack::none) + .value("socks4", settings_pack::socks4) + .value("socks5", settings_pack::socks5) + .value("socks5_pw", settings_pack::socks5_pw) + .value("http", settings_pack::http) + .value("http_pw", settings_pack::http_pw) + .value("i2p_proxy", settings_pack::i2p_proxy) + ; + +#if TORRENT_ABI_VERSION == 1 + scope().attr("proxy_type") = s; + + class_("proxy_settings") + .def_readwrite("hostname", &proxy_settings::hostname) + .def_readwrite("port", &proxy_settings::port) + .def_readwrite("password", &proxy_settings::password) + .def_readwrite("username", &proxy_settings::username) + .def_readwrite("type", &proxy_settings::type) + .def_readwrite("proxy_peer_connections", &proxy_settings::proxy_peer_connections) + .def_readwrite("proxy_hostnames", &proxy_settings::proxy_hostnames) + ; +#endif + } + +#ifndef TORRENT_DISABLE_DHT +#if TORRENT_ABI_VERSION <= 2 + class_("dht_settings") + .def_readwrite("max_peers_reply", &dht::dht_settings::max_peers_reply) + .def_readwrite("search_branching", &dht::dht_settings::search_branching) + .def_readwrite("max_fail_count", &dht::dht_settings::max_fail_count) + .def_readwrite("max_torrents", &dht::dht_settings::max_torrents) + .def_readwrite("max_dht_items", &dht::dht_settings::max_dht_items) + .def_readwrite("restrict_routing_ips", &dht::dht_settings::restrict_routing_ips) + .def_readwrite("restrict_search_ips", &dht::dht_settings::restrict_search_ips) + .def_readwrite("max_torrent_search_reply", &dht::dht_settings::max_torrent_search_reply) + .def_readwrite("extended_routing_table", &dht::dht_settings::extended_routing_table) + .def_readwrite("aggressive_lookups", &dht::dht_settings::aggressive_lookups) + .def_readwrite("privacy_lookups", &dht::dht_settings::privacy_lookups) + .def_readwrite("enforce_node_id", &dht::dht_settings::enforce_node_id) + .def_readwrite("ignore_dark_internet", &dht::dht_settings::ignore_dark_internet) + .def_readwrite("block_timeout", &dht::dht_settings::block_timeout) + .def_readwrite("block_ratelimit", &dht::dht_settings::block_ratelimit) + .def_readwrite("read_only", &dht::dht_settings::read_only) + .def_readwrite("item_lifetime", &dht::dht_settings::item_lifetime) + ; +#endif +#endif + +#if TORRENT_ABI_VERSION == 1 + class_("pe_settings") + .def_readwrite("out_enc_policy", &pe_settings::out_enc_policy) + .def_readwrite("in_enc_policy", &pe_settings::in_enc_policy) + .def_readwrite("allowed_enc_level", &pe_settings::allowed_enc_level) + .def_readwrite("prefer_rc4", &pe_settings::prefer_rc4) + ; +#endif + +} diff --git a/bindings/python/src/sha1_hash.cpp b/bindings/python/src/sha1_hash.cpp new file mode 100644 index 0000000..9fa4d0c --- /dev/null +++ b/bindings/python/src/sha1_hash.cpp @@ -0,0 +1,46 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include + +#include "bytes.hpp" + +namespace { + +using namespace lt; + +long get_hash(sha1_hash const& s) +{ + return std::hash{}(s); +} + +bytes sha1_hash_bytes(const sha1_hash& bn) { + return bytes(bn.to_string()); +} + +} + +void bind_sha1_hash() +{ + using namespace boost::python; + using namespace lt; + + class_("sha1_hash") + .def(self == self) + .def(self != self) + .def(self < self) + .def(self_ns::str(self)) + .def(init()) + .def("clear", &sha1_hash::clear) + .def("is_all_zeros", &sha1_hash::is_all_zeros) + .def("to_string", sha1_hash_bytes) + .def("__hash__", get_hash) + .def("to_bytes", sha1_hash_bytes) + ; + + scope().attr("peer_id") = scope().attr("sha1_hash"); +} + diff --git a/bindings/python/src/sha256_hash.cpp b/bindings/python/src/sha256_hash.cpp new file mode 100644 index 0000000..5da2ad7 --- /dev/null +++ b/bindings/python/src/sha256_hash.cpp @@ -0,0 +1,44 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include + +#include "bytes.hpp" + +namespace { + +using namespace lt; + +long get_hash(sha256_hash const& s) +{ + return std::hash{}(s); +} + +bytes sha256_hash_bytes(const sha256_hash& bn) { + return bytes(bn.to_string()); +} + +} + +void bind_sha256_hash() +{ + using namespace boost::python; + using namespace lt; + + class_("sha256_hash") + .def(self == self) + .def(self != self) + .def(self < self) + .def(self_ns::str(self)) + .def(init()) + .def("clear", &sha256_hash::clear) + .def("is_all_zeros", &sha256_hash::is_all_zeros) + .def("to_string", sha256_hash_bytes) + .def("__hash__", get_hash) + .def("to_bytes", sha256_hash_bytes) + ; +} + diff --git a/bindings/python/src/string.cpp b/bindings/python/src/string.cpp new file mode 100644 index 0000000..408b014 --- /dev/null +++ b/bindings/python/src/string.cpp @@ -0,0 +1,53 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include + +using namespace boost::python; + +struct unicode_from_python +{ + unicode_from_python() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { +#if PY_VERSION_HEX >= 0x03020000 + return PyUnicode_Check(x) ? x : nullptr; +#else + return PyString_Check(x) ? x : PyUnicode_Check(x) ? x : nullptr; +#endif + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage< + std::string>*)data)->storage.bytes; + +#if PY_VERSION_HEX < 0x03000000 + if (PyString_Check(x)) + { + data->convertible = new (storage) std::string(PyString_AsString(x) + , PyString_Size(x)); + } + else +#endif + { + Py_ssize_t size = 0; + char const* unicode = PyUnicode_AsUTF8AndSize(x, &size); + data->convertible = new (storage) std::string(unicode, size); + } + } +}; + +void bind_unicode_string_conversion() +{ + unicode_from_python(); +} + diff --git a/bindings/python/src/torrent_handle.cpp b/bindings/python/src/torrent_handle.cpp new file mode 100644 index 0000000..56801c5 --- /dev/null +++ b/bindings/python/src/torrent_handle.cpp @@ -0,0 +1,679 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include +#include "bytes.hpp" +#include +#include +#include +#include +#include +#include "libtorrent/announce_entry.hpp" +#include +#include "gil.hpp" + +using namespace boost::python; +using namespace lt; + +#ifdef _MSC_VER +#pragma warning(push) +// warning c4996: x: was declared deprecated +#pragma warning( disable : 4996 ) +#endif + +namespace +{ + + list url_seeds(torrent_handle& handle) + { + list ret; + std::set urls; + { + allow_threading_guard guard; + urls = handle.url_seeds(); + } + + for (std::set::iterator i(urls.begin()) + , end(urls.end()); i != end; ++i) + ret.append(*i); + return ret; + } + + list http_seeds(torrent_handle& handle) + { + list ret; + std::set urls; + { + allow_threading_guard guard; + urls = handle.http_seeds(); + } + + for (std::set::iterator i(urls.begin()) + , end(urls.end()); i != end; ++i) + ret.append(*i); + return ret; + } + + list piece_availability(torrent_handle& handle) + { + list ret; + std::vector avail; + { + allow_threading_guard guard; + handle.piece_availability(avail); + } + + for (auto const a : avail) + ret.append(a); + return ret; + } + + list piece_priorities(torrent_handle& handle) + { + list ret; + std::vector prio; + { + allow_threading_guard guard; + prio = handle.get_piece_priorities(); + } + + for (auto const p : prio) + ret.append(p); + return ret; + } + +} // namespace unnamed + +list file_progress(torrent_handle& handle, file_progress_flags_t const flags) +{ + std::vector p; + + { + allow_threading_guard guard; + std::shared_ptr ti = handle.torrent_file(); + if (ti) + { + p.reserve(ti->num_files()); + handle.file_progress(p, flags); + } + } + + list result; + + for (std::vector::iterator i(p.begin()), e(p.end()); i != e; ++i) + result.append(*i); + + return result; +} + +list get_peer_info(torrent_handle const& handle) +{ + std::vector pi; + + { + allow_threading_guard guard; + handle.get_peer_info(pi); + } + + list result; + + for (std::vector::iterator i = pi.begin(); i != pi.end(); ++i) + result.append(*i); + + return result; +} + +namespace +{ + template + T extract_fn(object o) + { + return boost::python::extract(o); + } +} + +void prioritize_pieces(torrent_handle& info, object o) +{ + stl_input_iterator begin(o), end; + if (begin == end) return; + + // determine which overload should be selected. the one taking a list of + // priorities or the one taking a list of piece -> priority mappings + bool const is_piece_list = extract>(*begin).check(); + + if (is_piece_list) + { + std::vector> piece_list; + std::transform(begin, end, std::back_inserter(piece_list) + , &extract_fn>); + info.prioritize_pieces(piece_list); + } + else + { + std::vector priority_vector; + std::transform(begin, end, std::back_inserter(priority_vector) + , &extract_fn); + info.prioritize_pieces(priority_vector); + } +} + +void prioritize_files(torrent_handle& info, object o) +{ + stl_input_iterator begin(o), end; + info.prioritize_files(std::vector(begin, end)); +} + +list file_priorities(torrent_handle& handle) +{ + list ret; + std::vector priorities = handle.get_file_priorities(); + + for (auto const p : priorities) + ret.append(p); + + return ret; +} + +download_priority_t file_priority0(torrent_handle& h, file_index_t index) +{ + return h.file_priority(index); +} + +void file_priority1(torrent_handle& h, file_index_t index, download_priority_t prio) +{ + return h.file_priority(index, prio); +} + +void dict_to_announce_entry(dict d, announce_entry& ae) +{ + ae.url = extract(d["url"]); + if (d.has_key("tier")) + ae.tier = extract(d["tier"]); + if (d.has_key("fail_limit")) + ae.fail_limit = extract(d["fail_limit"]); +} + +void replace_trackers(torrent_handle& h, object trackers) +{ + object iter(trackers.attr("__iter__")()); + + std::vector result; + + for (;;) + { + handle<> entry(allow_null(PyIter_Next(iter.ptr()))); + + if (entry == handle<>()) + break; + + if (extract(object(entry)).check()) + { + result.push_back(extract(object(entry))); + } + else + { + dict d; + d = extract(object(entry)); + announce_entry ae; + dict_to_announce_entry(d, ae); + result.push_back(ae); + } + } + + allow_threading_guard guard; + h.replace_trackers(result); +} + +void add_tracker(torrent_handle& h, dict d) +{ + announce_entry ae; + dict_to_announce_entry(d, ae); + h.add_tracker(ae); +} + +namespace +{ + using std::chrono::system_clock; + + object to_ptime(time_point tpt) + { + object ret; + if (tpt > min_time()) + { + ret = long_(system_clock::to_time_t(system_clock::now() + + duration_cast(tpt - clock_type::now()))); + } + return ret; + } +} + +list trackers(torrent_handle& h) +{ + list ret; + std::vector const trackers = h.trackers(); + for (std::vector::const_iterator i = trackers.begin(), end(trackers.end()); i != end; ++i) + { + dict d; + d["url"] = i->url; + d["trackerid"] = i->trackerid; + d["tier"] = i->tier; + d["fail_limit"] = i->fail_limit; + d["source"] = i->source; + d["verified"] = i->verified; + +#if TORRENT_ABI_VERSION == 1 + if (!i->endpoints.empty()) + { + announce_endpoint const& aep = i->endpoints.front(); + announce_infohash const& aih = aep.info_hashes[protocol_version::V1]; + d["message"] = aih.message; + dict last_error; + last_error["value"] = aih.last_error.value(); + last_error["category"] = aih.last_error.category().name(); + d["last_error"] = last_error; + d["next_announce"] = to_ptime(aih.next_announce); + d["min_announce"] = to_ptime(aih.min_announce); + d["scrape_incomplete"] = aih.scrape_incomplete; + d["scrape_complete"] = aih.scrape_complete; + d["scrape_downloaded"] = aih.scrape_downloaded; + d["fails"] = aih.fails; + d["updating"] = aih.updating; + d["start_sent"] = aih.start_sent; + d["complete_sent"] = aih.complete_sent; + } + else + { + d["message"] = std::string(); + dict last_error; + last_error["value"] = 0; + last_error["category"] = ""; + d["last_error"] = last_error; + d["next_announce"] = object(); + d["min_announce"] = object(); + d["scrape_incomplete"] = 0; + d["scrape_complete"] = 0; + d["scrape_downloaded"] = 0; + d["fails"] = 0; + d["updating"] = false; + d["start_sent"] = false; + d["complete_sent"] = false; + } +#endif + + list aeps; + for (auto const& aep : i->endpoints) + { + dict e; + e["local_address"] = boost::python::make_tuple(aep.local_endpoint.address().to_string(), aep.local_endpoint.port()); + + list aihs; + for (auto const& aih : aep.info_hashes) + { + dict i; + i["message"] = aih.message; + dict last_error; + last_error["value"] = aih.last_error.value(); + last_error["category"] = aih.last_error.category().name(); + i["last_error"] = last_error; + i["next_announce"] = to_ptime(aih.next_announce); + i["min_announce"] = to_ptime(aih.min_announce); + i["scrape_incomplete"] = aih.scrape_incomplete; + i["scrape_complete"] = aih.scrape_complete; + i["scrape_downloaded"] = aih.scrape_downloaded; + i["fails"] = aih.fails; + i["updating"] = aih.updating; + i["start_sent"] = aih.start_sent; + i["complete_sent"] = aih.complete_sent; + aihs.append(std::move(i)); + } + e["info_hashes"] = std::move(aihs); + +#if TORRENT_ABI_VERSION <= 2 + announce_infohash const& aih = aep.info_hashes[protocol_version::V1]; + e["message"] = aih.message; + dict last_error; + last_error["value"] = aih.last_error.value(); + last_error["category"] = aih.last_error.category().name(); + e["last_error"] = last_error; + e["next_announce"] = to_ptime(aih.next_announce); + e["min_announce"] = to_ptime(aih.min_announce); + e["scrape_incomplete"] = aih.scrape_incomplete; + e["scrape_complete"] = aih.scrape_complete; + e["scrape_downloaded"] = aih.scrape_downloaded; + e["fails"] = aih.fails; + e["updating"] = aih.updating; + e["start_sent"] = aih.start_sent; + e["complete_sent"] = aih.complete_sent; +#endif + aeps.append(std::move(e)); + } + d["endpoints"] = std::move(aeps); + +#if TORRENT_ABI_VERSION == 1 + d["send_stats"] = i->send_stats; +#endif + ret.append(std::move(d)); + } + return ret; +} + +list get_download_queue(torrent_handle& handle) +{ + list ret; + + std::vector downloading; + + { + allow_threading_guard guard; + downloading = handle.get_download_queue(); + } + + for (std::vector::iterator i = downloading.begin() + , end(downloading.end()); i != end; ++i) + { + dict partial_piece; + partial_piece["piece_index"] = i->piece_index; + partial_piece["blocks_in_piece"] = i->blocks_in_piece; + list block_list; + for (int k = 0; k < i->blocks_in_piece; ++k) + { + dict block_info; + block_info["state"] = i->blocks[k].state; + block_info["num_peers"] = i->blocks[k].num_peers; + block_info["bytes_progress"] = i->blocks[k].bytes_progress; + block_info["block_size"] = i->blocks[k].block_size; + block_info["peer"] = boost::python::make_tuple( + i->blocks[k].peer().address().to_string() + , i->blocks[k].peer().port()); + block_list.append(block_info); + } + partial_piece["blocks"] = block_list; + + ret.append(partial_piece); + } + + return ret; +} + +void set_metadata(torrent_handle& handle, std::string const& buf) +{ + handle.set_metadata(buf); +} + +#if TORRENT_ABI_VERSION == 1 + +std::shared_ptr get_torrent_info(torrent_handle const& h) +{ + allow_threading_guard guard; + return h.torrent_file(); +} + +#endif // TORRENT_ABI_VERSION + +// TODO: this overload should probably be deprecated +void add_piece_str(torrent_handle& th, piece_index_t piece, char const *data + , add_piece_flags_t const flags) +{ + th.add_piece(piece, data, flags); +} + +void add_piece_bytes(torrent_handle& th, piece_index_t piece, bytes data + , add_piece_flags_t const flags) +{ + std::vector buffer; + buffer.reserve(data.arr.size()); + std::copy(data.arr.begin(), data.arr.end(), std::back_inserter(buffer)); + th.add_piece(piece, std::move(buffer), flags); +} + +class dummy5 {}; +class dummy {}; +class dummy4 {}; +class dummy6 {}; +class dummy7 {}; +class dummy8 {}; +class dummy15 {}; +class dummy16 {}; + +using by_value = return_value_policy; +void bind_torrent_handle() +{ + // arguments are: number of seconds and tracker index + void (torrent_handle::*force_reannounce0)(int, int, reannounce_flags_t) const = &torrent_handle::force_reannounce; + +#if TORRENT_ABI_VERSION == 1 + bool (torrent_handle::*super_seeding0)() const = &torrent_handle::super_seeding; + void (torrent_handle::*super_seeding1)(bool) const = &torrent_handle::super_seeding; +#endif + void (torrent_handle::*set_flags0)(torrent_flags_t) const = &torrent_handle::set_flags; + void (torrent_handle::*set_flags1)(torrent_flags_t, torrent_flags_t) const = &torrent_handle::set_flags; + + download_priority_t (torrent_handle::*piece_priority0)(piece_index_t) const = &torrent_handle::piece_priority; + void (torrent_handle::*piece_priority1)(piece_index_t, download_priority_t) const = &torrent_handle::piece_priority; + + void (torrent_handle::*move_storage0)(std::string const&, lt::move_flags_t) const = &torrent_handle::move_storage; + void (torrent_handle::*rename_file0)(file_index_t, std::string const&) const = &torrent_handle::rename_file; + + bool (torrent_handle::*need_save_resume_data0)() const = &torrent_handle::need_save_resume_data; + bool (torrent_handle::*need_save_resume_data1)(resume_data_flags_t) const = &torrent_handle::need_save_resume_data; + + std::vector (torrent_handle::*file_status0)() const = &torrent_handle::file_status; + +#define _ allow_threads + + enum_("move_flags_t") + .value("always_replace_files", move_flags_t::always_replace_files) + .value("fail_if_exist", move_flags_t::fail_if_exist) + .value("dont_replace", move_flags_t::dont_replace) + .value("reset_save_path", move_flags_t::reset_save_path) + .value("reset_save_path_unchecked", move_flags_t::reset_save_path_unchecked) + ; + +#if TORRENT_ABI_VERSION == 1 + enum_("deprecated_move_flags_t") + .value("always_replace_files", deprecated_move_flags_t::always_replace_files) + .value("fail_if_exist", deprecated_move_flags_t::fail_if_exist) + .value("dont_replace", deprecated_move_flags_t::dont_replace) + ; +#endif + + { + scope s = class_("torrent_handle") + .def(self == self) + .def(self != self) + .def(self < self) + .def("__hash__", (std::size_t (*)(torrent_handle const&))&libtorrent::hash_value) + .def("get_peer_info", get_peer_info) + .def("post_peer_info", &torrent_handle::post_peer_info) + .def("status", _(&torrent_handle::status), arg("flags") = 0xffffffff) + .def("post_status", &torrent_handle::post_status, arg("flags") = 0xffffffff) + .def("get_download_queue", get_download_queue) + .def("post_download_queue", &torrent_handle::post_download_queue) + .def("file_progress", file_progress, arg("flags") = file_progress_flags_t{}) + .def("post_file_progress", &torrent_handle::post_file_progress, arg("flags") = file_progress_flags_t{}) + .def("trackers", trackers) + .def("post_trackers", &torrent_handle::post_trackers) + .def("replace_trackers", replace_trackers) + .def("add_tracker", add_tracker) + .def("add_url_seed", _(&torrent_handle::add_url_seed)) + .def("remove_url_seed", _(&torrent_handle::remove_url_seed)) + .def("url_seeds", url_seeds) + .def("add_http_seed", _(&torrent_handle::add_http_seed)) + .def("remove_http_seed", _(&torrent_handle::remove_http_seed)) + .def("http_seeds", http_seeds) + .def("torrent_file", _(&torrent_handle::torrent_file)) + .def("set_metadata", set_metadata) + .def("is_valid", _(&torrent_handle::is_valid)) + .def("pause", _(&torrent_handle::pause), arg("flags") = 0) + .def("resume", _(&torrent_handle::resume)) + .def("clear_error", _(&torrent_handle::clear_error)) + .def("queue_position", _(&torrent_handle::queue_position)) + .def("queue_position_up", _(&torrent_handle::queue_position_up)) + .def("queue_position_down", _(&torrent_handle::queue_position_down)) + .def("queue_position_top", _(&torrent_handle::queue_position_top)) + .def("queue_position_bottom", _(&torrent_handle::queue_position_bottom)) + + .def("add_piece", add_piece_str) + .def("add_piece", add_piece_bytes) + .def("read_piece", _(&torrent_handle::read_piece)) + .def("have_piece", _(&torrent_handle::have_piece)) + .def("set_piece_deadline", _(&torrent_handle::set_piece_deadline) + , (arg("index"), arg("deadline"), arg("flags") = 0)) + .def("reset_piece_deadline", _(&torrent_handle::reset_piece_deadline), (arg("index"))) + .def("clear_piece_deadlines", _(&torrent_handle::clear_piece_deadlines), (arg("index"))) + .def("piece_availability", &piece_availability) + .def("post_piece_availability", &torrent_handle::post_piece_availability) + .def("piece_priority", _(piece_priority0)) + .def("piece_priority", _(piece_priority1)) + .def("prioritize_pieces", &prioritize_pieces) + .def("get_piece_priorities", &piece_priorities) + .def("prioritize_files", &prioritize_files) + .def("get_file_priorities", &file_priorities) + .def("file_priority", &file_priority0) + .def("file_priority", &file_priority1) + .def("file_status", _(file_status0)) + .def("save_resume_data", _(&torrent_handle::save_resume_data), arg("flags") = 0) + .def("need_save_resume_data", _(need_save_resume_data0)) + .def("need_save_resume_data", _(need_save_resume_data1), arg("flags")) + .def("force_reannounce", _(force_reannounce0) + , (arg("seconds") = 0, arg("tracker_idx") = -1, arg("flags") = reannounce_flags_t{})) +#ifndef TORRENT_DISABLE_DHT + .def("force_dht_announce", _(&torrent_handle::force_dht_announce)) +#endif + .def("scrape_tracker", _(&torrent_handle::scrape_tracker), arg("index") = -1) + .def("flush_cache", &torrent_handle::flush_cache) + .def("set_upload_limit", _(&torrent_handle::set_upload_limit)) + .def("upload_limit", _(&torrent_handle::upload_limit)) + .def("set_download_limit", _(&torrent_handle::set_download_limit)) + .def("download_limit", _(&torrent_handle::download_limit)) + .def("connect_peer", &torrent_handle::connect_peer, (arg("endpoint"), arg("source")=0, arg("flags")=0xd)) + .def("set_max_uploads", &torrent_handle::set_max_uploads) + .def("max_uploads", _(&torrent_handle::max_uploads)) + .def("set_max_connections", &torrent_handle::set_max_connections) + .def("max_connections", _(&torrent_handle::max_connections)) + .def("move_storage", _(move_storage0), (arg("path"), arg("flags") = move_flags_t::always_replace_files)) + .def("info_hash", _(&torrent_handle::info_hash)) + .def("info_hashes", _(&torrent_handle::info_hashes)) + .def("force_recheck", _(&torrent_handle::force_recheck)) + .def("rename_file", _(rename_file0)) + .def("set_ssl_certificate_buffer", &torrent_handle::set_ssl_certificate_buffer, (arg("cert"), arg("private_key"), arg("dh_params"))) + .def("set_ssl_certificate", &torrent_handle::set_ssl_certificate, (arg("cert"), arg("private_key"), arg("dh_params"), arg("passphrase")="")) + .def("flags", _(&torrent_handle::flags)) + .def("set_flags", _(set_flags0)) + .def("set_flags", _(set_flags1)) + .def("unset_flags", _(&torrent_handle::unset_flags)) + // deprecated +#if TORRENT_ABI_VERSION == 1 + .def("piece_priorities", depr(&piece_priorities)) + .def("file_priorities", depr(&file_priorities)) + .def("stop_when_ready", depr(&torrent_handle::stop_when_ready)) + .def("super_seeding", depr(super_seeding1)) + .def("auto_managed", depr(&torrent_handle::auto_managed)) + .def("set_priority", depr(&torrent_handle::set_priority)) + .def("get_torrent_info", depr(&get_torrent_info)) + .def("super_seeding", depr(super_seeding0)) + .def("write_resume_data", depr(&torrent_handle::write_resume_data)) + .def("is_seed", depr(&torrent_handle::is_seed)) + .def("is_finished", depr(&torrent_handle::is_finished)) + .def("has_metadata", depr(&torrent_handle::has_metadata)) + .def("use_interface", depr(&torrent_handle::use_interface)) + .def("name", depr(&torrent_handle::name)) + .def("is_paused", depr(&torrent_handle::is_paused)) + .def("is_auto_managed", depr(&torrent_handle::is_auto_managed)) + .def("set_upload_mode", depr(&torrent_handle::set_upload_mode)) + .def("set_share_mode", depr(&torrent_handle::set_share_mode)) + .def("apply_ip_filter", depr(&torrent_handle::apply_ip_filter)) + .def("set_sequential_download", depr(&torrent_handle::set_sequential_download)) + .def("set_peer_upload_limit", depr(&torrent_handle::set_peer_upload_limit)) + .def("set_peer_download_limit", depr(&torrent_handle::set_peer_download_limit)) + .def("set_ratio", depr(&torrent_handle::set_ratio)) + .def("save_path", depr(&torrent_handle::save_path)) + .def("set_tracker_login", depr(&torrent_handle::set_tracker_login)) +#endif + ; + + s.attr("ignore_min_interval") = torrent_handle::ignore_min_interval; + s.attr("overwrite_existing") = torrent_handle::overwrite_existing; + s.attr("piece_granularity") = torrent_handle::piece_granularity; + s.attr("graceful_pause") = torrent_handle::graceful_pause; + s.attr("flush_disk_cache") = torrent_handle::flush_disk_cache; + s.attr("save_info_dict") = torrent_handle::save_info_dict; + s.attr("only_if_modified") = torrent_handle::only_if_modified; + s.attr("alert_when_available") = torrent_handle::alert_when_available; + s.attr("query_distributed_copies") = torrent_handle::query_distributed_copies; + s.attr("query_accurate_download_counters") = torrent_handle::query_accurate_download_counters; + s.attr("query_last_seen_complete") = torrent_handle::query_last_seen_complete; + s.attr("query_pieces") = torrent_handle::query_pieces; + s.attr("query_verified_pieces") = torrent_handle::query_verified_pieces; + } + + class_("open_file_state") + .add_property("file_index", make_getter((&open_file_state::file_index), by_value())) + .def_readonly("last_use", &open_file_state::last_use) + .def_readonly("open_mode", &open_file_state::open_mode) + ; + + { + scope s = class_("file_open_mode"); + s.attr("read_only") = file_open_mode::read_only; + s.attr("write_only") = file_open_mode::write_only; + s.attr("read_write") = file_open_mode::read_write; + s.attr("rw_mask") = file_open_mode::rw_mask; + s.attr("sparse") = file_open_mode::sparse; + s.attr("no_atime") = file_open_mode::no_atime; + s.attr("random_access") = file_open_mode::random_access; +#if TORRENT_ABI_VERSION == 1 + s.attr("locked") = 0; +#endif + s.attr("mmapped") = file_open_mode::mmapped; + } + + { + scope s = class_("file_progress_flags_t"); + s.attr("piece_granularity") = torrent_handle::piece_granularity; + } + + { + scope s = class_("add_piece_flags_t"); + s.attr("overwrite_existing") = torrent_handle::overwrite_existing; + } + + { + scope s = class_("pause_flags_t"); + s.attr("graceful_pause") = torrent_handle::graceful_pause; + } + + { + scope s = class_("save_resume_flags_t"); + s.attr("flush_disk_cache") = torrent_handle::flush_disk_cache; + s.attr("save_info_dict") = torrent_handle::save_info_dict; + s.attr("only_if_modified") = torrent_handle::only_if_modified; + } + + { + scope s = class_("reannounce_flags_t"); + s.attr("ignore_min_interval") = torrent_handle::ignore_min_interval; + } + + { + scope s = class_("deadline_flags_t"); + s.attr("alert_when_available") = torrent_handle::alert_when_available; + } + + { + scope s = class_("status_flags_t"); + s.attr("query_distributed_copies") = torrent_handle::query_distributed_copies; + s.attr("query_accurate_download_counters") = torrent_handle::query_accurate_download_counters; + s.attr("query_last_seen_complete") = torrent_handle::query_last_seen_complete; + s.attr("query_pieces") = torrent_handle::query_pieces; + s.attr("query_verified_pieces") = torrent_handle::query_verified_pieces; + } + +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/bindings/python/src/torrent_info.cpp b/bindings/python/src/torrent_info.cpp new file mode 100644 index 0000000..6509e05 --- /dev/null +++ b/bindings/python/src/torrent_info.cpp @@ -0,0 +1,514 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +#include "boost_python.hpp" +#include + +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/info_hash.hpp" // for protocol_version +#include "libtorrent/tracker_manager.hpp" // for event_t +#include "bytes.hpp" +#include "gil.hpp" + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4996: X: was declared deprecated +#pragma warning( disable : 4996 ) +#endif + +using namespace boost::python; +using namespace lt; + +namespace +{ + + std::vector::const_iterator begin_trackers(torrent_info& i) + { + return i.trackers().begin(); + } + + std::vector::const_iterator end_trackers(torrent_info& i) + { + return i.trackers().end(); + } + + void add_node(torrent_info& ti, char const* hostname, int port) + { + ti.add_node(std::make_pair(hostname, port)); + } + + list nodes(torrent_info const& ti) + { + list result; + + for (auto const& i : ti.nodes()) + result.append(boost::python::make_tuple(i.first, i.second)); + + return result; + } + + list get_web_seeds(torrent_info const& ti) + { + std::vector const& ws = ti.web_seeds(); + list ret; + for (std::vector::const_iterator i = ws.begin() + , end(ws.end()); i != end; ++i) + { + dict d; + d["url"] = i->url; + d["type"] = i->type; + d["auth"] = i->auth; + ret.append(d); + } + + return ret; + } + + void set_web_seeds(torrent_info& ti, list ws) + { + std::vector web_seeds; + int const len = static_cast(boost::python::len(ws)); + for (int i = 0; i < len; i++) + { + dict e = extract(ws[i]); + int const type = extract(e["type"]); + web_seeds.push_back(web_seed_entry( + extract(e["url"]) + , static_cast(type) + , extract(e["auth"]))); + } + ti.set_web_seeds(web_seeds); + } + +#if TORRENT_ABI_VERSION <= 2 + list get_merkle_tree(torrent_info const& ti) + { + std::vector const& mt = ti.merkle_tree(); + list ret; + for (std::vector::const_iterator i = mt.begin() + , end(mt.end()); i != end; ++i) + { + ret.append(bytes(i->to_string())); + } + return ret; + } + + void set_merkle_tree(torrent_info& ti, list hashes) + { + std::vector h; + for (int i = 0, e = int(len(hashes)); i < e; ++i) + h.push_back(sha1_hash(bytes(extract(hashes[i])).arr.data())); + + ti.set_merkle_tree(h); + } +#endif + + bytes hash_for_piece(torrent_info const& ti, piece_index_t i) + { + return bytes(ti.hash_for_piece(i).to_string()); + } + +#if TORRENT_ABI_VERSION <= 2 + bytes metadata(torrent_info const& ti) + { + auto const s = ti.info_section(); + return bytes(s.data(), s.size()); + } +#endif + + bytes get_info_section(torrent_info const& ti) + { + auto const s = ti.info_section(); + return bytes(s.data(), s.size()); + } + + list map_block(torrent_info& ti, piece_index_t piece, std::int64_t offset, int size) + { + std::vector p = ti.map_block(piece, offset, size); + list result; + + for (std::vector::iterator i(p.begin()), e(p.end()); i != e; ++i) + result.append(*i); + + return result; + } + +#if TORRENT_ABI_VERSION == 1 + // Create getters for announce_entry data members with non-trivial types which need converting. + lt::time_point get_next_announce(announce_entry const& ae) + { + python_deprecated("next_announce is deprecated"); + return ae.endpoints.empty() ? lt::time_point() : lt::time_point(ae.endpoints.front().info_hashes[protocol_version::V1].next_announce); + } + lt::time_point get_min_announce(announce_entry const& ae) + { + python_deprecated("min_announce is deprecated"); + return ae.endpoints.empty() ? lt::time_point() : lt::time_point(ae.endpoints.front().info_hashes[protocol_version::V1].min_announce); + } + // announce_entry data member bit-fields. + int get_fails(announce_entry const& ae) + { + python_deprecated("fails is deprecated"); + return ae.endpoints.empty() ? 0 : ae.endpoints.front().info_hashes[protocol_version::V1].fails; + } + bool get_updating(announce_entry const& ae) + { + python_deprecated("updating is deprecated"); + return ae.endpoints.empty() ? false : ae.endpoints.front().info_hashes[protocol_version::V1].updating; + } + bool get_start_sent(announce_entry const& ae) + { + python_deprecated("start_sent is deprecated"); + return ae.endpoints.empty() ? false : ae.endpoints.front().info_hashes[protocol_version::V1].start_sent; + } + bool get_complete_sent(announce_entry const& ae) + { + python_deprecated("complete_sent is deprecated"); + return ae.endpoints.empty() ? false : ae.endpoints.front().info_hashes[protocol_version::V1].complete_sent; + } + // announce_entry method requires lt::time_point. + bool can_announce(announce_entry const& ae, bool is_seed) { + python_deprecated("can_announce() is deprecated"); + // an entry without endpoints implies it has never been announced so it can be now + if (ae.endpoints.empty()) return true; + lt::time_point now = lt::clock_type::now(); + return ae.endpoints.front().can_announce(now, is_seed, ae.fail_limit); + } + bool is_working(announce_entry const& ae) + { + python_deprecated("is_working is deprecated"); + return ae.endpoints.empty() ? false : ae.endpoints.front().is_working(); + } +#endif + int get_source(announce_entry const& ae) { return ae.source; } + bool get_verified(announce_entry const& ae) { return ae.verified; } + +#if TORRENT_ABI_VERSION == 1 + std::string get_message(announce_entry const& ae) + { + python_deprecated("message is deprecated"); + return ae.endpoints.empty() ? "" : ae.endpoints.front().info_hashes[protocol_version::V1].message; + } + error_code get_last_error(announce_entry const& ae) + { + python_deprecated("last_error is deprecated"); + return ae.endpoints.empty() ? error_code() : ae.endpoints.front().info_hashes[protocol_version::V1].last_error; + } + int get_scrape_incomplete(announce_entry const& ae) + { + python_deprecated("scrape_incomplete is deprecated"); + return ae.endpoints.empty() ? 0 : ae.endpoints.front().info_hashes[protocol_version::V1].scrape_incomplete; + } + int get_scrape_complete(announce_entry const& ae) + { + python_deprecated("scrape_complete is deprecated"); + return ae.endpoints.empty() ? 0 : ae.endpoints.front().info_hashes[protocol_version::V1].scrape_complete; + } + int get_scrape_downloaded(announce_entry const& ae) + { + python_deprecated("scrape_downloaded is deprecated"); + return ae.endpoints.empty() ? 0 : ae.endpoints.front().info_hashes[protocol_version::V1].scrape_downloaded; + } + int next_announce_in(announce_entry const&) + { + python_deprecated("next_announce_in is deprecated"); + return 0; + } + int min_announce_in(announce_entry const&) + { + python_deprecated("min_announce_in is deprecated"); + return 0; + } + bool get_send_stats(announce_entry const& ae) + { + python_deprecated("send_stats is deprecated"); + return ae.send_stats; + } + std::int64_t get_size(file_entry const& fe) + { + python_deprecated("file_entry is deprecated"); + return fe.size; + } + std::int64_t get_offset(file_entry const& fe) + { + python_deprecated("file_entry is deprecated"); + return fe.offset; + } + bool get_pad_file(file_entry const& fe) + { + python_deprecated("file_entry is deprecated"); + return fe.pad_file; + } + bool get_executable_attribute(file_entry const& fe) + { + python_deprecated("file_entry is deprecated"); + return fe.executable_attribute; + } + bool get_hidden_attribute(file_entry const& fe) + { + python_deprecated("file_entry is deprecated"); + return fe.hidden_attribute; + } + bool get_symlink_attribute(file_entry const& fe) + { + python_deprecated("file_entry is deprecated"); + return fe.symlink_attribute; + } +#endif + +} // namespace unnamed + +load_torrent_limits dict_to_limits(dict limits) +{ + load_torrent_limits ret; + + list items = limits.items(); + int const len = int(boost::python::len(limits)); + for (int i = 0; i < len; i++) + { + boost::python::api::object_item item = items[i]; + std::string const key = extract(item[0]); + object const value = item[1]; + + if (key == "max_buffer_size") + { + ret.max_buffer_size = extract(value); + continue; + } + else if (key == "max_pieces") + { + ret.max_pieces = extract(value); + continue; + } + else if (key == "max_decode_depth") + { + ret.max_decode_depth = extract(value); + continue; + } + else if (key == "max_decode_tokens") + { + ret.max_decode_tokens = extract(value); + continue; + } + } + return ret; +} + +std::shared_ptr buffer_constructor0(bytes b) +{ + return std::make_shared(b.arr, from_span); +} + +std::shared_ptr buffer_constructor1(bytes b, dict limits) +{ + std::shared_ptr ret = std::make_shared(b.arr + , dict_to_limits(limits), from_span); + return ret; +} + +std::shared_ptr file_constructor0(lt::string_view filename) +{ + return std::make_shared(std::string(filename)); +} + +std::shared_ptr file_constructor1(lt::string_view filename, dict limits) +{ + return std::make_shared(std::string(filename), dict_to_limits(limits)); +} + +std::shared_ptr sha1_constructor0(sha1_hash const& ih) +{ + return std::make_shared(info_hash_t(ih)); +} + +std::shared_ptr sha256_constructor0(sha256_hash const& ih) +{ + return std::make_shared(info_hash_t(ih)); +} + +std::shared_ptr bencoded_constructor0(dict d) +{ + entry ent = extract(d); + std::vector buf; + bencode(std::back_inserter(buf), ent); + return std::make_shared(buf, lt::from_span); +} + +std::shared_ptr bencoded_constructor1(dict d, dict limits) +{ + entry ent = extract(d); + std::vector buf; + bencode(std::back_inserter(buf), ent); + return std::make_shared(buf, dict_to_limits(limits) + , lt::from_span); +} + +using by_value = return_value_policy; +void bind_torrent_info() +{ + return_value_policy copy; + + void (torrent_info::*rename_file0)(file_index_t, std::string const&) = &torrent_info::rename_file; + + class_("file_slice") + .add_property("file_index", make_getter((&file_slice::file_index), by_value())) + .def_readwrite("offset", &file_slice::offset) + .def_readwrite("size", &file_slice::size) + ; + + enum_("protocol_version") + .value("V1", protocol_version::V1) + .value("V2", protocol_version::V2) + ; + + enum_("tracker_source") + .value("source_torrent", announce_entry::source_torrent) + .value("source_client", announce_entry::source_client) + .value("source_magnet_link", announce_entry::source_magnet_link) + .value("source_tex", announce_entry::source_tex) + ; + + using add_tracker1 = void (torrent_info::*)(std::string const&, int, announce_entry::tracker_source); + + class_>("torrent_info", no_init) + .def(init(arg("info_hash"))) + .def("__init__", make_constructor(&bencoded_constructor0)) + .def("__init__", make_constructor(&bencoded_constructor1)) + .def("__init__", make_constructor(&buffer_constructor0)) + .def("__init__", make_constructor(&buffer_constructor1)) + .def("__init__", make_constructor(&file_constructor0)) + .def("__init__", make_constructor(&file_constructor1)) + .def(init((arg("ti")))) + + .def("__init__", make_constructor(&sha1_constructor0)) + .def("__init__", make_constructor(&sha256_constructor0)) + + .def("add_tracker", (add_tracker1)&torrent_info::add_tracker + , (arg("url"), arg("tier") = 0 + , arg("source") = announce_entry::source_client)) + .def("add_url_seed", &torrent_info::add_url_seed, (arg("url") + , arg("extern_auth") = std::string{} + , arg("extra_headers") = web_seed_entry::headers_t{})) + .def("add_http_seed", &torrent_info::add_http_seed, (arg("url") + , arg("extern_auth") = std::string{} + , arg("extra_headers") = web_seed_entry::headers_t{})) + .def("web_seeds", get_web_seeds) + .def("set_web_seeds", set_web_seeds) + + .def("name", &torrent_info::name, copy) + .def("comment", &torrent_info::comment, copy) + .def("creator", &torrent_info::creator, copy) + .def("total_size", &torrent_info::total_size) + .def("piece_length", &torrent_info::piece_length) + .def("num_pieces", &torrent_info::num_pieces) + .def("info_hash", &torrent_info::info_hash) + .def("info_hashes", &torrent_info::info_hashes, copy) + .def("hash_for_piece", &hash_for_piece) +#if TORRENT_ABI_VERSION <= 2 + .def("merkle_tree", depr(&get_merkle_tree)) + .def("set_merkle_tree", depr(&set_merkle_tree)) +#endif + .def("piece_size", &torrent_info::piece_size) + + .def("similar_torrents", &torrent_info::similar_torrents) + .def("collections", &torrent_info::collections) + .def("ssl_cert", &torrent_info::ssl_cert) + .def("num_files", &torrent_info::num_files) + .def("rename_file", rename_file0) + .def("remap_files", &torrent_info::remap_files) + .def("files", &torrent_info::files, return_internal_reference<>()) + .def("orig_files", &torrent_info::orig_files, return_internal_reference<>()) +#if TORRENT_ABI_VERSION == 1 + .def("file_at", depr(&torrent_info::file_at)) +#endif // TORRENT_ABI_VERSION + + .def("is_valid", &torrent_info::is_valid) + .def("priv", &torrent_info::priv) + .def("is_i2p", &torrent_info::is_i2p) +#if TORRENT_ABI_VERSION <= 2 + .def("is_merkle_torrent", depr(&torrent_info::is_merkle_torrent)) +#endif + .def("trackers", range(begin_trackers, end_trackers)) + + .def("creation_date", &torrent_info::creation_date) + + .def("add_node", &add_node) + .def("nodes", &nodes) +#if TORRENT_ABI_VERSION <= 2 + .def("metadata", depr(&metadata)) + .def("metadata_size", depr(&torrent_info::metadata_size)) +#endif + .def("info_section", &get_info_section) + .def("map_block", map_block) + .def("map_file", &torrent_info::map_file) + ; + +#if TORRENT_ABI_VERSION == 1 + class_("file_entry") + .def_readwrite("path", &file_entry::path) + .def_readwrite("symlink_path", &file_entry::symlink_path) + .def_readwrite("filehash", &file_entry::filehash) + .def_readwrite("mtime", &file_entry::mtime) + .add_property("pad_file", &get_pad_file) + .add_property("executable_attribute", &get_executable_attribute) + .add_property("hidden_attribute", &get_hidden_attribute) + .add_property("symlink_attribute", &get_symlink_attribute) + .add_property("offset", &get_offset) + .add_property("size", &get_size) + ; +#endif + + class_("announce_entry", init()) + .def_readwrite("url", &announce_entry::url) + .def_readonly("trackerid", &announce_entry::trackerid) +#if TORRENT_ABI_VERSION == 1 + .add_property("message", &get_message) + .add_property("last_error", &get_last_error) + .add_property("next_announce", &get_next_announce) + .add_property("min_announce", &get_min_announce) + .add_property("scrape_incomplete", &get_scrape_incomplete) + .add_property("scrape_complete", &get_scrape_complete) + .add_property("scrape_downloaded", &get_scrape_downloaded) +#endif + .def_readwrite("tier", &announce_entry::tier) + .def_readwrite("fail_limit", &announce_entry::fail_limit) + .add_property("source", &get_source) + .add_property("verified", &get_verified) +#if TORRENT_ABI_VERSION == 1 + .add_property("fails", &get_fails) + .add_property("updating", &get_updating) + .add_property("start_sent", &get_start_sent) + .add_property("complete_sent", &get_complete_sent) + .add_property("send_stats", &get_send_stats) + .def("next_announce_in", depr(&next_announce_in)) + .def("min_announce_in", depr(&min_announce_in)) + .def("can_announce", depr(&can_announce)) + .def("is_working", depr(&is_working)) +#endif +#if TORRENT_ABI_VERSION <= 2 + .def("reset", depr(&announce_entry::reset)) + .def("trim", depr(&announce_entry::trim)) +#endif + ; + + enum_("event_t") + .value("none", event_t::none) + .value("completed", event_t::completed) + .value("started", event_t::started) + .value("stopped", event_t::stopped) + .value("paused", event_t::paused) + ; + + implicitly_convertible, std::shared_ptr>(); + boost::python::register_ptr_to_python>(); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/bindings/python/src/torrent_status.cpp b/bindings/python/src/torrent_status.cpp new file mode 100644 index 0000000..6e48275 --- /dev/null +++ b/bindings/python/src/torrent_status.cpp @@ -0,0 +1,143 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include "gil.hpp" +#include +#include +#include + +using namespace boost::python; +using namespace lt; + +using by_value = return_value_policy; +std::shared_ptr get_torrent_file(torrent_status const& st) +{ + return st.torrent_file.lock(); +} + +void bind_torrent_status() +{ + scope status = class_("torrent_status") + .def(self == self) + .def_readonly("handle", &torrent_status::handle) + .add_property("torrent_file", &get_torrent_file) + .def_readonly("state", &torrent_status::state) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("paused", &torrent_status::paused) + .def_readonly("stop_when_ready", &torrent_status::stop_when_ready) + .def_readonly("auto_managed", &torrent_status::auto_managed) + .def_readonly("sequential_download", &torrent_status::sequential_download) +#endif + .def_readonly("is_seeding", &torrent_status::is_seeding) + .def_readonly("is_finished", &torrent_status::is_finished) + .def_readonly("has_metadata", &torrent_status::has_metadata) + .def_readonly("progress", &torrent_status::progress) + .def_readonly("progress_ppm", &torrent_status::progress_ppm) + .add_property("next_announce", make_getter(&torrent_status::next_announce, by_value())) +#if TORRENT_ABI_VERSION == 1 + .add_property("announce_interval", make_getter(&torrent_status::announce_interval, by_value())) +#endif + .def_readonly("current_tracker", &torrent_status::current_tracker) + .def_readonly("total_download", &torrent_status::total_download) + .def_readonly("total_upload", &torrent_status::total_upload) + .def_readonly("total_payload_download", &torrent_status::total_payload_download) + .def_readonly("total_payload_upload", &torrent_status::total_payload_upload) + .def_readonly("total_failed_bytes", &torrent_status::total_failed_bytes) + .def_readonly("total_redundant_bytes", &torrent_status::total_redundant_bytes) + .def_readonly("download_rate", &torrent_status::download_rate) + .def_readonly("upload_rate", &torrent_status::upload_rate) + .def_readonly("download_payload_rate", &torrent_status::download_payload_rate) + .def_readonly("upload_payload_rate", &torrent_status::upload_payload_rate) + .def_readonly("num_seeds", &torrent_status::num_seeds) + .def_readonly("num_peers", &torrent_status::num_peers) + .def_readonly("num_complete", &torrent_status::num_complete) + .def_readonly("num_incomplete", &torrent_status::num_incomplete) + .def_readonly("list_seeds", &torrent_status::list_seeds) + .def_readonly("list_peers", &torrent_status::list_peers) + .def_readonly("connect_candidates", &torrent_status::connect_candidates) + .add_property("pieces", make_getter(&torrent_status::pieces, by_value())) + .add_property("verified_pieces", make_getter(&torrent_status::verified_pieces, by_value())) + .def_readonly("num_pieces", &torrent_status::num_pieces) + .def_readonly("total_done", &torrent_status::total_done) + .def_readonly("total", &torrent_status::total) + .def_readonly("total_wanted_done", &torrent_status::total_wanted_done) + .def_readonly("total_wanted", &torrent_status::total_wanted) + .def_readonly("distributed_full_copies", &torrent_status::distributed_full_copies) + .def_readonly("distributed_fraction", &torrent_status::distributed_fraction) + .def_readonly("distributed_copies", &torrent_status::distributed_copies) + .def_readonly("block_size", &torrent_status::block_size) + .def_readonly("num_uploads", &torrent_status::num_uploads) + .def_readonly("num_connections", &torrent_status::num_connections) + .def_readonly("uploads_limit", &torrent_status::uploads_limit) + .def_readonly("connections_limit", &torrent_status::connections_limit) + .def_readonly("storage_mode", &torrent_status::storage_mode) + .def_readonly("up_bandwidth_queue", &torrent_status::up_bandwidth_queue) + .def_readonly("down_bandwidth_queue", &torrent_status::down_bandwidth_queue) + .def_readonly("all_time_upload", &torrent_status::all_time_upload) + .def_readonly("all_time_download", &torrent_status::all_time_download) + .def_readonly("seed_rank", &torrent_status::seed_rank) + .def_readonly("has_incoming", &torrent_status::has_incoming) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("seed_mode", &torrent_status::seed_mode) + .def_readonly("upload_mode", &torrent_status::upload_mode) + .def_readonly("share_mode", &torrent_status::share_mode) + .def_readonly("super_seeding", &torrent_status::super_seeding) + .def_readonly("active_time", &torrent_status::active_time) + .def_readonly("finished_time", &torrent_status::finished_time) + .def_readonly("seeding_time", &torrent_status::seeding_time) + .def_readonly("last_scrape", &torrent_status::last_scrape) + .def_readonly("error", &torrent_status::error) + .def_readonly("priority", &torrent_status::priority) + .def_readonly("time_since_upload", &torrent_status::time_since_upload) + .def_readonly("time_since_download", &torrent_status::time_since_download) +#endif + .def_readonly("errc", &torrent_status::errc) + .add_property("error_file", make_getter(&torrent_status::error_file, by_value())) + .def_readonly("name", &torrent_status::name) + .def_readonly("save_path", &torrent_status::save_path) + .def_readonly("added_time", &torrent_status::added_time) + .def_readonly("completed_time", &torrent_status::completed_time) + .def_readonly("last_seen_complete", &torrent_status::last_seen_complete) + .add_property("queue_position", make_getter(&torrent_status::queue_position, by_value())) + .def_readonly("need_save_resume", &torrent_status::need_save_resume) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("ip_filter_applies", &torrent_status::ip_filter_applies) +#endif + .def_readonly("moving_storage", &torrent_status::moving_storage) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("is_loaded", &torrent_status::is_loaded) +#endif + .def_readonly("announcing_to_trackers", &torrent_status::announcing_to_trackers) + .def_readonly("announcing_to_lsd", &torrent_status::announcing_to_lsd) + .def_readonly("announcing_to_dht", &torrent_status::announcing_to_dht) +#if TORRENT_ABI_VERSION < 3 + .def_readonly("info_hash", &torrent_status::info_hash) +#endif + .def_readonly("info_hashes", &torrent_status::info_hashes) + .add_property("last_upload", make_getter(&torrent_status::last_upload, by_value())) + .add_property("last_download", make_getter(&torrent_status::last_download, by_value())) + .add_property("active_duration", make_getter(&torrent_status::active_duration, by_value())) + .add_property("finished_duration", make_getter(&torrent_status::finished_duration, by_value())) + .add_property("seeding_duration", make_getter(&torrent_status::seeding_duration, by_value())) + .add_property("flags", make_getter(&torrent_status::flags, by_value())) + ; + + enum_("states") +#if TORRENT_ABI_VERSION == 1 + .value("queued_for_checking", torrent_status::queued_for_checking) +#endif + .value("checking_files", torrent_status::checking_files) + .value("downloading_metadata", torrent_status::downloading_metadata) + .value("downloading", torrent_status::downloading) + .value("finished", torrent_status::finished) + .value("seeding", torrent_status::seeding) +#if TORRENT_ABI_VERSION == 1 + .value("allocating", torrent_status::allocating) +#endif + .value("checking_resume_data", torrent_status::checking_resume_data) + .export_values() + ; +} + diff --git a/bindings/python/src/utility.cpp b/bindings/python/src/utility.cpp new file mode 100644 index 0000000..0e8f513 --- /dev/null +++ b/bindings/python/src/utility.cpp @@ -0,0 +1,130 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include +#include +#include "bytes.hpp" + +using namespace boost::python; +using namespace lt; + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4996: X: was declared deprecated +#pragma warning( disable : 4996 ) +#endif + +struct bytes_to_python +{ + static PyObject* convert(bytes const& p) + { +#if PY_MAJOR_VERSION >= 3 + PyObject *ret = PyBytes_FromStringAndSize(p.arr.c_str(), p.arr.size()); +#else + PyObject *ret = PyString_FromStringAndSize(p.arr.c_str(), p.arr.size()); +#endif + return ret; + } +}; + +template +struct array_to_python +{ + static PyObject* convert(std::array const& p) + { +#if PY_MAJOR_VERSION >= 3 + PyObject *ret = PyBytes_FromStringAndSize(p.data(), p.size()); +#else + PyObject *ret = PyString_FromStringAndSize(p.data(), p.size()); +#endif + return ret; + } +}; + +struct bytes_from_python +{ + bytes_from_python() + { + converter::registry::push_back( + &convertible, &construct, type_id()); + } + + static void* convertible(PyObject* x) + { +#if PY_MAJOR_VERSION >= 3 + return (PyBytes_Check(x) || PyByteArray_Check(x)) ? x : nullptr; +#else + return PyString_Check(x) ? x : nullptr; +#endif + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { +#if PY_MAJOR_VERSION >= 3 + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + bytes* ret = new (storage) bytes(); + if (PyByteArray_Check(x)) + { + ret->arr.resize(PyByteArray_Size(x)); + memcpy(&ret->arr[0], PyByteArray_AsString(x), ret->arr.size()); + } + else + { + ret->arr.resize(PyBytes_Size(x)); + memcpy(&ret->arr[0], PyBytes_AsString(x), ret->arr.size()); + } + data->convertible = storage; +#else + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + bytes* ret = new (storage) bytes(); + ret->arr.resize(PyString_Size(x)); + memcpy(&ret->arr[0], PyString_AsString(x), ret->arr.size()); + data->convertible = storage; +#endif + } +}; + +#if TORRENT_ABI_VERSION == 1 +object client_fingerprint_(peer_id const& id) +{ + python_deprecated("client_fingerprint is deprecated"); + boost::optional result = client_fingerprint(id); + return result ? object(*result) : object(); +} +#endif + +entry bdecode_(bytes const& data) +{ + return bdecode(data.arr); +} + +bytes bencode_(entry const& e) +{ + bytes result; + bencode(std::back_inserter(result.arr), e); + return result; +} + +void bind_utility() +{ + // TODO: it would be nice to install converters for sha1_hash as well + to_python_converter(); + to_python_converter, array_to_python<32>>(); + to_python_converter, array_to_python<64>>(); + bytes_from_python(); + +#if TORRENT_ABI_VERSION == 1 + def("identify_client", <::identify_client); + def("client_fingerprint", &client_fingerprint_); +#endif + def("bdecode", &bdecode_); + def("bencode", &bencode_); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + diff --git a/bindings/python/src/version.cpp b/bindings/python/src/version.cpp new file mode 100644 index 0000000..832a526 --- /dev/null +++ b/bindings/python/src/version.cpp @@ -0,0 +1,21 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include + +using namespace boost::python; +using lt::version; + +void bind_version() +{ + scope().attr("__version__") = version(); + +#if TORRENT_ABI_VERSION == 1 + scope().attr("version") = lt::version_str; + scope().attr("version_major") = LIBTORRENT_VERSION_MAJOR; + scope().attr("version_minor") = LIBTORRENT_VERSION_MINOR; +#endif +} + diff --git a/bindings/python/test.py b/bindings/python/test.py new file mode 100644 index 0000000..d1958cc --- /dev/null +++ b/bindings/python/test.py @@ -0,0 +1,1357 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + + +import libtorrent as lt + +import unittest +import time +import datetime +import os +import shutil +import binascii +import subprocess as sub +import sys +import pickle +import threading +import tempfile +import socket +import select +import logging +import ssl +import http.server +import functools + +import dummy_data + +# include terminal interface for travis parallel executions of scripts which use +# terminal features: fix multiple stdin assignment at termios.tcgetattr +if os.name != 'nt': + import pty + +settings = { + 'alert_mask': lt.alert.category_t.all_categories, + 'enable_dht': False, 'enable_lsd': False, 'enable_natpmp': False, + 'enable_upnp': False, 'listen_interfaces': '0.0.0.0:0', 'file_pool_size': 1} + + +def has_deprecated(): + return hasattr(lt, 'version') + + +class test_create_torrent(unittest.TestCase): + + def test_from_torrent_info(self): + ti = lt.torrent_info('unordered.torrent') + print(ti.ssl_cert()) + ct = lt.create_torrent(ti) + entry = ct.generate() + content = lt.bencode(entry).strip() + with open('unordered.torrent', 'rb') as f: + file_content = bytearray(f.read().strip()) + print(content) + print(file_content) + print(entry) + self.assertEqual(content, file_content) + + def test_from_scratch(self): + fs = lt.file_storage() + fs.add_file('test/file1', 1000) + fs.add_file('test/file2', 2000) + self.assertEqual(fs.file_name(0), 'file1') + self.assertEqual(fs.file_name(1), 'file2') + ct = lt.create_torrent(fs, 0, lt.create_torrent.canonical_files_no_tail_padding) + ct.add_url_seed('foo') + ct.add_http_seed('bar') + ct.add_tracker('bar') + ct.set_root_cert('1234567890') + ct.add_collection('1337') + for i in range(ct.num_pieces()): + ct.set_hash(i, b'abababababababababab') + entry = ct.generate() + encoded = lt.bencode(entry) + print(encoded) + + # zero out the creation date: + encoded = encoded.split(b'13:creation datei', 1) + encoded[1] = b'0e' + encoded[1].split(b'e', 1)[1] + encoded = b'13:creation datei'.join(encoded) + + self.assertEqual(encoded, b'd8:announce3:bar13:creation datei0e9:httpseeds3:bar4:infod11:collectionsl4:1337e5:filesld6:lengthi1000e4:pathl5:file1eed4:attr1:p6:lengthi15384e4:pathl4:.pad5:15384eed6:lengthi2000e4:pathl5:file2eee4:name4:test12:piece lengthi16384e6:pieces40:abababababababababababababababababababab8:ssl-cert10:1234567890e8:url-list3:fooe') + + +class test_session_stats(unittest.TestCase): + + def test_add_torrent_params(self): + atp = lt.add_torrent_params() + + for field_name in dir(atp): + field = getattr(atp, field_name) + print(field_name, field) + + atp.renamed_files = {} + atp.merkle_tree = [] + atp.unfinished_pieces = {} + atp.have_pieces = [] + atp.banned_peers = [] + atp.verified_pieces = [] + atp.piece_priorities = [] + atp.url_seeds = [] + + def test_unique(self): + metrics = lt.session_stats_metrics() + self.assertTrue(len(metrics) > 40) + idx = set() + for m in metrics: + self.assertTrue(m.value_index not in idx) + idx.add(m.value_index) + + def test_find_idx(self): + self.assertEqual(lt.find_metric_idx("peer.error_peers"), 0) + + +class test_torrent_handle(unittest.TestCase): + + def setup(self): + self.ses = lt.session(settings) + self.ti = lt.torrent_info('url_seed_multi.torrent') + self.h = self.ses.add_torrent({ + 'ti': self.ti, 'save_path': os.getcwd(), + 'flags': lt.torrent_flags.default_flags}) + + def test_add_torrent_error(self): + self.ses = lt.session(settings) + self.ti = lt.torrent_info('url_seed_multi.torrent') + with self.assertRaises(RuntimeError): + self.ses.add_torrent({'ti': self.ti, 'save_path': os.getcwd(), 'info_hashes': b'abababababababababab'}) + + def test_move_storage(self): + self.setup() + self.h.move_storage(u'test-dir') + self.h.move_storage(b'test-dir2') + self.h.move_storage('test-dir3') + self.h.move_storage(u'test-dir', flags=lt.move_flags_t.dont_replace) + self.h.move_storage(u'test-dir', flags=2) + self.h.move_storage(b'test-dir2', flags=2) + self.h.move_storage('test-dir3', flags=2) + + def test_torrent_handle(self): + self.setup() + self.assertEqual(self.h.get_file_priorities(), [4, 4]) + self.assertEqual(self.h.get_piece_priorities(), [4]) + + self.h.prioritize_files([0, 1]) + # workaround for asynchronous priority update + time.sleep(1) + self.assertEqual(self.h.get_file_priorities(), [0, 1]) + + self.h.prioritize_pieces([0]) + self.assertEqual(self.h.get_piece_priorities(), [0]) + + # also test the overload that takes a list of piece->priority mappings + self.h.prioritize_pieces([(0, 1)]) + self.assertEqual(self.h.get_piece_priorities(), [1]) + self.h.connect_peer(('127.0.0.1', 6881)) + self.h.connect_peer(('127.0.0.2', 6881), source=4) + self.h.connect_peer(('127.0.0.3', 6881), flags=2) + self.h.connect_peer(('127.0.0.4', 6881), flags=2, source=4) + + torrent_files = self.h.torrent_file() + print(torrent_files.map_file(0, 0, 0).piece) + + print(self.h.queue_position()) + + def test_torrent_handle_in_set(self): + self.setup() + torrents = set() + torrents.add(self.h) + + # get another instance of a torrent_handle that represents the same + # torrent. Make sure that when we add it to a set, it just replaces the + # existing object + t = self.ses.get_torrents() + self.assertEqual(len(t), 1) + for h in t: + torrents.add(h) + + self.assertEqual(len(torrents), 1) + + def test_torrent_handle_in_dict(self): + self.setup() + torrents = {} + torrents[self.h] = 'foo' + + # get another instance of a torrent_handle that represents the same + # torrent. Make sure that when we add it to a dict, it just replaces the + # existing object + t = self.ses.get_torrents() + self.assertEqual(len(t), 1) + for h in t: + torrents[h] = 'bar' + + self.assertEqual(len(torrents), 1) + self.assertEqual(torrents[self.h], 'bar') + + def test_replace_trackers(self): + self.setup() + trackers = [] + for idx, tracker_url in enumerate(('udp://tracker1.com', 'udp://tracker2.com')): + tracker = lt.announce_entry(tracker_url) + tracker.tier = idx + tracker.fail_limit = 2 + trackers.append(tracker) + self.assertEqual(tracker.url, tracker_url) + self.h.replace_trackers(trackers) + new_trackers = self.h.trackers() + self.assertEqual(new_trackers[0]['url'], 'udp://tracker1.com') + self.assertEqual(new_trackers[1]['tier'], 1) + self.assertEqual(new_trackers[1]['fail_limit'], 2) + + def test_pickle_trackers(self): + """Test lt objects converters are working and trackers can be pickled""" + self.setup() + tracker = lt.announce_entry('udp://tracker1.com') + tracker.tier = 0 + tracker.fail_limit = 1 + trackers = [tracker] + self.h.replace_trackers(trackers) + # wait a bit until the endpoints list gets populated + while len(self.h.trackers()[0]['endpoints']) == 0: + time.sleep(0.1) + + trackers = self.h.trackers() + self.assertEqual(trackers[0]['url'], 'udp://tracker1.com') + # this is not necessarily 0, it could also be (EHOSTUNREACH) if the + # local machine doesn't support the address family + expect_value = trackers[0]['endpoints'][0]['info_hashes'][0]['last_error']['value'] + pickled_trackers = pickle.dumps(trackers) + unpickled_trackers = pickle.loads(pickled_trackers) + self.assertEqual(unpickled_trackers[0]['url'], 'udp://tracker1.com') + self.assertEqual(unpickled_trackers[0]['endpoints'][0]['info_hashes'][0]['last_error']['value'], expect_value) + + def test_file_status(self): + self.setup() + status = self.h.file_status() + print(status) + + def test_piece_deadlines(self): + self.setup() + self.h.clear_piece_deadlines() + + def test_status_last_uploaded_downloaded(self): + # we want to check at seconds precision but can't control session + # time, wait for next full second to prevent second increment + time.sleep(1 - datetime.datetime.now().microsecond / 1000000.0) + + self.setup() + st = self.h.status() + for attr in dir(st): + print('%s: %s' % (attr, getattr(st, attr))) + # last upload and download times are at session start time + self.assertEqual(st.last_upload, None) + self.assertEqual(st.last_download, None) + + def test_serialize_trackers(self): + """Test to ensure the dict contains only python built-in types""" + self.setup() + self.h.add_tracker({'url': 'udp://tracker1.com'}) + tr = self.h.trackers()[0] + # wait a bit until the endpoints list gets populated + while len(tr['endpoints']) == 0: + time.sleep(0.1) + tr = self.h.trackers()[0] + import json + print(json.dumps(self.h.trackers()[0])) + + def test_torrent_status(self): + self.setup() + st = self.h.status() + ti = st.handle + self.assertEqual(ti.info_hashes(), self.ti.info_hashes()) + # make sure we can compare torrent_status objects + st2 = self.h.status() + self.assertEqual(st2, st) + print(st2) + + def test_read_resume_data(self): + + resume_data = lt.bencode({ + 'file-format': 'libtorrent resume file', + 'info-hash': 'abababababababababab', + 'name': 'test', + 'save_path': '.', + 'peers': '\x01\x01\x01\x01\x00\x01\x02\x02\x02\x02\x00\x02', + 'file_priority': [0, 1, 1]}) + tp = lt.read_resume_data(resume_data) + + self.assertEqual(tp.name, 'test') + self.assertEqual(tp.info_hashes.v1, lt.sha1_hash('abababababababababab')) + self.assertEqual(tp.file_priorities, [0, 1, 1]) + self.assertEqual(tp.peers, [('1.1.1.1', 1), ('2.2.2.2', 2)]) + + ses = lt.session(settings) + h = ses.add_torrent(tp) + for attr in dir(tp): + print('%s: %s' % (attr, getattr(tp, attr))) + + h.connect_peer(('3.3.3.3', 3)) + + for i in range(0, 10): + alerts = ses.pop_alerts() + for a in alerts: + print(a.message()) + time.sleep(0.1) + + def test_scrape(self): + self.setup() + # this is just to make sure this function can be called like this + # from python + self.h.scrape_tracker() + + def test_unknown_torrent_parameter(self): + self.ses = lt.session(settings) + try: + self.h = self.ses.add_torrent({'unexpected-key-name': ''}) + self.assertFalse('should have thrown an exception') + except KeyError as e: + print(e) + + def test_torrent_parameter(self): + self.ses = lt.session(settings) + self.ti = lt.torrent_info('url_seed_multi.torrent') + self.h = self.ses.add_torrent({ + 'ti': self.ti, + 'save_path': os.getcwd(), + 'trackers': ['http://test.com/announce'], + 'dht_nodes': [('1.2.3.4', 6881), ('4.3.2.1', 6881)], + 'file_priorities': [1, 1], + 'http_seeds': ['http://test.com/file3'], + 'url_seeds': ['http://test.com/announce-url'], + 'peers': [('5.6.7.8', 6881)], + 'banned_peers': [('8.7.6.5', 6881)], + 'renamed_files': {0: 'test.txt', 2: 'test.txt'} + }) + self.st = self.h.status() + self.assertEqual(self.st.save_path, os.getcwd()) + trackers = self.h.trackers() + self.assertEqual(len(trackers), 1) + self.assertEqual(trackers[0].get('url'), 'http://test.com/announce') + self.assertEqual(trackers[0].get('tier'), 0) + self.assertEqual(self.h.get_file_priorities(), [1, 1]) + self.assertEqual(self.h.http_seeds(), ['http://test.com/file3']) + # url_seeds was already set, test that it did not get overwritten + self.assertEqual(self.h.url_seeds(), + ['http://test.com/announce-url/', 'http://test.com/file/']) + # piece priorities weren't set explicitly, but they were updated by the + # file priorities being set + self.assertEqual(self.h.get_piece_priorities(), [1]) + self.assertEqual(self.st.verified_pieces, []) + + +class TestAddPiece(unittest.TestCase): + + def setUp(self): + self.dir = tempfile.TemporaryDirectory() + self.session = lt.session(settings) + self.ti = lt.torrent_info(dummy_data.DICT) + self.atp = lt.add_torrent_params() + self.atp.ti = self.ti + self.atp.save_path = self.dir.name + self.handle = self.session.add_torrent(self.atp) + self.wait_for(lambda: self.handle.status().state != lt.torrent_status.checking_files + and self.handle.status().state != lt.torrent_status.checking_resume_data, msg="checking") + + def wait_for(self, condition, msg="condition", timeout=5): + deadline = time.time() + timeout + while not condition(): + self.assertLess(time.time(), deadline, msg="%s timed out" % msg) + time.sleep(0.1) + + def wait_until_torrent_finished(self): + self.wait_for(lambda: self.handle.status().progress == 1.0, msg="progress") + + def file_written(): + with open(os.path.join(self.dir.name.encode(), dummy_data.NAME), mode="rb") as f: + return f.read() == dummy_data.DATA + + self.wait_for(file_written, msg="file write") + + def test_with_str(self): + for i, data in enumerate(dummy_data.PIECES): + self.handle.add_piece(i, data.decode(), 0) + + self.wait_until_torrent_finished() + + def test_with_bytes(self): + for i, data in enumerate(dummy_data.PIECES): + self.handle.add_piece(i, data, 0) + + self.wait_until_torrent_finished() + + +class test_load_torrent(unittest.TestCase): + + def test_bytearray(self): + # a bytearray object is interpreted as a bencoded buffer + atp = lt.load_torrent_buffer(bytearray(lt.bencode({'info': { + 'name': 'test_torrent', 'length': 1234, + 'piece length': 16 * 1024, + 'pieces': 'aaaaaaaaaaaaaaaaaaaa'}}))) + self.assertEqual(atp.ti.num_files(), 1) + + def test_bytes(self): + # a bytes object is interpreted as a bencoded buffer + atp = lt.load_torrent_buffer(bytes(lt.bencode({'info': { + 'name': 'test_torrent', 'length': 1234, + 'piece length': 16 * 1024, + 'pieces': 'aaaaaaaaaaaaaaaaaaaa'}}))) + self.assertEqual(atp.ti.num_files(), 1) + + def test_info_section(self): + atp = lt.load_torrent_file('base.torrent') + + self.assertTrue(len(atp.ti.info_section()) != 0) + self.assertTrue(len(atp.ti.hash_for_piece(0)) != 0) + +class test_torrent_info(unittest.TestCase): + + def test_non_ascii_file(self): + try: + shutil.copy('base.torrent', 'base-\u745E\u5177.torrent') + except shutil.SameFileError: + pass + ti = lt.torrent_info('base-\u745E\u5177.torrent') + + self.assertTrue(len(ti.info_section()) != 0) + self.assertTrue(len(ti.hash_for_piece(0)) != 0) + + def test_bencoded_constructor(self): + # things that can be converted to a bencoded entry, will be interpreted + # as such and encoded + info = lt.torrent_info({'info': { + 'name': 'test_torrent', 'length': 1234, + 'piece length': 16 * 1024, + 'pieces': 'aaaaaaaaaaaaaaaaaaaa'}}) + + self.assertEqual(info.num_files(), 1) + + f = info.files() + self.assertEqual(f.file_path(0), 'test_torrent') + self.assertEqual(f.file_name(0), 'test_torrent') + self.assertEqual(f.file_size(0), 1234) + self.assertEqual(info.total_size(), 1234) + self.assertEqual(info.creation_date(), 0) + + def test_bytearray(self): + # a bytearray object is interpreted as a bencoded buffer + info = lt.torrent_info(bytearray(lt.bencode({'info': { + 'name': 'test_torrent', 'length': 1234, + 'piece length': 16 * 1024, + 'pieces': 'aaaaaaaaaaaaaaaaaaaa'}}))) + self.assertEqual(info.num_files(), 1) + + def test_bytes(self): + # a bytes object is interpreted as a bencoded buffer + info = lt.torrent_info(bytes(lt.bencode({'info': { + 'name': 'test_torrent', 'length': 1234, + 'piece length': 16 * 1024, + 'pieces': 'aaaaaaaaaaaaaaaaaaaa'}}))) + self.assertEqual(info.num_files(), 1) + + def test_load_decode_depth_limit(self): + self.assertRaises(RuntimeError, lambda: lt.torrent_info( + {'test': {'test': {'test': {'test': {'test': {}}}}}, 'info': { + 'name': 'test_torrent', 'length': 1234, + 'piece length': 16 * 1024, + 'pieces': 'aaaaaaaaaaaaaaaaaaaa'}}, {'max_decode_depth': 1})) + + def test_load_max_pieces_limit(self): + self.assertRaises(RuntimeError, lambda: lt.torrent_info( + {'info': { + 'name': 'test_torrent', 'length': 1234000, + 'piece length': 16 * 1024, + 'pieces': 'aaaaaaaaaaaaaaaaaaaa'}}, {'max_pieces': 1})) + + def test_load_max_buffer_size_limit(self): + self.assertRaises(RuntimeError, lambda: lt.torrent_info( + {'info': { + 'name': 'test_torrent', 'length': 1234000, + 'piece length': 16 * 1024, + 'pieces': 'aaaaaaaaaaaaaaaaaaaa'}}, {'max_buffer_size': 1})) + + def test_info_section(self): + ti = lt.torrent_info('base.torrent') + + self.assertTrue(len(ti.info_section()) != 0) + self.assertTrue(len(ti.hash_for_piece(0)) != 0) + + def test_torrent_info_bytes_overload(self): + # bytes will never be interpreted as a file name. It's interpreted as a + # bencoded buffer + with self.assertRaises(RuntimeError): + ti = lt.torrent_info(b'base.torrent') + + def test_web_seeds(self): + ti = lt.torrent_info('base.torrent') + + ws = [{'url': 'http://foo/test', 'auth': '', 'type': 0}, + {'url': 'http://bar/test', 'auth': '', 'type': 1}] + ti.set_web_seeds(ws) + web_seeds = ti.web_seeds() + self.assertEqual(len(ws), len(web_seeds)) + for i in range(len(web_seeds)): + self.assertEqual(web_seeds[i]["url"], ws[i]["url"]) + self.assertEqual(web_seeds[i]["auth"], ws[i]["auth"]) + self.assertEqual(web_seeds[i]["type"], ws[i]["type"]) + + def test_announce_entry(self): + ae = lt.announce_entry('test') + self.assertEqual(ae.url, 'test') + self.assertEqual(ae.tier, 0) + self.assertEqual(ae.verified, False) + self.assertEqual(ae.source, 0) + + def test_torrent_info_sha1_overload(self): + ti = lt.torrent_info(lt.info_hash_t(lt.sha1_hash(b'a' * 20))) + self.assertEqual(ti.info_hash(), lt.sha1_hash(b'a' * 20)) + self.assertEqual(ti.info_hashes().v1, lt.sha1_hash(b'a' * 20)) + + ti_copy = lt.torrent_info(ti) + self.assertEqual(ti_copy.info_hash(), lt.sha1_hash(b'a' * 20)) + self.assertEqual(ti_copy.info_hashes().v1, lt.sha1_hash(b'a' * 20)) + + def test_torrent_info_sha256_overload(self): + ti = lt.torrent_info(lt.info_hash_t(lt.sha256_hash(b'a' * 32))) + self.assertEqual(ti.info_hashes().v2, lt.sha256_hash(b'a' * 32)) + + ti_copy = lt.torrent_info(ti) + self.assertEqual(ti_copy.info_hashes().v2, lt.sha256_hash(b'a' * 32)) + + def test_url_seed(self): + ti = lt.torrent_info('base.torrent') + + ti.add_tracker('foobar1') + ti.add_url_seed('foobar2') + ti.add_url_seed('foobar3', 'username:password') + ti.add_url_seed('foobar4', 'username:password', []) + + seeds = ti.web_seeds() + self.assertEqual(seeds, [ + {'url': 'foobar2', 'type': 0, 'auth': ''}, + {'url': 'foobar3', 'type': 0, 'auth': 'username:password'}, + {'url': 'foobar4', 'type': 0, 'auth': 'username:password'}, + ]) + + def test_http_seed(self): + ti = lt.torrent_info('base.torrent') + + ti.add_http_seed('foobar2') + ti.add_http_seed('foobar3', 'username:password') + ti.add_http_seed('foobar4', 'username:password', []) + + seeds = ti.web_seeds() + self.assertEqual(seeds, [ + {'url': 'foobar2', 'type': 1, 'auth': ''}, + {'url': 'foobar3', 'type': 1, 'auth': 'username:password'}, + {'url': 'foobar4', 'type': 1, 'auth': 'username:password'}, + ]) + +class test_alerts(unittest.TestCase): + + def test_alert(self): + + ses = lt.session(settings) + ti = lt.torrent_info('base.torrent') + h = ses.add_torrent({'ti': ti, 'save_path': os.getcwd()}) + st = h.status() + time.sleep(1) + ses.remove_torrent(h) + ses.wait_for_alert(1000) # milliseconds + alerts = ses.pop_alerts() + for a in alerts: + if a.what() == 'add_torrent_alert': + self.assertEqual(a.torrent_name, 'temp') + print(a.message()) + for field_name in dir(a): + if field_name.startswith('__'): + continue + field = getattr(a, field_name) + if callable(field): + print(' ', field_name, ' = ', field()) + else: + print(' ', field_name, ' = ', field) + + print(st.next_announce) + self.assertEqual(st.name, 'temp') + print(st.errc.message()) + print(st.pieces) + print(st.last_seen_complete) + print(st.completed_time) + print(st.progress) + print(st.num_pieces) + print(st.distributed_copies) + print(st.info_hashes) + print(st.seeding_duration) + print(st.last_upload) + print(st.last_download) + self.assertEqual(st.save_path, os.getcwd()) + + def test_alert_fs(self): + ses = lt.session(settings) + s1, s2 = socket.socketpair() + ses.set_alert_fd(s2.fileno()) + + ses.pop_alerts() + + # make sure there's an alert to wake us up + ses.post_session_stats() + + read_sockets, write_sockets, error_sockets = select.select([s1], [], []) + + self.assertEqual(len(read_sockets), 1) + for s in read_sockets: + s.recv(10) + + def test_pop_alerts(self): + ses = lt.session(settings) + ses.async_add_torrent( + {"ti": lt.torrent_info("base.torrent"), "save_path": "."}) + +# this will cause an error (because of duplicate torrents) and the +# torrent_info object created here will be deleted once the alert goes out +# of scope. When that happens, it will decrement the python object, to allow +# it to release the object. +# we're trying to catch the error described in this post, with regards to +# torrent_info. +# https://mail.python.org/pipermail/cplusplus-sig/2007-June/012130.html + ses.async_add_torrent( + {"ti": lt.torrent_info("base.torrent"), "save_path": "."}) + time.sleep(1) + for i in range(0, 10): + alerts = ses.pop_alerts() + for a in alerts: + print(a.message()) + time.sleep(0.1) + + def test_alert_notify(self): + ses = lt.session(settings) + event = threading.Event() + + def callback(): + event.set() + + ses.set_alert_notify(callback) + ses.async_add_torrent( + {"ti": lt.torrent_info("base.torrent"), "save_path": "."}) + event.wait() + + +class test_bencoder(unittest.TestCase): + + def test_bencode(self): + encoded = lt.bencode({'a': 1, 'b': [1, 2, 3], 'c': 'foo'}) + self.assertEqual(encoded, b'd1:ai1e1:bli1ei2ei3ee1:c3:fooe') + + def test_bdecode(self): + encoded = b'd1:ai1e1:bli1ei2ei3ee1:c3:fooe' + decoded = lt.bdecode(encoded) + self.assertEqual(decoded, {b'a': 1, b'b': [1, 2, 3], b'c': b'foo'}) + + def test_string(self): + encoded = lt.bencode('foo\u00e5\u00e4\u00f6') + self.assertEqual(encoded, b'9:foo\xc3\xa5\xc3\xa4\xc3\xb6') + + def test_bytes(self): + encoded = lt.bencode(b'foo') + self.assertEqual(encoded, b'3:foo') + + def test_float(self): + # TODO: this should throw a TypeError in the future + with self.assertWarns(DeprecationWarning): + encoded = lt.bencode(1.337) + self.assertEqual(encoded, b'0:') + + def test_object(self): + class FooBar: + dummy = 1 + + # TODO: this should throw a TypeError in the future + with self.assertWarns(DeprecationWarning): + encoded = lt.bencode(FooBar()) + self.assertEqual(encoded, b'0:') + + def test_preformatted(self): + encoded = lt.bencode((1, 2, 3, 4, 5)) + self.assertEqual(encoded, b'\x01\x02\x03\x04\x05') + +class test_sha1hash(unittest.TestCase): + + def test_sha1hash(self): + h = 'a0' * 20 + s = lt.sha1_hash(binascii.unhexlify(h)) + self.assertEqual(h, str(s)) + + def test_hash(self): + self.assertNotEqual(hash(lt.sha1_hash(b'b' * 20)), hash(lt.sha1_hash(b'a' * 20))) + self.assertEqual(hash(lt.sha1_hash(b'b' * 20)), hash(lt.sha1_hash(b'b' * 20))) + +class test_sha256hash(unittest.TestCase): + + def test_sha1hash(self): + h = 'a0' * 32 + s = lt.sha256_hash(binascii.unhexlify(h)) + self.assertEqual(h, str(s)) + + def test_hash(self): + self.assertNotEqual(hash(lt.sha256_hash(b'b' * 32)), hash(lt.sha256_hash(b'a' * 32))) + self.assertEqual(hash(lt.sha256_hash(b'b' * 32)), hash(lt.sha256_hash(b'b' * 32))) + +class test_info_hash(unittest.TestCase): + + def test_info_hash(self): + s1 = lt.sha1_hash(b'a' * 20) + s2 = lt.sha256_hash(b'b' * 32) + + ih1 = lt.info_hash_t(s1); + self.assertTrue(ih1.has_v1()) + self.assertFalse(ih1.has_v2()) + self.assertEqual(ih1.v1, s1) + + ih2 = lt.info_hash_t(s2); + self.assertFalse(ih2.has_v1()) + self.assertTrue(ih2.has_v2()) + self.assertEqual(ih2.v2, s2) + + ih12 = lt.info_hash_t(s1, s2); + self.assertTrue(ih12.has_v1()) + self.assertTrue(ih12.has_v2()) + self.assertEqual(ih12.v1, s1) + self.assertEqual(ih12.v2, s2) + + self.assertNotEqual(hash(ih1), hash(ih2)) + self.assertNotEqual(hash(ih1), hash(ih12)) + self.assertEqual(hash(ih1), hash(lt.info_hash_t(s1))) + self.assertEqual(hash(ih2), hash(lt.info_hash_t(s2))) + self.assertEqual(hash(ih12), hash(lt.info_hash_t(s1, s2))) + +class test_magnet_link(unittest.TestCase): + + def test_parse_magnet_uri(self): + ses = lt.session({}) + magnet = 'magnet:?xt=urn:btih:C6EIF4CCYDBTIJVG3APAGM7M4NDONCTI' + p = lt.parse_magnet_uri(magnet) + self.assertEqual(str(p.info_hashes.v1), '178882f042c0c33426a6d81e0333ece346e68a68') + p.save_path = '.' + h = ses.add_torrent(p) + self.assertEqual(str(h.info_hash()), '178882f042c0c33426a6d81e0333ece346e68a68') + self.assertEqual(str(h.info_hashes().v1), '178882f042c0c33426a6d81e0333ece346e68a68') + + def test_parse_magnet_uri_dict(self): + ses = lt.session({}) + magnet = 'magnet:?xt=urn:btih:C6EIF4CCYDBTIJVG3APAGM7M4NDONCTI' + p = lt.parse_magnet_uri_dict(magnet) + self.assertEqual(binascii.hexlify(p['info_hashes']), b'178882f042c0c33426a6d81e0333ece346e68a68') + p['save_path'] = '.' + h = ses.add_torrent(p) + self.assertEqual(str(h.info_hash()), '178882f042c0c33426a6d81e0333ece346e68a68') + self.assertEqual(str(h.info_hashes().v1), '178882f042c0c33426a6d81e0333ece346e68a68') + + def test_add_deprecated_magnet_link(self): + ses = lt.session() + atp = lt.add_torrent_params() + atp.info_hashes = lt.info_hash_t(lt.sha1_hash(b"a" * 20)) + atp.save_path = "." + h = ses.add_torrent(atp) + + self.assertTrue(h.status().info_hashes == lt.info_hash_t(lt.sha1_hash(b"a" * 20))) + + def test_add_magnet_link(self): + ses = lt.session() + atp = lt.add_torrent_params() + atp.save_path = "." + atp.info_hash = lt.sha1_hash(b"a" * 20) + h = ses.add_torrent(atp) + + self.assertTrue(h.status().info_hashes == lt.info_hash_t(lt.sha1_hash(b"a" * 20))) + + +class test_peer_class(unittest.TestCase): + + def test_peer_class_ids(self): + s = lt.session(settings) + + print('global_peer_class_id:', lt.session.global_peer_class_id) + print('tcp_peer_class_id:', lt.session.tcp_peer_class_id) + print('local_peer_class_id:', lt.session.local_peer_class_id) + + print('global: ', s.get_peer_class(s.global_peer_class_id)) + print('tcp: ', s.get_peer_class(s.local_peer_class_id)) + print('local: ', s.get_peer_class(s.local_peer_class_id)) + + def test_peer_class(self): + s = lt.session(settings) + + c = s.create_peer_class('test class') + print('new class: ', s.get_peer_class(c)) + + nfo = s.get_peer_class(c) + self.assertEqual(nfo['download_limit'], 0) + self.assertEqual(nfo['upload_limit'], 0) + self.assertEqual(nfo['ignore_unchoke_slots'], False) + self.assertEqual(nfo['connection_limit_factor'], 100) + self.assertEqual(nfo['download_priority'], 1) + self.assertEqual(nfo['upload_priority'], 1) + self.assertEqual(nfo['label'], 'test class') + + nfo['download_limit'] = 1337 + nfo['upload_limit'] = 1338 + nfo['ignore_unchoke_slots'] = True + nfo['connection_limit_factor'] = 42 + nfo['download_priority'] = 2 + nfo['upload_priority'] = 3 + + s.set_peer_class(c, nfo) + + nfo2 = s.get_peer_class(c) + self.assertEqual(nfo, nfo2) + + def test_peer_class_filter(self): + filt = lt.peer_class_type_filter() + filt.add(lt.peer_class_type_filter.tcp_socket, lt.session.global_peer_class_id) + filt.remove(lt.peer_class_type_filter.utp_socket, lt.session.local_peer_class_id) + + filt.disallow(lt.peer_class_type_filter.tcp_socket, lt.session.global_peer_class_id) + filt.allow(lt.peer_class_type_filter.utp_socket, lt.session.local_peer_class_id) + + def test_peer_class_ip_filter(self): + s = lt.session(settings) + s.set_peer_class_type_filter(lt.peer_class_type_filter()) + s.set_peer_class_filter(lt.ip_filter()) + +class test_ip_filter(unittest.TestCase): + + def test_export(self): + + f = lt.ip_filter() + self.assertEqual(f.access('1.1.1.1'), 0) + f.add_rule('1.1.1.1', '1.1.1.2', 1) + self.assertEqual(f.access('1.1.1.0'), 0) + self.assertEqual(f.access('1.1.1.1'), 1) + self.assertEqual(f.access('1.1.1.2'), 1) + self.assertEqual(f.access('1.1.1.3'), 0) + exp = f.export_filter() + self.assertEqual(exp, ([('0.0.0.0', '1.1.1.0'), ('1.1.1.1', '1.1.1.2'), ('1.1.1.3', '255.255.255.255')], [('::', 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff')])) + +class test_session(unittest.TestCase): + + def test_settings(self): + sett = { 'alert_mask': lt.alert.category_t.all_categories } + s = lt.session(sett) + sett = s.get_settings() + self.assertEqual(sett['alert_mask'] & 0x7fffffff, 0x7fffffff) + + def test_session_params(self): + sp = lt.session_params() + sp.settings = { 'alert_mask': lt.alert.category_t.all_categories } + s = lt.session(sp) + sett = s.get_settings() + self.assertEqual(sett['alert_mask'] & 0x7fffffff, 0x7fffffff) + + def test_session_params_constructor(self): + sp = lt.session_params({ 'alert_mask': lt.alert.category_t.all_categories }) + s = lt.session(sp) + sett = s.get_settings() + self.assertEqual(sett['alert_mask'] & 0x7fffffff, 0x7fffffff) + + def test_session_params_ip_filter(self): + sp = lt.session_params() + sp.ip_filter.add_rule("1.1.1.1", "1.1.1.2", 1337) + self.assertEqual(sp.ip_filter.access("1.1.1.1"), 1337) + self.assertEqual(sp.ip_filter.access("1.1.1.2"), 1337) + self.assertEqual(sp.ip_filter.access("1.1.1.3"), 0) + + def test_session_params_roundtrip_buf(self): + + sp = lt.session_params() + sp.settings = { 'alert_mask': lt.alert.category_t.all_categories } + + buf = lt.write_session_params_buf(sp) + sp2 = lt.read_session_params(buf) + self.assertEqual(sp2.settings['alert_mask'] & 0x7fffffff, 0x7fffffff) + + def test_session_params_roundtrip_entry(self): + + sp = lt.session_params() + sp.settings = { 'alert_mask': lt.alert.category_t.all_categories } + + ent = lt.write_session_params(sp) + print(ent) + sp2 = lt.read_session_params(ent) + self.assertEqual(sp2.settings['alert_mask'] & 0x7fffffff, 0x7fffffff) + + def test_add_torrent(self): + s = lt.session(settings) + h = s.add_torrent({'ti': lt.torrent_info('base.torrent'), + 'save_path': '.', + 'dht_nodes': [('1.2.3.4', 6881), ('4.3.2.1', 6881)], + 'http_seeds': ['http://test.com/seed'], + 'peers': [('5.6.7.8', 6881)], + 'banned_peers': [('8.7.6.5', 6881)], + 'file_priorities': [1, 1, 1, 2, 0]}) + + def test_find_torrent(self): + s = lt.session(settings) + h = s.add_torrent({'info_hash': b"a" * 20, + 'save_path': '.'}) + self.assertTrue(h.is_valid()) + + h2 = s.find_torrent(lt.sha1_hash(b"a" * 20)) + self.assertTrue(h2.is_valid()) + h3 = s.find_torrent(lt.sha1_hash(b"b" * 20)) + self.assertFalse(h3.is_valid()) + + self.assertEqual(h, h2) + self.assertNotEqual(h, h3) + + def test_add_torrent_info_hash(self): + s = lt.session(settings) + h = s.add_torrent({ + 'info_hash': b'a' * 20, + 'info_hashes': b'a' * 32, + 'save_path': '.'}) + + time.sleep(1) + alerts = s.pop_alerts() + + while len(alerts) > 0: + a = alerts.pop(0) + print(a) + + self.assertTrue(h.is_valid()) + self.assertEqual(h.status().info_hashes, lt.info_hash_t(lt.sha256_hash(b'a' * 32))) + + def test_session_status(self): + if not has_deprecated(): + return + + s = lt.session() + st = s.status() + print(st) + print(st.active_requests) + print(st.dht_nodes) + print(st.dht_node_cache) + print(st.dht_torrents) + print(st.dht_global_nodes) + print(st.dht_total_allocations) + + def test_apply_settings(self): + + s = lt.session(settings) + s.apply_settings({'num_want': 66, 'user_agent': 'test123'}) + self.assertEqual(s.get_settings()['num_want'], 66) + self.assertEqual(s.get_settings()['user_agent'], 'test123') + + def test_post_session_stats(self): + s = lt.session({'alert_mask': 0, 'enable_dht': False}) + s.post_session_stats() + alerts = [] + # first the stats headers log line. but not if logging is disabled + while len(alerts) == 0: + s.wait_for_alert(1000) + alerts = s.pop_alerts() + + while len(alerts) > 0: + a = alerts.pop(0) + print(a) + if isinstance(a, lt.session_stats_header_alert): + break + self.assertTrue(isinstance(a, lt.session_stats_header_alert)) + # then the actual stats values + while len(alerts) == 0: + s.wait_for_alert(1000) + alerts = s.pop_alerts() + a = alerts.pop(0) + print(a) + self.assertTrue(isinstance(a, lt.session_stats_alert)) + self.assertTrue(isinstance(a.values, dict)) + self.assertTrue(len(a.values) > 0) + + def test_post_dht_stats(self): + s = lt.session({'alert_mask': 0, 'enable_dht': False}) + s.post_dht_stats() + alerts = [] + cnt = 0 + while len(alerts) == 0: + s.wait_for_alert(1000) + alerts = s.pop_alerts() + cnt += 1 + if cnt > 60: + print('no dht_stats_alert in 1 minute!') + sys.exit(1) + a = alerts.pop(0) + self.assertTrue(isinstance(a, lt.dht_stats_alert)) + self.assertTrue(isinstance(a.active_requests, list)) + self.assertTrue(isinstance(a.routing_table, list)) + + def test_unknown_settings(self): + try: + lt.session({'unexpected-key-name': 42}) + self.assertFalse('should have thrown an exception') + except KeyError as e: + print(e) + + def test_fingerprint(self): + self.assertEqual(lt.generate_fingerprint('LT', 0, 1, 2, 3), '-LT0123-') + self.assertEqual(lt.generate_fingerprint('..', 10, 1, 2, 3), '-..A123-') + + def test_min_memory_preset(self): + min_mem = lt.min_memory_usage() + print(min_mem) + + self.assertTrue('allow_idna' not in min_mem) + self.assertTrue('connection_speed' in min_mem) + self.assertTrue('file_pool_size' in min_mem) + + def test_seed_mode_preset(self): + seed_mode = lt.high_performance_seed() + print(seed_mode) + + self.assertTrue('alert_queue_size' in seed_mode) + self.assertTrue('connection_speed' in seed_mode) + self.assertTrue('file_pool_size' in seed_mode) + + def test_default_settings(self): + + default = lt.default_settings() + print(default) + + +class test_example_client(unittest.TestCase): + + def test_execute_client(self): + if os.name == 'nt': + # TODO: fix windows includes of client.py + return + my_stdin = sys.stdin + if os.name != 'nt': + master_fd, slave_fd = pty.openpty() + # slave_fd fix multiple stdin assignment at termios.tcgetattr + my_stdin = slave_fd + + process = sub.Popen( + [sys.executable, "client.py", "url_seed_multi.torrent"], + stdin=my_stdin, stdout=sub.PIPE, stderr=sub.PIPE) + # python2 has no Popen.wait() timeout + time.sleep(5) + returncode = process.poll() + if returncode is None: + # this is an expected use-case + process.kill() + err = process.stderr.read().decode("utf-8") + self.assertEqual('', err, 'process throw errors: \n' + err) + # check error code if process did unexpected end + if returncode is not None: + # in case of error return: output stdout if nothing was on stderr + if returncode != 0: + print("stdout:\n" + process.stdout.read().decode("utf-8")) + self.assertEqual(returncode, 0, "returncode: " + str(returncode) + "\n" + + "stderr: empty\n" + + "some configuration does not output errors like missing module members," + + "try to call it manually to get the error message\n") + + def test_execute_simple_client(self): + process = sub.Popen( + [sys.executable, "simple_client.py", "url_seed_multi.torrent"], + stdout=sub.PIPE, stderr=sub.PIPE) + # python2 has no Popen.wait() timeout + time.sleep(5) + returncode = process.poll() + if returncode is None: + # this is an expected use-case + process.kill() + err = process.stderr.read().decode("utf-8") + self.assertEqual('', err, 'process throw errors: \n' + err) + # check error code if process did unexpected end + if returncode is not None: + # in case of error return: output stdout if nothing was on stderr + if returncode != 0: + print("stdout:\n" + process.stdout.read().decode("utf-8")) + self.assertEqual(returncode, 0, "returncode: " + str(returncode) + "\n" + + "stderr: empty\n" + + "some configuration does not output errors like missing module members," + + "try to call it manually to get the error message\n") + + def test_execute_make_torrent(self): + process = sub.Popen( + [sys.executable, "make_torrent.py", "url_seed_multi.torrent", + "http://test.com/test"], stdout=sub.PIPE, stderr=sub.PIPE) + returncode = process.wait() + # python2 has no Popen.wait() timeout + err = process.stderr.read().decode("utf-8") + self.assertEqual('', err, 'process throw errors: \n' + err) + # in case of error return: output stdout if nothing was on stderr + if returncode != 0: + print("stdout:\n" + process.stdout.read().decode("utf-8")) + self.assertEqual(returncode, 0, "returncode: " + str(returncode) + "\n" + + "stderr: empty\n" + + "some configuration does not output errors like missing module members," + + "try to call it manually to get the error message\n") + + def test_default_settings(self): + + default = lt.default_settings() + self.assertNotIn('', default) + print(default) + + +class test_operation_t(unittest.TestCase): + + def test_enum(self): + self.assertEqual(lt.operation_name(lt.operation_t.sock_accept), "sock_accept") + self.assertEqual(lt.operation_name(lt.operation_t.unknown), "unknown") + self.assertEqual(lt.operation_name(lt.operation_t.mkdir), "mkdir") + self.assertEqual(lt.operation_name(lt.operation_t.partfile_write), "partfile_write") + self.assertEqual(lt.operation_name(lt.operation_t.hostname_lookup), "hostname_lookup") + + +class test_error_code(unittest.TestCase): + + def test_error_code(self): + + a = lt.error_code() + a = lt.error_code(10, lt.libtorrent_category()) + self.assertEqual(a.category().name(), 'libtorrent') + + self.assertEqual(lt.libtorrent_category().name(), 'libtorrent') + self.assertEqual(lt.upnp_category().name(), 'upnp') + self.assertEqual(lt.http_category().name(), 'http') + self.assertEqual(lt.socks_category().name(), 'socks') + self.assertEqual(lt.bdecode_category().name(), 'bdecode') + self.assertEqual(lt.generic_category().name(), 'generic') + self.assertEqual(lt.system_category().name(), 'system') + + +class test_peer_info(unittest.TestCase): + + def test_peer_info_members(self): + + p = lt.peer_info() + + print(p.client) + print(p.pieces) + print(p.pieces) + print(p.last_request) + print(p.last_active) + print(p.flags) + print(p.source) + print(p.pid) + print(p.downloading_piece_index) + print(p.ip) + print(p.local_endpoint) + print(p.read_state) + print(p.write_state) + + +class test_dht_settings(unittest.TestCase): + + def test_dht_get_peers(self): + session = lt.session(); + info_hash = lt.sha1_hash(b"a" * 20) + session.dht_get_peers(info_hash); + + def test_construct(self): + + ds = lt.dht_settings() + + print(ds.max_peers_reply) + print(ds.search_branching) + print(ds.max_fail_count) + print(ds.max_fail_count) + print(ds.max_torrents) + print(ds.max_dht_items) + print(ds.restrict_routing_ips) + print(ds.restrict_search_ips) + print(ds.max_torrent_search_reply) + print(ds.extended_routing_table) + print(ds.aggressive_lookups) + print(ds.privacy_lookups) + print(ds.enforce_node_id) + print(ds.ignore_dark_internet) + print(ds.block_timeout) + print(ds.block_ratelimit) + print(ds.read_only) + print(ds.item_lifetime) + + +def get_isolated_settings(): + return { + "enable_dht": False, + "enable_lsd": False, + "enable_natpmp": False, + "enable_upnp": False, + "listen_interfaces": "127.0.0.1:0", + "dht_bootstrap_nodes": "", + } + + +def loop_until_timeout(timeout, msg="condition"): + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + yield + raise AssertionError(f"{msg} timed out") + + +def unlink_all_files(path): + for dirpath, _, filenames in os.walk(path): + for filename in filenames: + filepath = os.path.join(dirpath, filename) + os.unlink(filepath) + + +# In test cases where libtorrent writes torrent data in a temporary directory, +# cleaning up the tempdir on Windows CI sometimes fails with a PermissionError +# having WinError 5 (Access Denied). I can't repro this WinError in any way; +# holding an open file handle results in a different WinError. Seems to be a +# race condition which only happens with very short-lived tests which write +# data. Work around by cleaning up the tempdir in a loop. + +# TODO: why is this necessary? +def cleanup_with_windows_fix(tempdir, *, timeout): + # Clean up just the files, so we don't have to bother with depth-first + # traversal + for _ in loop_until_timeout(timeout, msg="PermissionError clear"): + try: + unlink_all_files(tempdir.name) + except PermissionError: + if sys.platform == "win32": + # current release of mypy doesn't know about winerror + # if exc.winerror == 5: + continue + raise + break + # This removes directories in depth-first traversal. + # It also marks the tempdir as explicitly cleaned so it doesn't trigger a + # ResourceWarning. + tempdir.cleanup() + + +def wait_for(session, alert_type, *, timeout, prefix=None): + # Return the first alert of type, but log all alerts. + result = None + for _ in loop_until_timeout(timeout, msg=alert_type.__name__): + for alert in session.pop_alerts(): + print(f"{alert.what()}: {alert.message()}") + if result is None and isinstance(alert, alert_type): + result = alert + if result is not None: + return result + raise AssertionError("unreachable") + + +class LambdaRequestHandler(http.server.BaseHTTPRequestHandler): + default_request_version = "HTTP/1.1" + + def __init__(self, get_data, *args, **kwargs): + self.get_data = get_data + super().__init__(*args, **kwargs) + + def do_GET(self): + print(f"mock tracker request: {self.requestline}") + data = self.get_data() + self.send_response(200) + self.send_header("Content-Type", "application/octet-stream") + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + + +class SSLTrackerAlertTest(unittest.TestCase): + + def setUp(self): + self.cert_path = os.path.realpath(os.path.join( + os.path.dirname(__file__), "..", "..", "test", "ssl", "server.pem" + )) + print(f"cert_path = {self.cert_path}") + + self.tracker_response = { + b"external ip": b"\x01\x02\x03\x04", + } + self.tracker = http.server.HTTPServer( + ("127.0.0.1", 0), + functools.partial( + LambdaRequestHandler, lambda: lt.bencode(self.tracker_response) + ), + ) + self.ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + self.ctx.load_cert_chain(self.cert_path) + self.tracker.socket = self.ctx.wrap_socket( + self.tracker.socket, server_side=True + ) + self.tracker_thread = threading.Thread(target=self.tracker.serve_forever) + self.tracker_thread.start() + # HTTPServer.server_name seems to resolve to things like + # "localhost.localdomain" + port = self.tracker.server_port + self.tracker_url = f"https://127.0.0.1:{port}/announce" + print(f"mock tracker url = {self.tracker_url}") + + self.settings = get_isolated_settings() + self.settings["alert_mask"] = lt.alert_category.status + # I couldn't get validation to work on all platforms. Setting + # SSL_CERT_FILE to our self-signed cert works on linux and mac, but + # not on Windows. + self.settings["validate_https_trackers"] = False + self.session = lt.session(self.settings) + self.dir = tempfile.TemporaryDirectory() + self.atp = lt.add_torrent_params() + self.atp.info_hash = dummy_data.get_sha1_hash() + self.atp.flags &= ~lt.torrent_flags.auto_managed + self.atp.flags &= ~lt.torrent_flags.paused + self.atp.save_path = self.dir.name + + def tearDown(self): + # we do this because sessions writing data can collide with + # cleaning up temporary directories. session.abort() isn't bound + handles = self.session.get_torrents() + for handle in handles: + self.session.remove_torrent(handle) + for _ in loop_until_timeout(5, msg="clear all handles"): + if not any(handle.is_valid() for handle in handles): + break + cleanup_with_windows_fix(self.dir, timeout=5) + self.tracker.shutdown() + # Explicitly clean up server sockets, to avoid ResourceWarning + self.tracker.server_close() + + def test_external_ip_alert_via_ssl_tracker(self): + handle = self.session.add_torrent(self.atp) + handle.add_tracker({"url": self.tracker_url}) + + alert = wait_for(self.session, lt.external_ip_alert, timeout=60) + + self.assertEqual(alert.category(), lt.alert_category.status) + self.assertEqual(alert.what(), "external_ip") + self.assertIsInstance(alert.message(), str) + self.assertNotEqual(alert.message(), "") + self.assertEqual(str(alert), alert.message()) + self.assertEqual(alert.external_address, "1.2.3.4") + + +if __name__ == '__main__': + print(lt.__version__) + try: + shutil.copy(os.path.join('..', '..', 'test', 'test_torrents', + 'url_seed_multi.torrent'), '.') + except shutil.SameFileError: + pass + try: + shutil.copy(os.path.join('..', '..', 'test', 'test_torrents', + 'base.torrent'), '.') + except shutil.SameFileError: + pass + try: + shutil.copy(os.path.join('..', '..', 'test', 'test_torrents', + 'unordered.torrent'), '.') + except shutil.SameFileError: + pass + unittest.main() diff --git a/clang_tidy.jam b/clang_tidy.jam new file mode 100644 index 0000000..b80948b --- /dev/null +++ b/clang_tidy.jam @@ -0,0 +1,105 @@ +# Copyright (c) 2018 Arvid Norberg (arvid@libtorrent.org) +# +# Use, modification and distribution is subject to the Boost Software +# License Version 1.0. (See accompanying file LICENSE_1_0.txt or +# http://www.boost.org/LICENSE_1_0.txt) + +import common ; +import toolset ; +import feature ; + +feature.extend toolset : clang_tidy ; + +generators.register-c-compiler clang_tidy.compile.c++ : CPP : OBJ : clang_tidy ; +generators.register-c-compiler clang_tidy.compile.c : C : OBJ : clang_tidy ; +generators.register-archiver clang_tidy.archive : OBJ : STATIC_LIB : clang_tidy ; +generators.register-linker clang_tidy.link : OBJ SEARCHED_LIB STATIC_LIB : EXE : clang_tidy ; +generators.register-linker clang_tidy.link.dll : OBJ SEARCHED_LIB STATIC_LIB : SHARED_LIB : clang_tidy ; + +rule init ( version ? : command * : options * ) { + command = [ common.get-invocation-command clang_tidy : clang-tidy + : $(command) ] ; + + # Determine the version + if $(command) { + local command-string = \"$(command)\" ; + command-string = $(command-string:J=" ") ; + version ?= [ MATCH "version ([0-9.]+)" + : [ SHELL "$(command-string) --version" ] ] ; + } + + local condition = [ common.check-init-parameters clang_tidy + : version $(version) ] ; + + common.handle-options clang_tidy : $(condition) : $(command) : $(options) ; +} + +############################################################################### +# Flags + +toolset.flags clang_tidy.compile OPTIONS ; +toolset.flags clang_tidy.compile.c++ OPTIONS ; + +toolset.flags clang_tidy.compile DEFINES ; +toolset.flags clang_tidy.compile INCLUDES ; + +toolset.flags clang_tidy.compile OPTIONS off : ; +toolset.flags clang_tidy.compile OPTIONS speed : -O3 ; +toolset.flags clang_tidy.compile OPTIONS space : -Os ; + +toolset.flags clang_tidy.compile OPTIONS off : -fno-inline ; +# For clang, 'on' and 'full' are identical. +toolset.flags clang_tidy.compile OPTIONS on : -Wno-inline ; +toolset.flags clang_tidy.compile OPTIONS full : -Wno-inline ; + +toolset.flags clang_tidy.compile OPTIONS off : -w ; +toolset.flags clang_tidy.compile OPTIONS on : -Wall ; +toolset.flags clang_tidy.compile OPTIONS all : -Wall -pedantic ; +toolset.flags clang_tidy.compile OPTIONS on : -Werror ; + +toolset.flags clang_tidy.compile OPTIONS on : -g ; +toolset.flags clang_tidy.compile OPTIONS on : -pg ; +toolset.flags clang_tidy.compile OPTIONS off : -fno-rtti ; + +local rule cxxstd-flags ( toolset : condition * : options * ) +{ + toolset.flags $(toolset).compile.c++ OPTIONS $(condition) : $(options) : unchecked ; + toolset.flags $(toolset).link OPTIONS $(condition) : $(options) : unchecked ; +} + +cxxstd-flags clang_tidy : 11/iso : -std=c++11 ; +cxxstd-flags clang_tidy : 11/gnu : -std=gnu++11 ; +cxxstd-flags clang_tidy : 14/iso : -std=c++14 ; +cxxstd-flags clang_tidy : 14/gnu : -std=gnu++14 ; +cxxstd-flags clang_tidy : 17/iso : -std=c++17 ; +cxxstd-flags clang_tidy : 17/gnu : -std=gnu++17 ; + +############################################################################### +# C and C++ compilation + +TOUCH = [ common.file-creation-command ] ; + +actions compile.c++ { + "$(CONFIG_COMMAND)" -quiet -header-filter=* -warnings-as-errors=* "$(>)" -- -x c++ $(OPTIONS) -D$(DEFINES) -I"$(INCLUDES)" \ + && $(TOUCH) "$(<)" +} + +actions compile.c { + "$(CONFIG_COMMAND)" -quiet -header-filter=* -warnings-as-errors=* "$(>)" -- -x c $(OPTIONS) -D$(DEFINES) -I"$(INCLUDES)" \ + && $(TOUCH) "$(<)" +} + +############################################################################### +# Linking + +actions archive { + $(TOUCH) "$(<)" +} + +actions link { + $(TOUCH) "$(<)" +} + +actions link.dll { + $(TOUCH) "$(<)" +} diff --git a/cmake/Modules/FindLibGcrypt.cmake b/cmake/Modules/FindLibGcrypt.cmake new file mode 100644 index 0000000..dcf1c98 --- /dev/null +++ b/cmake/Modules/FindLibGcrypt.cmake @@ -0,0 +1,127 @@ +#.rst: +# FindLibGcrypt +# ------------- +# +# Try to find libgcrypt. +# +# This will define the following variables: +# +# ``LibGcrypt_FOUND`` +# True if libgcrypt is available. +# +# ``LibGcrypt_VERSION`` +# The version of LibGcrypt +# +# ``LibGcrypt_INCLUDE_DIRS`` +# This should be passed to target_include_directories() if +# the target is not used for linking +# +# ``LibGcrypt_LIBRARIES`` +# This can be passed to target_link_libraries() instead of +# the ``LibGcrypt::LibGcrypt`` target +# +# If ``LibGcrypt_FOUND`` is TRUE, the following imported target +# will be available: +# +# ``LibGcrypt::LibGcrypt`` +# The libgcrypt library +# +# Since 1.9.50. + +#============================================================================= +# Copyright 2007 Charles Connell (This was based upon FindKopete.cmake) +# Copyright 2010 Joris Guisson +# Copyright 2016 Christophe Giboudeaux +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#============================================================================= + +find_path(LibGcrypt_INCLUDE_DIRS + NAMES gcrypt.h + PATH_SUFFIXES libgcrypt +) + +find_library(LibGcrypt_LIBRARIES + NAMES gcrypt +) + +if(MSVC) + find_library(LibGcrypt_LIBRARIES_DEBUG + NAMES gcryptd + ) + + if(NOT LibGcrypt_LIBRARIES_DEBUG) + unset(LibGcrypt_LIBRARIES CACHE) + endif() + + if(MSVC_IDE) + if(NOT (LibGcrypt_LIBRARIES_DEBUG AND LibGcrypt_LIBRARIES)) + message(STATUS + "\nCould NOT find the debug AND release version of the libgcrypt library.\n + You need to have both to use MSVC projects.\n + Please build and install both libgcrypt libraries first.\n" + ) + unset(LibGcrypt_LIBRARIES CACHE) + endif() + else() + string(TOLOWER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_TOLOWER) + if(CMAKE_BUILD_TYPE_TOLOWER MATCHES debug) + set(LibGcrypt_LIBRARIES ${LibGcrypt_LIBRARIES_DEBUG}) + endif() + endif() +endif() + +# Get version from gcrypt.h +# #define GCRYPT_VERSION "1.6.4" +if(LibGcrypt_INCLUDE_DIRS AND LibGcrypt_LIBRARIES) + file(STRINGS ${LibGcrypt_INCLUDE_DIRS}/gcrypt.h _GCRYPT_H REGEX "^#define GCRYPT_VERSION[ ]+.*$") + string(REGEX REPLACE "^.*GCRYPT_VERSION[ ]+\"([0-9]+).([0-9]+).([0-9]+).*\".*$" "\\1" LibGcrypt_MAJOR_VERSION "${_GCRYPT_H}") + string(REGEX REPLACE "^.*GCRYPT_VERSION[ ]+\"([0-9]+).([0-9]+).([0-9]+).*\".*$" "\\2" LibGcrypt_MINOR_VERSION "${_GCRYPT_H}") + string(REGEX REPLACE "^.*GCRYPT_VERSION[ ]+\"([0-9]+).([0-9]+).([0-9]+).*\".*$" "\\3" LibGcrypt_PATCH_VERSION "${_GCRYPT_H}") + + set(LibGcrypt_VERSION "${LibGcrypt_MAJOR_VERSION}.${LibGcrypt_MINOR_VERSION}.${LibGcrypt_PATCH_VERSION}") + unset(_GCRYPT_H) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LibGcrypt + FOUND_VAR LibGcrypt_FOUND + REQUIRED_VARS LibGcrypt_INCLUDE_DIRS LibGcrypt_LIBRARIES + VERSION_VAR LibGcrypt_VERSION +) + +if(LibGcrypt_FOUND AND NOT TARGET LibGcrypt::LibGcrypt) + add_library(LibGcrypt::LibGcrypt UNKNOWN IMPORTED) + set_target_properties(LibGcrypt::LibGcrypt PROPERTIES + IMPORTED_LOCATION "${LibGcrypt_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${LibGcrypt_INCLUDE_DIRS}") +endif() + +mark_as_advanced(LibGcrypt_INCLUDE_DIRS LibGcrypt_LIBRARIES) + +include(FeatureSummary) +set_package_properties(LibGcrypt PROPERTIES + URL "http://directory.fsf.org/wiki/Libgcrypt" + DESCRIPTION "General purpose crypto library based on the code used in GnuPG." +) diff --git a/cmake/Modules/GeneratePkgConfig.cmake b/cmake/Modules/GeneratePkgConfig.cmake new file mode 100644 index 0000000..49a14a6 --- /dev/null +++ b/cmake/Modules/GeneratePkgConfig.cmake @@ -0,0 +1,183 @@ +# This module provides generate_and_install_pkg_config_file() function. +# The function takes target name and expects a fully configured project, i.e. with set version and +# description. The function extracts interface libraries, include dirs, definitions and options +# from the target and generates pkg-config file with install() command +# The function expands imported targets and generator expressions + +# save the current file dir for later use in the generate_and_install_pkg_config_file() function +set(_GeneratePkGConfigDir "${CMAKE_CURRENT_LIST_DIR}/GeneratePkgConfig") + +include(GNUInstallDirs) + +function(_get_target_property_merging_configs _var_name _target_name _property_name) + get_property(prop_set TARGET ${_target_name} PROPERTY ${_property_name} SET) + if (prop_set) + get_property(vals TARGET ${_target_name} PROPERTY ${_property_name}) + else() + if (CMAKE_BUILD_TYPE) + list(APPEND configs ${CMAKE_BUILD_TYPE}) + elseif(CMAKE_CONFIGURATION_TYPES) + list(APPEND configs ${CMAKE_CONFIGURATION_TYPES}) + endif() + foreach(cfg ${configs}) + string(TOUPPER "${cfg}" UPPERCFG) + get_property(mapped_configs TARGET ${_target_name} PROPERTY "MAP_IMPORTED_CONFIG_${UPPERCFG}") + if (mapped_configs) + list(GET "${mapped_configs}" 0 target_cfg) + else() + set(target_cfg "${UPPERCFG}") + endif() + get_property(prop_set TARGET ${_target_name} PROPERTY ${_property_name}_${target_cfg} SET) + if (prop_set) + get_property(val_for_cfg TARGET ${_target_name} PROPERTY ${_property_name}_${target_cfg}) + list(APPEND vals "$<$:${val_for_cfg}>") + break() + endif() + endforeach() + if (NOT prop_set) + get_property(imported_cfgs TARGET ${_target_name} PROPERTY IMPORTED_CONFIGURATIONS) + # CMake docs say we can use any of the imported configs + list(GET imported_cfgs 0 imported_config) + get_property(vals TARGET ${_target_name} PROPERTY ${_property_name}_${imported_config}) + # remove config generator expression. Only in this case! Notice we use such expression + # ourselves in the loop above + string(REPLACE "$<$:" "$<1:" vals "${vals}") + endif() + endif() + # HACK for static libraries cmake populates link dependencies as $. + # pkg-config does not support special handling for static libraries and as such we will remove + # that generator expression + string(REPLACE "$" "" _interface_compile_options "${_interface_compile_options}") + endif() + + # put target and project properties into a file + configure_file("${_GeneratePkGConfigDir}/target-compile-settings.cmake.in" + "${_generate_target_dir}/compile-settings.cmake" @ONLY) + + get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if (NOT _isMultiConfig) + set(_variables_file_name "${_generate_target_dir}/compile-settings-expanded.cmake") + + file(GENERATE OUTPUT "${_variables_file_name}" INPUT "${_generate_target_dir}/compile-settings.cmake" ${_target_arg}) + + configure_file("${_GeneratePkGConfigDir}/generate-pkg-config.cmake.in" + "${_generate_target_dir}/generate-pkg-config.cmake" @ONLY) + + install(SCRIPT "${_generate_target_dir}/generate-pkg-config.cmake") + else() + foreach(cfg IN LISTS CMAKE_CONFIGURATION_TYPES) + set(_variables_file_name "${_generate_target_dir}/${cfg}/compile-settings-expanded.cmake") + + file(GENERATE OUTPUT "${_variables_file_name}" INPUT "${_generate_target_dir}/compile-settings.cmake" CONDITION "$" ${_target_arg}) + + configure_file("${_GeneratePkGConfigDir}/generate-pkg-config.cmake.in" + "${_generate_target_dir}/${cfg}/generate-pkg-config.cmake" @ONLY) + + install(SCRIPT "${_generate_target_dir}/${cfg}/generate-pkg-config.cmake") + endforeach() + endif() +endfunction() diff --git a/cmake/Modules/GeneratePkgConfig/generate-pkg-config.cmake.in b/cmake/Modules/GeneratePkgConfig/generate-pkg-config.cmake.in new file mode 100644 index 0000000..6adea52 --- /dev/null +++ b/cmake/Modules/GeneratePkgConfig/generate-pkg-config.cmake.in @@ -0,0 +1,60 @@ +cmake_policy(SET CMP0007 NEW) +cmake_policy(SET CMP0011 NEW) + +include("@_variables_file_name@") + +function (cmake_list_to_pkg_config _result _list _prefix) + set(_tmp_list "${_list}") + list(REMOVE_ITEM _tmp_list "") + # remove prefix from prefixed items + string(REGEX REPLACE "(^|;)(${_prefix})" "\\1" _tmp_list "${_tmp_list}") + # append 'prefix' to each element + string(REGEX REPLACE "([^;]+)" "${_prefix}\\1" _tmp_list "${_tmp_list}") + # transform cmake list into a space delimited list + string(REPLACE ";" " " _tmp_list "${_tmp_list}") + set(${_result} "${_tmp_list}" PARENT_SCOPE) +endfunction() + +# Helper function for splitting full library paths into [dir, name] and merging repetitive dir entries +function(split_library_dirs _libraries _base_library_dir _library_dirs_var _library_names_var) + set(libdirs "${_base_library_dir}") + set(libs "") + foreach (l IN LISTS _libraries) + get_filename_component(lDir "${l}" DIRECTORY) + if (lDir) + get_filename_component(lDir "${lDir}" REALPATH) + endif() + get_filename_component(lFile "${l}" NAME_WE) + string(REPLACE "${_SHARED_LIBRARY_PREFIX}" "" lFile "${lFile}") + list(APPEND libdirs "${lDir}") + list(APPEND libs "${lFile}") + endforeach() + list(REMOVE_DUPLICATES libdirs) + list(REMOVE_AT libdirs 0) # as it is the base libdir and will be handled separately + + set(${_library_dirs_var} "${libdirs}" PARENT_SCOPE) + set(${_library_names_var} "${libs}" PARENT_SCOPE) +endfunction() + +split_library_dirs("${_TARGET_INTERFACE_LINK_LIBRARIES}" "${CMAKE_INSTALL_PREFIX}/${_INSTALL_LIBDIR}" _lib_dirs _library_names) +set(_linker_options "${_library_names}") +list(FILTER _linker_options INCLUDE REGEX "^-.*") +list(FILTER _library_names EXCLUDE REGEX "^-.*") +cmake_list_to_pkg_config(_libs "${_library_names}" "-l") +list(JOIN _linker_options " " _linker_options) +string(JOIN " " _libs "${_linker_options}" "${_libs}") +list(LENGTH _lib_dirs _additional_libdirs_count) +if (_additional_libdirs_count GREATER 0) + cmake_list_to_pkg_config(_additional_libdirs "${_lib_dirs}" "-L") + set(_interface_link_libraries "${_additional_libdirs} ${_libs}") +else() + set(_interface_link_libraries "${_libs}") +endif() + +cmake_list_to_pkg_config(_interface_definitions "${_TARGET_INTERFACE_DEFINITIONS}" "-D") +cmake_list_to_pkg_config(_interface_include_dirs "${_TARGET_INTERFACE_INCLUDE_DIRS}" "-I") +set(_interface_compile_options "${_TARGET_INTERFACE_COMPILE_OPTIONS}") +string(REPLACE ";" " " _interface_compile_options "${_interface_compile_options}") + +configure_file("@_pkg_config_file_template_filename@" "@_generate_target_dir@/@_package_name@.pc" @ONLY) +file(INSTALL "@_generate_target_dir@/@_package_name@.pc" DESTINATION "@CMAKE_INSTALL_FULL_LIBDIR@/pkgconfig") diff --git a/cmake/Modules/GeneratePkgConfig/pkg-config.cmake.in b/cmake/Modules/GeneratePkgConfig/pkg-config.cmake.in new file mode 100644 index 0000000..4354134 --- /dev/null +++ b/cmake/Modules/GeneratePkgConfig/pkg-config.cmake.in @@ -0,0 +1,8 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +libdir=${prefix}/@_INSTALL_LIBDIR@ + +Name: @_PROJECT_NAME@ +Description: @_PROJECT_DESCRIPTION@ +Version: @_PROJECT_VERSION@ +Libs: -L${libdir} -l@_TARGET_OUTPUT_NAME@ @_interface_link_libraries@ +Cflags: @_interface_compile_options@ @_interface_include_dirs@ @_interface_definitions@ diff --git a/cmake/Modules/GeneratePkgConfig/target-compile-settings.cmake.in b/cmake/Modules/GeneratePkgConfig/target-compile-settings.cmake.in new file mode 100644 index 0000000..e16d7bc --- /dev/null +++ b/cmake/Modules/GeneratePkgConfig/target-compile-settings.cmake.in @@ -0,0 +1,13 @@ +set(_TARGET_INTERFACE_LINK_LIBRARIES "@_interface_link_libraries@") +set(_TARGET_INTERFACE_COMPILE_OPTIONS "@_interface_compile_options@") +set(_TARGET_INTERFACE_INCLUDE_DIRS "@_interface_include_dirs@") +set(_TARGET_INTERFACE_DEFINITIONS "@_interface_definitions@") +set(_TARGET_OUTPUT_NAME "@_output_name@") + +set(_INSTALL_LIBDIR "@CMAKE_INSTALL_LIBDIR@") +set(_INSTALL_INCLUDEDIR "@CMAKE_INSTALL_INCLUDEDIR@") +set(_SHARED_LIBRARY_PREFIX "@CMAKE_SHARED_LIBRARY_PREFIX@") + +set(_PROJECT_NAME "@PROJECT_NAME@") +set(_PROJECT_DESCRIPTION "@PROJECT_DESCRIPTION@") +set(_PROJECT_VERSION "@PROJECT_VERSION@") diff --git a/cmake/Modules/LibtorrentMacros.cmake b/cmake/Modules/LibtorrentMacros.cmake new file mode 100644 index 0000000..a49462c --- /dev/null +++ b/cmake/Modules/LibtorrentMacros.cmake @@ -0,0 +1,80 @@ +# Various helper function and macros for building libtorrent + +include(FeatureSummary) + +# macro for issuing option() and add_feature_info() in a single call. +# Synopsis: +# feature_option( ) +macro(feature_option _name _description _default) + option(${_name} "${_description}" ${_default}) + add_feature_info(${_name} ${_name} "${_description}") +endmacro() + +# function to add a simple build option which controls compile definition(s) for a target. +# Synopsis: +# target_optional_compile_definitions( [FEATURE] +# NAME DESCRIPTION DEFAULT +# [ENABLED [enabled_compile_definitions...]] +# [DISABLED [disabled_compile_definitions...]] +# ) +# NAME, DESCRIPTION and DEFAULT are passed to option() call +# if FEATURE is given, they are passed to add_feature_info() +# ENABLED lists compile definitions that will be set on when option is enabled, +# DISABLED lists definitions that will be set otherwise +function(target_optional_compile_definitions _target _scope) + set(options FEATURE) + set(oneValueArgs NAME DESCRIPTION DEFAULT) + set(multiValueArgs ENABLED DISABLED) + cmake_parse_arguments(TOCD ${options} "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + option(${TOCD_NAME} "${TOCD_DESCRIPTION}" ${TOCD_DEFAULT}) + if (${${TOCD_NAME}}) + target_compile_definitions(${_target} ${_scope} ${TOCD_ENABLED}) + else() + target_compile_definitions(${_target} ${_scope} ${TOCD_DISABLED}) + endif() + if(${TOCD_FEATURE}) + add_feature_info(${TOCD_NAME} ${TOCD_NAME} "${TOCD_DESCRIPTION}") + endif() +endfunction() + +# a helper macro that calls find_package() and appends the package (if found) to the +# _package_dependencies list, which can be used later to generate package config file +macro(find_public_dependency _name) + find_package(${_name} ${ARGN}) + string(TOUPPER "${_name}" _name_uppercased) + if (${_name}_FOUND OR ${_name_uppercased}_FOUND) + # Dependencies to be used below for generating Config.cmake file + # We don't need the 'REQUIRED' argument there + set(_args "${_name}") + list(APPEND _args "${ARGN}") + list(REMOVE_ITEM _args "REQUIRED") + list(REMOVE_ITEM _args "") # just in case + string(REPLACE ";" " " _args "${_args}") + list(APPEND _package_dependencies "${_args}") + endif() +endmacro() + +# function for parsing version variables that are set in version.hpp file +# the version identifiers there are defined as follows: +# #define LIBTORRENT_VERSION_MAJOR 1 +# #define LIBTORRENT_VERSION_MINOR 2 +# #define LIBTORRENT_VERSION_TINY 0 + +function(read_version _verFile _outVarMajor _outVarMinor _outVarTiny) + file(STRINGS ${_verFile} verFileContents REGEX ".+LIBTORRENT_VERSION_[A-Z]+.[0-9]+.*") +# message(STATUS "version file contents: ${verFileContents}") + # the verFileContents variable contains something like the following: + # #define LIBTORRENT_VERSION_MAJOR 1;#define LIBTORRENT_VERSION_MINOR 2;#define LIBTORRENT_VERSION_TINY 0 + set(_regex ".+_MAJOR +([0-9]+);.+_MINOR +([0-9]+);.+_TINY +([0-9]+)") + # note quotes around _regex, they are needed because the variable contains semicolons + string(REGEX MATCH "${_regex}" _tmp "${verFileContents}") + if (NOT _tmp) + message(FATAL_ERROR "Could not detect project version number from ${_verFile}") + endif() + +# message(STATUS "Matched version string: ${_tmp}") + + set(${_outVarMajor} ${CMAKE_MATCH_1} PARENT_SCOPE) + set(${_outVarMinor} ${CMAKE_MATCH_2} PARENT_SCOPE) + set(${_outVarTiny} ${CMAKE_MATCH_3} PARENT_SCOPE) +endfunction() diff --git a/cmake/Modules/ucm_flags.cmake b/cmake/Modules/ucm_flags.cmake new file mode 100644 index 0000000..7b3af2a --- /dev/null +++ b/cmake/Modules/ucm_flags.cmake @@ -0,0 +1,118 @@ +# taken from https://github.com/onqtam/ucm/blob/master/cmake/ucm.cmake + +# +# ucm.cmake - useful cmake macros +# +# Copyright (c) 2016 Viktor Kirilov +# +# Distributed under the MIT Software License +# See accompanying file LICENSE.txt or copy at +# https://opensource.org/licenses/MIT +# +# The documentation can be found at the library's page: +# https://github.com/onqtam/ucm + +include(CMakeParseArguments) + +# ucm_gather_flags +# Gathers all lists of flags for printing or manipulation +macro(ucm_gather_flags with_linker result) + set(${result} "") + # add the main flags without a config + list(APPEND ${result} CMAKE_C_FLAGS) + list(APPEND ${result} CMAKE_CXX_FLAGS) + if(${with_linker}) + list(APPEND ${result} CMAKE_EXE_LINKER_FLAGS) + list(APPEND ${result} CMAKE_MODULE_LINKER_FLAGS) + list(APPEND ${result} CMAKE_SHARED_LINKER_FLAGS) + list(APPEND ${result} CMAKE_STATIC_LINKER_FLAGS) + endif() + + if("${CMAKE_CONFIGURATION_TYPES}" STREQUAL "" AND NOT "${CMAKE_BUILD_TYPE}" STREQUAL "") + # handle single config generators - like makefiles/ninja - when CMAKE_BUILD_TYPE is set + string(TOUPPER ${CMAKE_BUILD_TYPE} config) + list(APPEND ${result} CMAKE_C_FLAGS_${config}) + list(APPEND ${result} CMAKE_CXX_FLAGS_${config}) + if(${with_linker}) + list(APPEND ${result} CMAKE_EXE_LINKER_FLAGS_${config}) + list(APPEND ${result} CMAKE_MODULE_LINKER_FLAGS_${config}) + list(APPEND ${result} CMAKE_SHARED_LINKER_FLAGS_${config}) + list(APPEND ${result} CMAKE_STATIC_LINKER_FLAGS_${config}) + endif() + else() + # handle multi config generators (like msvc, xcode) + foreach(config ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER ${config} config) + list(APPEND ${result} CMAKE_C_FLAGS_${config}) + list(APPEND ${result} CMAKE_CXX_FLAGS_${config}) + if(${with_linker}) + list(APPEND ${result} CMAKE_EXE_LINKER_FLAGS_${config}) + list(APPEND ${result} CMAKE_MODULE_LINKER_FLAGS_${config}) + list(APPEND ${result} CMAKE_SHARED_LINKER_FLAGS_${config}) + list(APPEND ${result} CMAKE_STATIC_LINKER_FLAGS_${config}) + endif() + endforeach() + endif() +endmacro() + +# ucm_set_runtime +# Sets the runtime (static/dynamic) for msvc/gcc +macro(ucm_set_runtime) + cmake_parse_arguments(ARG "STATIC;DYNAMIC" "" "" ${ARGN}) + + if(ARG_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "unrecognized arguments: ${ARG_UNPARSED_ARGUMENTS}") + endif() + + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" STREQUAL "") + message(AUTHOR_WARNING "ucm_set_runtime() does not support clang yet!") + endif() + + ucm_gather_flags(0 flags_configs) + + # add/replace the flags + # note that if the user has messed with the flags directly this function might fail + # - for example if with MSVC and the user has removed the flags - here we just switch/replace them + if("${ARG_STATIC}") + foreach(flags ${flags_configs}) + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + if(NOT ${flags} MATCHES "-static-libstdc\\+\\+") + set(${flags} "${${flags}} -static-libstdc++") + endif() + if(NOT ${flags} MATCHES "-static-libgcc") + set(${flags} "${${flags}} -static-libgcc") + endif() + elseif(MSVC) + if(${flags} MATCHES "/MD") + string(REGEX REPLACE "/MD" "/MT" ${flags} "${${flags}}") + endif() + endif() + endforeach() + elseif("${ARG_DYNAMIC}") + foreach(flags ${flags_configs}) + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + if(${flags} MATCHES "-static-libstdc\\+\\+") + string(REGEX REPLACE "-static-libstdc\\+\\+" "" ${flags} "${${flags}}") + endif() + if(${flags} MATCHES "-static-libgcc") + string(REGEX REPLACE "-static-libgcc" "" ${flags} "${${flags}}") + endif() + elseif(MSVC) + if(${flags} MATCHES "/MT") + string(REGEX REPLACE "/MT" "/MD" ${flags} "${${flags}}") + endif() + endif() + endforeach() + endif() +endmacro() + +# ucm_print_flags +# Prints all compiler flags for all configurations +macro(ucm_print_flags) + ucm_gather_flags(1 flags_configs) + message("") + foreach(flags ${flags_configs}) + message("${flags}: ${${flags}}") + endforeach() + message("") +endmacro() diff --git a/deps/try_signal/.travis.yml b/deps/try_signal/.travis.yml new file mode 100644 index 0000000..778d067 --- /dev/null +++ b/deps/try_signal/.travis.yml @@ -0,0 +1,44 @@ +language: cpp +matrix: + include: + - env: toolset=gcc + - os: osx + osx_image: xcode11.2 + env: toolset=darwin + +branches: + only: + - master + +git: + submodules: false + depth: 1 + +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - libboost-tools-dev + - g++-9 + +install: + + - 'if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew update > /dev/null && brew install boost-build; fi' + - 'if [[ $TRAVIS_OS_NAME != "osx" ]]; then + export B2=bjam; + else + export B2=b2; + fi' + - touch ~/user-config.jam + - 'if [[ $toolset == "gcc" ]]; then + g++-5 --version; + echo "using gcc : : g++-5 : -std=c++11 ;" >> ~/user-config.jam; + fi' + - 'echo "using darwin : : clang++ : -std=c++11 ;" >> ~/user-config.jam' + +script: + + - ${B2} link=static stage_test + - ./test + diff --git a/deps/try_signal/CMakeLists.txt b/deps/try_signal/CMakeLists.txt new file mode 100644 index 0000000..945cd6c --- /dev/null +++ b/deps/try_signal/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.8.12) +project(try_signal) + +add_library(try_signal signal_error_code try_signal) +target_include_directories(try_signal PUBLIC .) + diff --git a/deps/try_signal/Jamfile b/deps/try_signal/Jamfile new file mode 100644 index 0000000..ec001a8 --- /dev/null +++ b/deps/try_signal/Jamfile @@ -0,0 +1,18 @@ +lib try_signal + : # sources + signal_error_code.cpp try_signal.cpp + : # requirements + : # default build + static + : # usage requirements + . + ; + +exe test : test.cpp : try_signal static ; +explicit test ; + +exe example : example.cpp : try_signal static ; +explicit example ; + +install stage_test : test : . ; + diff --git a/deps/try_signal/LICENSE b/deps/try_signal/LICENSE new file mode 100644 index 0000000..1523026 --- /dev/null +++ b/deps/try_signal/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/deps/try_signal/README.rst b/deps/try_signal/README.rst new file mode 100644 index 0000000..22cbe18 --- /dev/null +++ b/deps/try_signal/README.rst @@ -0,0 +1,53 @@ +try_signal +========== + +.. image:: https://travis-ci.org/arvidn/try_signal.svg?branch=master + :target: https://travis-ci.org/arvidn/try_signal + +.. image:: https://ci.appveyor.com/api/projects/status/le8jjroaai8081f1?svg=true + :target: https://ci.appveyor.com/project/arvidn/try-signal/branch/master + +The ``try_signal`` library provide a way to turn signals into C++ exceptions. +This is especially useful when performing disk I/O via memory mapped files, +where I/O errors are reported as ``SIGBUS`` and ``SIGSEGV`` or as structured +exceptions on windows. + +The function ``try_signal`` takes a function object that will be executed once. +If the function causes a signal (or structured exception) to be raised, it will +throw a C++ exception. Note that RAII may not be relied upon within this function. +It may not rely on destructors being called. Stick to simple operations like +memcopy. + +Example:: + + #include + #include + #include + #include "try_signal.hpp" + #include + #include + #include + + int main() try + { + int fd = open("test_file", O_RDWR); + void* map = mmap(nullptr, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + + std::vector buf(1024); + std::iota(buf.begin(), buf.end(), 0); + + // disk full or access after EOF are reported as exceptions + sig::try_signal([&]{ + std::memcpy(map, buf.data(), buf.size()); + }); + + munmap(map, 1024); + close(fd); + return 0; + } + catch (std::exception const& e) + { + fprintf(stderr, "exited with exception: %s\n", e.what()); + return 1; + } + diff --git a/deps/try_signal/appveyor.yml b/deps/try_signal/appveyor.yml new file mode 100644 index 0000000..a185535 --- /dev/null +++ b/deps/try_signal/appveyor.yml @@ -0,0 +1,50 @@ +version: "{build}" +branches: + only: + - master +os: Visual Studio 2015 +clone_depth: 1 +environment: + matrix: + - variant: debug + compiler: msvc-14.0 + model: 64 + - variant: debug + compiler: msvc-14.0 + model: 32 + - variant: release + compiler: msvc-14.0 + model: 64 + - variant: debug + compiler: gcc + model: 32 + - variant: debug + compiler: gcc + model: 64 + - variant: release + compiler: gcc + model: 32 + +install: +- set ROOT_DIRECTORY=%CD% +- set BOOST_ROOT=c:\Libraries\boost_1_67_0 +- set BOOST_BUILD_PATH=%BOOST_ROOT%\tools\build +- echo %BOOST_ROOT% +- echo %BOOST_BUILD_PATH% +- set PATH=%PATH%;%BOOST_BUILD_PATH%\src\engine\bin.ntx86 +- ps: '"using msvc : 14.0 ;`nusing gcc : : : -std=c++11 ;" | Set-Content $env:HOMEDRIVE\$env:HOMEPATH\user-config.jam' +- type %HOMEDRIVE%%HOMEPATH%\user-config.jam +- set PATH=c:\msys64\mingw32\bin;%PATH% +- g++ --version +- python --version +- echo %ROOT_DIRECTORY% +- cd %BOOST_BUILD_PATH%\src\engine +- build.bat >nul +- cd %ROOT_DIRECTORY% + +build_script: +# examples +- b2.exe warnings=all warnings-as-errors=on -j2 %compiler% address-model=%model% variant=%variant% stage_test + +test_script: +- test diff --git a/deps/try_signal/example.cpp b/deps/try_signal/example.cpp new file mode 100644 index 0000000..703b817 --- /dev/null +++ b/deps/try_signal/example.cpp @@ -0,0 +1,31 @@ +#include +#include +#include +#include "try_signal.hpp" +#include +#include +#include + +int main() try +{ + int fd = open("test_file", O_RDWR); + void* map = mmap(nullptr, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + + std::vector buf(1024); + std::iota(buf.begin(), buf.end(), 0); + + // disk full or access after EOF are reported as exceptions + sig::try_signal([&]{ + std::memcpy(map, buf.data(), buf.size()); + }); + + munmap(map, 1024); + close(fd); + return 0; +} +catch (std::exception const& e) +{ + fprintf(stderr, "exited with exception: %s\n", e.what()); + return 1; +} + diff --git a/deps/try_signal/project-root.jam b/deps/try_signal/project-root.jam new file mode 100644 index 0000000..e69de29 diff --git a/deps/try_signal/signal_error_code.cpp b/deps/try_signal/signal_error_code.cpp new file mode 100644 index 0000000..ae016cf --- /dev/null +++ b/deps/try_signal/signal_error_code.cpp @@ -0,0 +1,209 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include + +#include "signal_error_code.hpp" + +namespace { + + struct signal_error_category : std::error_category + { + const char* name() const noexcept override + { return "signal"; } + std::string message(int ev) const noexcept override + { +#define SIGNAL_CASE(x) case sig::errors::error_code_enum:: x: return #x; + switch (ev) + { + SIGNAL_CASE(abort) + SIGNAL_CASE(alarm) + SIGNAL_CASE(arithmetic_exception) + SIGNAL_CASE(hangup) + SIGNAL_CASE(illegal) + SIGNAL_CASE(interrupt) + SIGNAL_CASE(kill) + SIGNAL_CASE(pipe) + SIGNAL_CASE(quit) + case sig::errors::error_code_enum::segmentation: return "segmentation fault"; + SIGNAL_CASE(terminate) + SIGNAL_CASE(user1) + SIGNAL_CASE(user2) + SIGNAL_CASE(child) + SIGNAL_CASE(cont) + SIGNAL_CASE(stop) + SIGNAL_CASE(terminal_stop) + SIGNAL_CASE(terminal_in) + SIGNAL_CASE(terminal_out) + SIGNAL_CASE(bus) +#ifdef SIGPOLL + SIGNAL_CASE(poll) +#endif + SIGNAL_CASE(profiler) + SIGNAL_CASE(system_call) + SIGNAL_CASE(trap) + SIGNAL_CASE(urgent_data) + SIGNAL_CASE(virtual_timer) + SIGNAL_CASE(cpu_limit) + SIGNAL_CASE(file_size_limit) + default: return "unknown"; + } +#undef SIGNAL_CASE + } + std::error_condition default_error_condition(int ev) const noexcept override + { return {ev, *this}; } + }; +} // anonymous namespace + +namespace sig { +namespace errors { + + std::error_code make_error_code(error_code_enum e) + { + return {e, sig_category()}; + } + + std::error_condition make_error_condition(error_code_enum e) + { + return {e, sig_category()}; + } + +} // namespace errors + +std::error_category& sig_category() +{ + static signal_error_category signal_category; + return signal_category; +} + +#ifdef _WIN32 + +namespace { + sig::errors::error_code_enum map_exception_code(int const ev) + { + switch (ev) + { + case seh_errors::error_code_enum::access_violation: + case seh_errors::error_code_enum::array_bounds_exceeded: + case seh_errors::error_code_enum::guard_page: + case seh_errors::error_code_enum::stack_overflow: + case seh_errors::error_code_enum::flt_stack_check: + case seh_errors::error_code_enum::in_page_error: + return sig::errors::segmentation; + case seh_errors::error_code_enum::breakpoint: + case seh_errors::error_code_enum::single_step: + return sig::errors::trap; + case seh_errors::error_code_enum::datatype_misalignment: + return sig::errors::bus; + case seh_errors::error_code_enum::flt_denormal_operand: + case seh_errors::error_code_enum::flt_divide_by_zero: + case seh_errors::error_code_enum::flt_inexact_result: + case seh_errors::error_code_enum::flt_invalid_operation: + case seh_errors::error_code_enum::flt_overflow: + case seh_errors::error_code_enum::flt_underflow: + case seh_errors::error_code_enum::int_divide_by_zero: + case seh_errors::error_code_enum::int_overflow: + return sig::errors::arithmetic_exception; + case seh_errors::error_code_enum::illegal_instruction: + case seh_errors::error_code_enum::invalid_disposition: + case seh_errors::error_code_enum::priv_instruction: + case seh_errors::error_code_enum::noncontinuable_exception: + case seh_errors::error_code_enum::status_unwind_consolidate: + return sig::errors::illegal; + case seh_errors::error_code_enum::invalid_handle: + return sig::errors::pipe; + default: + return sig::errors::illegal; + } + } + + struct seh_error_category : std::error_category + { + const char* name() const noexcept override + { return "SEH"; } + std::string message(int ev) const noexcept override + { +#define SIGNAL_CASE(x) case sig::seh_errors::error_code_enum:: x: return #x; + switch (ev) + { + SIGNAL_CASE(access_violation) + SIGNAL_CASE(array_bounds_exceeded) + SIGNAL_CASE(guard_page) + SIGNAL_CASE(stack_overflow) + SIGNAL_CASE(flt_stack_check) + SIGNAL_CASE(in_page_error) + SIGNAL_CASE(breakpoint) + SIGNAL_CASE(single_step) + SIGNAL_CASE(datatype_misalignment) + SIGNAL_CASE(flt_denormal_operand) + SIGNAL_CASE(flt_divide_by_zero) + SIGNAL_CASE(flt_inexact_result) + SIGNAL_CASE(flt_invalid_operation) + SIGNAL_CASE(flt_overflow) + SIGNAL_CASE(flt_underflow) + SIGNAL_CASE(int_divide_by_zero) + SIGNAL_CASE(int_overflow) + SIGNAL_CASE(illegal_instruction) + SIGNAL_CASE(invalid_disposition) + SIGNAL_CASE(priv_instruction) + SIGNAL_CASE(noncontinuable_exception) + SIGNAL_CASE(status_unwind_consolidate) + SIGNAL_CASE(invalid_handle) + default: return "unknown"; + } +#undef SIGNAL_CASE + } + std::error_condition default_error_condition(int ev) const noexcept override + { return std::error_condition(map_exception_code(ev), sig_category()); } + }; +} // anonymous namespace + +namespace seh_errors { + + std::error_code make_error_code(error_code_enum e) + { + return {static_cast(e), seh_category()}; + } + +} // namespace errors + +std::error_category& seh_category() +{ + static seh_error_category seh_category; + return seh_category; +} + +#endif + +} // namespace sig + diff --git a/deps/try_signal/signal_error_code.hpp b/deps/try_signal/signal_error_code.hpp new file mode 100644 index 0000000..91ea947 --- /dev/null +++ b/deps/try_signal/signal_error_code.hpp @@ -0,0 +1,162 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SIGNAL_ERROR_CODE_HPP_INCLUDED +#define SIGNAL_ERROR_CODE_HPP_INCLUDED + +#include +#include + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +#ifdef __GNUC__ +#include +#else +#include +#endif +#endif + +namespace sig { +namespace errors { + +#ifdef _WIN32 +#define SIG_ENUM(name, sig) name, +#else +#define SIG_ENUM(name, sig) name = sig, +#endif + + enum error_code_enum: int + { + SIG_ENUM(abort, SIGABRT) + SIG_ENUM(alarm, SIGALRM) + SIG_ENUM(arithmetic_exception, SIGFPE) + SIG_ENUM(hangup, SIGHUP) + SIG_ENUM(illegal, SIGILL) + SIG_ENUM(interrupt, SIGINT) + SIG_ENUM(kill, SIGKILL) + SIG_ENUM(pipe, SIGPIPE) + SIG_ENUM(quit, SIGQUIT) + SIG_ENUM(segmentation, SIGSEGV) + SIG_ENUM(terminate, SIGTERM) + SIG_ENUM(user1, SIGUSR1) + SIG_ENUM(user2, SIGUSR2) + SIG_ENUM(child, SIGCHLD) + SIG_ENUM(cont, SIGCONT) + SIG_ENUM(stop, SIGSTOP) + SIG_ENUM(terminal_stop, SIGTSTP) + SIG_ENUM(terminal_in, SIGTTIN) + SIG_ENUM(terminal_out, SIGTTOU) + SIG_ENUM(bus, SIGBUS) +#ifdef SIGPOLL + SIG_ENUM(poll, SIGPOLL) +#endif + SIG_ENUM(profiler, SIGPROF) + SIG_ENUM(system_call, SIGSYS) + SIG_ENUM(trap, SIGTRAP) + SIG_ENUM(urgent_data, SIGURG) + SIG_ENUM(virtual_timer, SIGVTALRM) + SIG_ENUM(cpu_limit, SIGXCPU) + SIG_ENUM(file_size_limit, SIGXFSZ) + }; + +#undef SIG_ENUM + + std::error_code make_error_code(error_code_enum e); + std::error_condition make_error_condition(error_code_enum e); + +} // namespace errors + +std::error_category& sig_category(); + +#ifdef _WIN32 +namespace seh_errors { + + // standard error codes are "int", the win32 exceptions are DWORD (i.e. + // unsigned int). We coerce them into int here for compatibility, and we're + // not concerned about their arithmetic + enum error_code_enum: int + { + access_violation = int(EXCEPTION_ACCESS_VIOLATION), + array_bounds_exceeded = int(EXCEPTION_ARRAY_BOUNDS_EXCEEDED), + guard_page = int(EXCEPTION_GUARD_PAGE), + stack_overflow = int(EXCEPTION_STACK_OVERFLOW), + flt_stack_check = int(EXCEPTION_FLT_STACK_CHECK), + in_page_error = int(EXCEPTION_IN_PAGE_ERROR), + breakpoint = int(EXCEPTION_BREAKPOINT), + single_step = int(EXCEPTION_SINGLE_STEP), + datatype_misalignment = int(EXCEPTION_DATATYPE_MISALIGNMENT), + flt_denormal_operand = int(EXCEPTION_FLT_DENORMAL_OPERAND), + flt_divide_by_zero = int(EXCEPTION_FLT_DIVIDE_BY_ZERO), + flt_inexact_result = int(EXCEPTION_FLT_INEXACT_RESULT), + flt_invalid_operation = int(EXCEPTION_FLT_INVALID_OPERATION), + flt_overflow = int(EXCEPTION_FLT_OVERFLOW), + flt_underflow = int(EXCEPTION_FLT_UNDERFLOW), + int_divide_by_zero = int(EXCEPTION_INT_DIVIDE_BY_ZERO), + int_overflow = int(EXCEPTION_INT_OVERFLOW), + illegal_instruction = int(EXCEPTION_ILLEGAL_INSTRUCTION), + invalid_disposition = int(EXCEPTION_INVALID_DISPOSITION), + priv_instruction = int(EXCEPTION_PRIV_INSTRUCTION), + noncontinuable_exception = int(EXCEPTION_NONCONTINUABLE_EXCEPTION), + status_unwind_consolidate = int(STATUS_UNWIND_CONSOLIDATE), + invalid_handle = int(EXCEPTION_INVALID_HANDLE), + }; + + std::error_code make_error_code(error_code_enum e); +} + +std::error_category& seh_category(); + +#endif // _WIN32 + +} // namespace sig + +namespace std +{ +template<> +struct is_error_code_enum : std::true_type {}; + +template<> +struct is_error_condition_enum : std::true_type {}; + +#ifdef _WIN32 +template<> +struct is_error_code_enum : std::true_type {}; +#endif + +} // namespace std + +#endif + diff --git a/deps/try_signal/test.cpp b/deps/try_signal/test.cpp new file mode 100644 index 0000000..b8c67c8 --- /dev/null +++ b/deps/try_signal/test.cpp @@ -0,0 +1,47 @@ +#include +#include +#include // for memcpy + +#include "try_signal.hpp" + +int main() +{ + char const buf[] = "test...test"; + char dest[sizeof(buf)]; + + { + sig::try_signal([&]{ + std::memcpy(dest, buf, sizeof(buf)); + }); + if (!std::equal(buf, buf + sizeof(buf), dest)) { + fprintf(stderr, "ERROR: buffer not copied correctly\n"); + return 1; + } + } + + try { + void* invalid_pointer = nullptr; + sig::try_signal([&]{ + std::memcpy(dest, buf, sizeof(buf)); + std::memcpy(dest, invalid_pointer, sizeof(buf)); + }); + } + catch (std::system_error const& e) + { + if (e.code() != std::error_condition(sig::errors::segmentation)) { + fprintf(stderr, "ERROR: expected segmentaiton violation error\n"); + } + else { + fprintf(stderr, "OK\n"); + } + fprintf(stderr, "exited with expected system_error exception: %s\n", e.what()); + + // we expect this to happen, so return 0 + return e.code() == std::error_condition(sig::errors::segmentation) ? 0 : 1; + } + + // return non-zero here because we don't expect this + fprintf(stderr, "ERROR: expected exit through exception\n"); + return 1; +} + diff --git a/deps/try_signal/try_signal.cpp b/deps/try_signal/try_signal.cpp new file mode 100644 index 0000000..be5cd8d --- /dev/null +++ b/deps/try_signal/try_signal.cpp @@ -0,0 +1,144 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include + +#include "try_signal.hpp" + +#if !defined _WIN32 +// linux + +namespace sig { +namespace detail { + +namespace { +thread_local sigjmp_buf* jmpbuf = nullptr; +} + +std::atomic_flag once = ATOMIC_FLAG_INIT; + +scoped_jmpbuf::scoped_jmpbuf(sigjmp_buf* ptr) +{ + _previous_ptr = jmpbuf; + jmpbuf = ptr; + std::atomic_signal_fence(std::memory_order_release); +} + +scoped_jmpbuf::~scoped_jmpbuf() { jmpbuf = _previous_ptr; } + +void handler(int const signo, siginfo_t*, void*) +{ + std::atomic_signal_fence(std::memory_order_acquire); + if (jmpbuf) + siglongjmp(*jmpbuf, signo); + + // this signal was not caused within the scope of a try_signal object, + // invoke the default handler + signal(signo, SIG_DFL); + raise(signo); +} + +void setup_handler() +{ + struct sigaction sa; + sa.sa_sigaction = &sig::detail::handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + sigaction(SIGSEGV, &sa, nullptr); + sigaction(SIGBUS, &sa, nullptr); +} + +} // detail namespace +} // sig namespace + +#elif __GNUC__ +// mingw + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +namespace sig { +namespace detail { + +thread_local jmp_buf* jmpbuf = nullptr; + +long CALLBACK handler(EXCEPTION_POINTERS* pointers) +{ + std::atomic_signal_fence(std::memory_order_acquire); + if (jmpbuf) + longjmp(*jmpbuf, pointers->ExceptionRecord->ExceptionCode); + return EXCEPTION_CONTINUE_SEARCH; +} + +scoped_handler::scoped_handler(jmp_buf* ptr) +{ + _previous_ptr = jmpbuf; + jmpbuf = ptr; + std::atomic_signal_fence(std::memory_order_release); + _handle = AddVectoredExceptionHandler(1, sig::detail::handler); +} +scoped_handler::~scoped_handler() +{ + RemoveVectoredExceptionHandler(_handle); + jmpbuf = _previous_ptr; +} + +} // detail namespace +} // sig namespace + +#else +// windows + +#include // for EXCEPTION_* + +namespace sig { +namespace detail { + + // these are the kinds of SEH exceptions we'll translate into C++ exceptions + bool catch_error(int const code) + { + return code == EXCEPTION_IN_PAGE_ERROR + || code == EXCEPTION_ACCESS_VIOLATION + || code == EXCEPTION_ARRAY_BOUNDS_EXCEEDED; + } +} // detail namespace +} // namespace sig + +#endif // _WIN32 + + diff --git a/deps/try_signal/try_signal.hpp b/deps/try_signal/try_signal.hpp new file mode 100644 index 0000000..557d92b --- /dev/null +++ b/deps/try_signal/try_signal.hpp @@ -0,0 +1,49 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TRY_SIGNAL_HPP_INCLUDED +#define TRY_SIGNAL_HPP_INCLUDED + +#if !defined _WIN32 +// linux +#include "try_signal_posix.hpp" +#elif __GNUC__ +// mingw +#include "try_signal_mingw.hpp" +#else +// windows +#include "try_signal_msvc.hpp" +#endif + + +#endif // TRY_SIGNAL_HPP_INCLUDED + diff --git a/deps/try_signal/try_signal_mingw.hpp b/deps/try_signal/try_signal_mingw.hpp new file mode 100644 index 0000000..e4db043 --- /dev/null +++ b/deps/try_signal/try_signal_mingw.hpp @@ -0,0 +1,78 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TRY_SIGNAL_MINGW_HPP_INCLUDED +#define TRY_SIGNAL_MINGW_HPP_INCLUDED + +#include "signal_error_code.hpp" + +#include // for jmp_buf + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +namespace sig { +namespace detail { + +struct scoped_handler +{ + scoped_handler(jmp_buf* ptr); + ~scoped_handler(); + scoped_handler(scoped_handler const&) = delete; + scoped_handler& operator=(scoped_handler const&) = delete; +private: + void* _handle; + jmp_buf* _previous_ptr; +}; + +} // detail namespace + +template +void try_signal(Fun&& f) +{ + jmp_buf buf; + int const code = setjmp(buf); + // set the thread local jmpbuf pointer, and make sure it's cleared when we + // leave the scope + sig::detail::scoped_handler scope(&buf); + if (code != 0) + throw std::system_error(std::error_code(code, seh_category())); + + f(); +} + +} // sig namespace + +#endif + diff --git a/deps/try_signal/try_signal_msvc.hpp b/deps/try_signal/try_signal_msvc.hpp new file mode 100644 index 0000000..04cc62d --- /dev/null +++ b/deps/try_signal/try_signal_msvc.hpp @@ -0,0 +1,61 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TRY_SIGNAL_MSVC_HPP_INCLUDED +#define TRY_SIGNAL_MSVC_HPP_INCLUDED + +#include "signal_error_code.hpp" + +namespace sig { +namespace detail { + +bool catch_error(int const code); + +} // detail namespace + +template +void try_signal(Fun&& f) +{ + __try + { + f(); + } + __except (detail::catch_error(GetExceptionCode())) + { + throw std::system_error(std::error_code(GetExceptionCode(), seh_category())); + } +} + +} // sig namespace + +#endif + diff --git a/deps/try_signal/try_signal_posix.hpp b/deps/try_signal/try_signal_posix.hpp new file mode 100644 index 0000000..2c4615d --- /dev/null +++ b/deps/try_signal/try_signal_posix.hpp @@ -0,0 +1,82 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TRY_SIGNAL_POSIX_HPP_INCLUDED +#define TRY_SIGNAL_POSIX_HPP_INCLUDED + +#include "signal_error_code.hpp" +#include // for sigjmp_buf +#include + +namespace sig { + +namespace detail { + +extern std::atomic_flag once; + +struct scoped_jmpbuf +{ + explicit scoped_jmpbuf(sigjmp_buf* ptr); + ~scoped_jmpbuf(); + scoped_jmpbuf(scoped_jmpbuf const&) = delete; + scoped_jmpbuf& operator=(scoped_jmpbuf const&) = delete; +private: + sigjmp_buf* _previous_ptr; +}; + +void handler(int const signo, siginfo_t* si, void*); +void setup_handler(); + +} // detail namespace + +template +void try_signal(Fun&& f) +{ + if (sig::detail::once.test_and_set() == false) { + sig::detail::setup_handler(); + } + + sigjmp_buf buf; + int const sig = sigsetjmp(buf, 1); + // set the thread local jmpbuf pointer, and make sure it's cleared when we + // leave the scope + sig::detail::scoped_jmpbuf scope(&buf); + if (sig != 0) + throw std::system_error(static_cast(sig)); + + f(); +} + +} + +#endif + diff --git a/docs/2020 Q4 Mozilla Libtorrent Report Public Report.pdf b/docs/2020 Q4 Mozilla Libtorrent Report Public Report.pdf new file mode 100644 index 0000000000000000000000000000000000000000..04ae5d0f00d52510749788631f3395097821e8a5 GIT binary patch literal 747116 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
{jdCi!L`$J|1-?Yz-U*hN^xr_Ly=o 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≤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%0AegOqrVQXrdPM<`_JSg=nt$BnQ16yv?m9Ky><{ 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�VCs1#l1DVg8EKH+;q; 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≠`Cz<4CeQO~Y z`x$MU=*O7A({#OFStQ(E#r)Z7!<$$dij<61XD1H1!wu9<=z6;NR-XB0_Q8r;81m)@ zPA)C6S`(?*xf|=LZ_FDKBvX0~XvmJE31|?{er+>m^;FX6iHagMu~W^DvXDm*_h$0Y ztk-`PoI1QC^gp=_C*?flVu=rt>4z|ik@HI60d_QMxEJZi^*D>j?|R$#kT~Nq3G&6V zOc;l?DIhral0Hm~7EF!Ee;KE2r$oyJ|Jg~mrV2=YyCbEU6w!Z?OcxMsiar(#C_OcT zXgBEhgowt^E7ITJibhj&5NI>@N2^mbqPMUsHmVo{jx;;2tKgFPOO&8wk2#F$#$Jv2 zr`(KQnamCHuN&P7apuB=Vlt?CwoevrdSP4n6PWkfvkT)I9Nw@91rLr+|y0p&U z@mA9+5>Q}65t#4Nl_- zMa9iB*^nL&`g1sh_c&fU)!Q01+fmUPS9>^AoJVy4UILf3u67;|)7L8xFDH+ix3j0} zy*fKqE*^wBapOD8P^}Rvq(Cr02r2hh%m(?GOZo(D;bZO2!y?&k6R&}rpO-Hu@JjEm zFDI@wJo`{c;zxI)zi%i4r;lpt?o>~}t5WTL5_-zB68+a&btSoj>iK~ZFF?P^kZgPT zb6aH|{-9m{cyMP^ub6^fYa0v2O0}(iGmnR*eey=gXB)4CgMrc*P~504S85AwD+3-KMlUKeBzsJme*rdkg~*9XzP&JG;_ zCTT}2fO>p6=x8!H=#^zE^g=U_ufrKp1FYHkDx+kJN^92s304Hm-ql~Z*~Rg3cP*=@ zU{CEbxJ!uG47O54w8{^tMYQYCpP<3jsTs#BPpXm$G*PqHdU~ctUS)_Cv{w;8CnowK z70R_h>4pl2Aj-((RXF1I#q3uUvelMB1%E`QmtcZdTZ>!&1{Q;lQNgWhjwiM-bk>HC zvRKe4XX=m`Oq*v@H7T<_44%shc%u~j#e>sQ_7Y>bKs|N=8vu!Nppu7D8&eBCx+@!CO&WB?69 zgUl#Lz5{Dd=Ex^cM#n8%ODeLit51xPD|^=(zBna+4uj0^JbE}>nUYL#nFR3&QpBYb zZ3~@mXKfz+rW&{0p!d(N6y}i+9y(OY%8-OfB+nf<4*`J9I4+sYJ8E}w zqmiWIu)v_*f$G0gP@ujD5iWwy2Km4Ld-op=+-J$xtb7U%+ z_7f|f5TD}4C|j6HjXcneS=Q+3HkNM$Ign}1?MxdP-JkEB%bER`WP-)Xr4`TmR@*g5o^aeVk_}89HDaj5`Y?du~9eS8&&=b97OE7 zmQ)!?U?o9c!vxzNaIc~C=c+b*bpcY_eZx9!fDN9z!kaXo>MWyP@6P=Jsa7ncw@BWA zUYIiMulrHX8%s%ynkw-*q^Y>+$SudF{0Ir4T4LR@ zNEpP$mZ=#sDj(p)%Mc@Nqm2S+>wfsne6KvK4Mb}lvNya9^FhO=^f~hlsg~EXQ{xP< zr{SEmtlLSw;AK}%01ZcczmW_%a$In0z`n%It^S&Evbm)?GIfNkGy=>fJ-(@=nG!=M zH`TFB=9fT@jO7m1qZzOJif>yyusEkVNTckGFIc=6I-J~l-|EyC z8}TY=#c_Z$HV+RvwIl%*qp(p{I#*afMHO>1c}?hX_N+=(R_LW(vRZsJXSC}_D+mFVB=6;#Ot@hqoS;2&RCXP#GTe$0(qJMg zCOlHd2;Bs)GNI={3cOf%V8tR7aC+W7I=XvC1Kv8fg#Viv{&OLz1n z{RiB2_(;Yx?+s$AZA13Mz$~FTUm9J^U~B7jPcvVJsyq&-g@NVB3r+(y!=m1u_Lzf0 zVRYiPUmwDN80WgJ&b!fbo$PmycCd2f?~zPo!PL7OZ+Dae z%b)6oTcH#MtHsU|&y6t|k|)+-6BlZv*OtZ0$?EpBNsEUj%GhXo08a^w{T6QyykE1l>0GMs>hwZlgNeh6#*2hiAV)@7I)-Ig}=fPmc zkfm^+tWMCD?`*D{9^pnQ$>WU@*KGrDk?Jyn4A7){Jp+~VJOTmlN?#vs8spd`7<7Gd z_ZouQA3GWgk{zAg#EUEVH9ME~nV#>G>X(m!a@ zIvY-#$(UvP0gx(=IA_83klSFFRFS`fN%VHrc)!y~a6JYpQJ)(PM(b6b`G*jLpBaDW zx@P)c@k*`ywUH}s(@?3LHs|*7+o?^f`xJv^uluHWm1)?u0eh+AgQA{4^P_L#;Sr`r z6lL`#i$y62i}?{ItEx?N6s;G>rD_D{QJ7|tl$hOGBXZ!q&n@NcAr_?=5v8eC4zdMS z*d?ayH>W*)Yo%2%O*3lPPw9Rk)@ey&`kRwe4RnpXgoIcNbi2Ey{k zmJXKtWbG!x0B=4cjO5ZWfb)P%WY8tCksznHxKxYZ2CH+H*8}P&e}IHQ!=~$og(aG2 z9zvD#W$#3_@lu9bIc4rV_%{pBp=%b}M1_XH83LK4ru0_<*u_eYSW7S%RW~a-jqA1; zfwN&C*}+U1qod@^>KGfRwCOyxpDzABDlBrj1ADpYh7~OssL&VQMNvsL?ZAXH+J6`} zGGbIYK2vk}hKl$XTzO8GNe|!l*nLU`z*(bj9~UANe+`2(8F+}DAv>J~7njGrIsk)b z7t{6ML&iN7Xeav2R1csn#xSqbn3IlF$?+(OkI$vo9DDUDL1{+NE&P&iR5a(0*%0w(7XcJEjx`WB|a*APGXzC|GEnc)0uWy zNh@2&VPNe4D7|_p9CnVj&#%rO7N6Wst{k?Wr#mzK%5ZSDlrz88P{3CsXo_Dp&;Gj? z+^WUPJ^k@b;jx^!$;52EESwS*jfAHmr1gmPteY+O1v^%q$NO(0sQ)LtiTMW~V`cim z$7rePSa6wH*r=KQf9NJ>XJ={$#~<|OhxD+wu{O1G_$NuGuWj$3`-2zR8E9LW>e=bq zxzgxsTk7iDS!?Us+Z)?-_B2ck^#7Q&#s6W}bpIXF z_)kXa|6ZfUO2_!$A&tp@sZsO&1zHYC-cdN?*r;u0|QGr-{+0j{8OX$@*t3Yc)Q!{ z-DP~b+Z!I<@4uhT>K_z-x(gUAjqG3DWgX`8+Q*Qq3+Sok`LJpMzF&*x2{~q64Fk&^ znR3g~{<6H=ix%-TzT5j`>fMW7MU!F(vf(e&iI43ZFea%p^ zFlcq62k`<5(Q2h*cXU7F@Q)HTbvLJ{CnAok+RyL`(mJ8;SMRYp{#2XNo zp|ahzB5TXV{jPXcN?O~7M$-%46o9Bd#LYh?YAp`4SzRK$OxGFl05}l@#)y-8`y=zD z5j8ERx1bRHHq5*IK>IQTR;Gx=jGo`UNwX%l)TJL0n%g^;|*Qp!EO(nm;BtZpIoP2i2!3(F-Sqg8bBMIo(1&&?xTWoqOXG3idqPX{v zHjTT{GRDX4JKjQ+tGB!7K~rodXEvogB88zWBPcXds+xn5$fi12Tu84qFNkrj?W$&e zk5!E}>!-QMh7Dzah9>n7_(a>J+)Vap#y59F(vKCrh0+}zEklB}-5fINC+b|h0EIm~ z9iGb@an+#nqA&q8^#5TEqkU&1-JVH&U2XyBnbtAHkRKYuaxIk#9+qB-QpH8uREFDDibd}X zCchl;1`ZxZr0+<~4{LZODDwp4QL^@2?9qm=5$plSd&yXTxN#!p+fv7wjlnyIp;b1>q5m@LkcQXu!Tyenj?|t_gn;BomA*gZ zPqB5eOD(;ItFfZf(_xNw-fLKgzG=Wh`ccO^ol65HKIOE7P_`m6nrqp?FPAC>vf=cH zOf{p48^dv_e$4nYj7DEzyeLA0BLmf$Kl-P)dI=&kks#?lXm!9_0-ExMF~BC?l|!?I zTz9=NccJNI@3WRpu+;>M%VB=#>4-2-rnN>0hK z8jnDVTbC3U@lP~ni6ximl(!-YPF+gb%b;K*Zz3#pkTzjcyePhse^fO4Vt`=MR$wg6 zeuwHGSo=V1M(_im=zXVSiv7`!{o+_}F=-(R4#Vid5}VKvHe!0a2N3nix21{_BjFDh z;P3G~b9IrGsSYZMdeY%Hm2P**P16IBjjywtSl26|rLhbxetaCg z15lODg9qBA>ZQAjx#G~ZBQ1%dTHIzHhZEb|V?RgB?e1KOuYkmHpm>@DqXkr}jNaT1 zE(~(v9GI0y@&+7Y#jg-BK@IYBfZ@d620F?7ZRknFUo+m zXHI*rX=DA7+EN>H#^YMS`9m2t*(YKH5e2oXVMm2d za{J*eR~d0GUfik_(p>?HuFDhyFcqt9iC(@k5J;4gK|N>Qto4#!2+Noi+^G^& znP424R@4?g&s|{#Q)M}@X9zFWeuMq#pfX}LC5O#eMoT|m7p>qsSj$}~Q~w`L^tCu| z9o@4_I2ZLhXGTKr0seJ_`6jh}nbz~G!wMH>@avF-O#wC&$>eexUoxMc96Snha*STIN z<#ORQ9i{NNNlFU#a5^mJ6zSv;$%mFi`qtv}V39ybMe|kU?DuGg>DblOD5XpQ4e%)? zW2%N9VZp?uo^vH6&6NG6ekEfWt>lzS=G^@DpQ}5=Mi2O5l~h2;e;c(dgQ^`tdvD9a z7Auu+Gl5MxOEF#KKt-fnz}FA>aE$6$%a)kmQ2h*z*IhwbSj!fbJN;=}1S^!i=DLR( zXfxt(uR)jDrzY(}MeC)P%~Z+D2?yJyk=wBF6yXHdI0{cz!Ojw&X5*G35MI3++zm7v zfrC`WuYk!eoG~d_JG=o zD1RQh7<(Pi(GSFt-~-5FLuFtavt6+KFykE>!8v6ay(UgEG(z)#F_?daF(=0 z3GaMIYY@WJ7TBfsn`sbokIkoW3o!cgASI`$JmX0Hi0^bTVxr;~k=W`m06O7-BwdW_L$ z`fyW*Q^;N~_LsAZs#!eQaOt+0>%Zu(U>|8^iklLGilq?)kMk*TK+j#=L4L|WOtNLs zCGZEkAnxZ58@q#z0}@#p+VJSM7n#B@of2L=i>f%OA6$rlbS#d@tuqK{r+;BkCYr#V zLDz`SC^GnNozzCP4&DM}c}?gtU06WZz3Xo=Ng>!b2fD@~TSTN7pVsLtm!B@uzcPT_ zd zN}XSgx;}1;AY&nO=jB+7FNxFZ6jYq-7kp$hlK2#S1jcSVA3k}dq^?4wY#FpQJqdr& z@QHLQSm6;AO@CC=VuDrQ+E0mmP{$5*`FN|gPcsSudlMMI*NU(PbR-4JUYIhK&clzB z4h>E}LyD9(1M>m(CeIs{#8`;Qd6E-gf|c(-Oj&)9??-x}{+=zJWs-_V*9}K+!F>U& z{kDa6jtmFd)n#2U^o3p^(&{WJKhj;miZbQrIfFU*U`g=#`3YpVADzR1h;oP*nRgO+ zANoquf_D)y?W4U|f&XL3?g14^l+w0=$8^3!9931-^~vL)HsaB^BNp@U_agdit2sz6 zbH7ClU*C!Z$RejC$a-9H$r?todtGl)Ha!nJMq80-T`x6TPIXYd2IEars`=At`f8)p ztnr#*hIADac0`r(JlR(sC8!@w5(t)fmsYR0X>k9{q#Z|2q=Tl9bq(grXyBS25j9IQ zSM$S78H^c1(`>#2GLvv)_uGwhBO~_pc0lF2JB#i*4HWj+z5!3J; zWlZ&TBIJtJZ!UTZ_x--|>@2vnjpmz;=g+MM&If1q&D;n9xn`DW@M?EAomxhVy(ZnZl zy58*);g4&YOt9;kJK2;jzuO@(;?I3Hv7R*PGY90IZofW0E;a`j!?m@$**f1|(7E<5 ztF5JXy0}4Wer*_DB$#;<7C^hl%1-u+?r6{6VgrJ&TYcx0_zJ#L88iy^I8P4`Tflin zt=c6H8;0nSeeHs(wNk78ZYHsXI}Rt5N)n=HUL9uLx?PUMB4_37*-y+gJJD!kdpWil zaI*K53d2CIHNB(C5*0XjON!2bFQ(RHF(C(El92z|9P*?(*t*$4{zp?fse90M4yuB@ zo#dr*{D!XV?U<@7A5=9Yvy2acOOc2*!RJmAT|6oVM_LtSOS&bLkoQqu6_o3kYi&>- zPnw+CXYLHBb~QH79q$-w=%veZWv(*|cn=WRy$iWg!H=L8SSHn59LF2$Q-y(V7aAOVJeHS$?|54ZQb%pi~y73U6 zKupVbvna)&KWufzK_YEgd?~dtI|6TLPaaDxMs(|Vw`~n5o1pE}sPBVwW=a(*0U3U* zO6de>Q{O1h^lHIrIx?QD&LPE+rC}t0XM5hz@uq+N@J7Dw#?v(tZX2IGxHNj#0hjDG zH8fx0_84|?)k*AaY2~tjz(aN)DuVxlRm@#lHyOE|6{}~j|LkC|e@-IIYs0?2p<@#- zvZIeFpB0lLg7_P<<#kIQms?5#?J+n=}~bL>84eAvUFuG3GON zbH-feX}gP99pu7!yoe0N=*63SyvQGi9U&-zr<3M`nx+gRS4RK3*Nd|$)`w8RV?EO7 zBS0QE*naGz2@Q5D=+`uBoZx1c9VLV5A2)Tr)q|4J13ckguf&+WqsL zufX>4r230Xum21zpm+t^q6;IFiu2er8kDvY4~{%49WmW?{pO_#H2%x)8}Q<$?%l zUA?%S#SqvOTQ41955fR^?OTW1=CMq%nK#ibl1Q*+el}MC5)ghW(ygPPlwaOQVUk4hdhLHh`2Nbc+?5r0Y>%$ZUY(aad($A?P3U7 z?AI4!Ofm!rO+W|S0*W}-cw?34gfPoKUgS-YAslbJSjwGp&3llipIO z?F6n8)LRI?j#@|!DZkqNK^=Lxo%=g|cMGPXs^(O(6g4dZG8YYrx0N-@p)nlA46(LB z{#qshC@f5Cozi5qe@6;$3p}jwsv;X2h`$6{9TZtdW`YE&Z!fRr>zjW&V3Hdny_tbI zWtdTZt%Kf%$_JR0lQl(5k%-9)yx06oS)Gw8Qb7`&G4tn_dobZ^79&RH&=aAUOC)Ax z0jm%t{MRa%2xTsv@ZSg0urBuK0YuiCvNn2jQA3e|asJ1cWw$L?)^+ zc-?3VXdG^Zcui@BD>hW9UfT3VC9g}?Nm!MkdkvAKaD}oQrkN1 zD1DQ$@CT_i{fZkpzl~7a1UwN*j+po!q!C?lu7#XbYu;nu(HtoK1$qlaYvie*O?_vJ50%9i1NdfqE zwP9#2^>Y<5_5Gi&=2H7U2CSF*U?60MCAJ&d6wb}4qx{)*g5?2RXApGzITr&jIRPE? zow0On7)h9K5d_!q*LSOo>9O2y~k`9iKKWK+9NFJodIB0_90u zC=i24D9^*ZB-*jMsK7CNMW(R%Ju-t{1j0e4jw(dK>gokkDC4awn06v!Jte~rW2^Fc zIgBvT(h;z*$nw>}pE)sn*|jr1wLPvTIp*`&#(^<#$I=n|7rmCAg6&%3o>6-iYOQiX zH1V;(T1Fm>WsM+yP{+@KFw^kuHztf`#BC zfXzfb#!CQ=-M9yMb@U0s`1))N;jn!=#snEP4t_7mm?d2(H2x2w82 z59sRbGxYll0hXSC&bLa+Jp+kfID>;OiXl4C+##BQa1jq3;pI81+0-cX?5(Vo;m4rS z;>acIMnE?~t?)8b?hi)j*#PTbFG`PM_dMK1nAy{f<0;U9f4!1HF(1x7UBMb2JC1;) zyEpgN`nMw?<(JFx=GLrw@OyWLaH$rVJ3eB7Fg`n-^l!pwJ{$ykgFZNJykO~Pptz8s zQGzw5HhyPa75EU~9Z)9U7=80J&ow#e<;>Z@y;VHV`zPVzi9r*GOjIUzm{f-qft6l7 zfT)|A{#UtxCMoj_^#DoM>Ot12n112f_e>rba92NAt9iEc#l0*}8-S(lAk3)gP2O&V zBIWrkbl@`6WlJjI=1tavN?V*CtMr@SIKN)n0RQlnTX1SpwYEV4e3Iq>&Yzo1hx0G6 z8nDxG8{oe-1iV|a!Kk|Wi4d3CZx{S*R1@G>+i6Rx;pYw3gA!ZVT+rYE4xvtj=QUKD zw8=aCbf?-J)L;16oSZ925{$(nFepIyW35^&?!t(1!t>VyEvKM!=55D7@b`$0Vltj( zXtf@i=})W_0THaudYH#grV7uj?s~7V{i;?U+x2g-cKhDm59_nLG9)CyRoR^LXNJ>( z{IerMBtxw}4os(b+s7LS!PvCaEgJTe#|%Chl;;)1*(CY<&ep$C@>wp2itVYf=ggHk z)%b^)OsN^-P$JzJ8&Ri6EmS23?iCD_l=#nXPNQTgCruRVSrCM3W+mC_tcZQI*81%N zS)tE!`2b-9QWKO_|1R6plc6a8Vaqq_3K~FG5#dzGMS- zTw?#$7M=p35C#_7JK*2?BW5af^onoY!;!7sj%7%PK$6>Vwk}`P!z{NH`giPuF?KjE zL;lD$4-8ybm5?-kdLueK$Oi?xbMw`+!JU+p&FwRLcK3_JnIvc(!B(xYqSS8A1H523yCTUkH(!YfLk7c;UQIJ+7xp zr)#|SB*WYRWNV}rPjD&+Xr)ok?klR1G#wlAp|Noo@E%^3Y;{U!C23M*(_E;ndME-N zWJ`160D_?*t$J@V{a38WNW*_Vd-f#-oCe*%{6AI@U@7$^i_c8y4l$N6Sh~ePFIg@B zwlk|0UWCg2K@U9{V1>)e(S+@d#0j1AMuqG3A0}NJ8G59Hs5E5_OAfwEO)k#HbZcZ? zJ~kH7wDxm@j|jQx_nj=+Vw&&s-K)FO6#$|O-m0H;1>H!aVoc-G{_R9(rwM@ z+R%;#Vz3@|l|5t@bOu}K6NgPd$0B-s2)^H9YwB~^D;71B4v%9slHTv?x~kGG=*G&* zDs*~Am(>u1V#7mJXB7??8zt!dNf8%i8?fq0qIjw5!4|1uF`7kF)Pg4yM|5Np6vmC#_z-ZQ;&i-G=B2xdl82tswbsUs; zH$R5~Wr?p?$b>bmb%sh-diu>!+amvh=sB()0H3edk_=^dzq*r8KY@NS8$-}60@h6l(keRb?y!hcIjBcOr z*Ab)l5yd<>*Vh-3Fwt7qtQ!P>!|&7KyY%5;-=Ae``sGqVyN0W>4^8OIjDAW0Tsh-l~E2&LdK)Ko0(yRcJen zuniWsfp$g}vz;nMPfk^(E_pmXx)F&mGy?qB!nyv}!wS&jN?$`ZFuCRUl1Vdb>Wo(S z-v+!x8Gq~mUtnx7}&zE9`>7bQks(mzbA4@p$7NH@o+nq^|mbUQ%W!g#?g<~li zTPH2wi83|5ZZFjnV*lJ)(|_U^5K~P5n*jS?8(;kU0GpAK?SJc=+WB8OY2R#!Qkx|W z>Q+L>R?IoZ$wM2dxdG>30R^ysc21ooI&HDC{r)sE8cig%MO#%{75yD1VQX~kdSs|Q za-S@eI2mtx$m#9%RaX{w>d0=-ovnSq(w(Kfv0ZSC6IX7LsG!=#{_Wtdx?9>^JW5HW z)nViHa+`R+ei}Ni@e$s7+!7TqP5YFA&&BmR{XBK{a1b0mAAg_S0tAh6KaT)|(hB1N zEkKMDkvpt<@w@8UWdiy{(iL$5=W z7C;1Ss8cNhG8k};A>p^e`-F(xPXA3Rk7(80E`#Y`v|T&mSD-{&*)*_S(D`YgD^&$z zBi$FBag_#;;~iF5CERS-Kq)HDWLHpK{1^SiRXgxqp$T)015#=@@gad>1StSOTN}tD zA=Iu9l}Fe20{XAvT?RUr$f?_DT>#JxtPV_WNRaHCkSUIDY`ZELp1oNTPPg~3Os*cm zp1mF0Gj&G23tf#}Kj1q63b_ndo`YI;=UdiKmt^}vDAM-aZG!m$e>uh@PSFO;-$2}b z&VnO(Is)5ojAtcjOWi4YR$lD^Igr|36Pdzv#~(mt5BKHGJnUDwivS_in*v(pE`}Mm3u&Xe1-tZp<4KsJ%=plp0CsU0q_YifY;gwmGBFWH{SoVp1}%$>BXw_jbRiI zYs{grW%mc?8N?DwfS1=*)J88O#?BYhevvevm zR@YzU{5W`4LDDIT#Q{L;aO;p-b?s?i(<) zXjp~*LK)u+xoW+9ARrQ%1mwS3lUgTqdpP4Zb{ z5yD(P47}a_L#I(9G1%iZNSj819nH5=evXNQ{Fa+*Eu%!Ah}Rt>Clj4gNjjTN`e27B=PQ5PjS*1MVFRN! zErdz zf0Mpc%jCSCgKOQM?R)b80MRHY!jfq14F@+j%{hVjN3G-(HLl@8sr*3=hyZm~-G4`Y ziw*vrHb#<`6z)zQhB?NtgGdpfErw8weECr46lNhky984r`Zro%wKLqrUk0VXr+9nk z6$a5$f3$}4M)gmsLme; zDr^c_c6gD1m}!W-lFhTHfbq1%%r{XkBwVf8<4Qpw_nbHaNP+B+dRRfrGB{d3`>g$} z@}>5fmVQR|xi|;}UQ2A5PAZl6uPq2wNR49)Yxa4X8cpF%M38FxxJ^$g7C2oWdZ-PQDl z2|7UC1RJQZA>^0)nfQ|j*XOd^tx!8%6j=7L*kp^}LD-gVqBQM|bu{2}+WAu?lEW$a zOr{;#vtpn9_oe8Ou8VA*g1+`Wpo8;$j7){48E|rp|AYv1191rxpri5kAd?gGg7_k^ zF36^Kh?`CUK;UU@7ofs^>xEx*O@G+%KwjU7uI$D7co1j#iDvp$FqEo~p{UFKI!Z5e zh*d=S&;sCuVwDoCBa50tlK0dpLp|NNIuUdXPhO%-e2-mFHYG1aaHte&0%6qb!inH; zc(j~dv`=-3Ygr~6Vb^Rg+*pl+=c_bJh@0~PaTSzQsQ6*jq>I?%EEg9NQsf;&6+qQk z)kM^`i$&KH_%<<%ehjl`WjVV7j*x>DfvwZKVjBB(WK^6_Fjn%aB@`DBW}4+=9N(NI zY8yF`B#80z6UtJDh`S`kx&BH`0K%|n%yTA5(ZOOKQNfE~@Tqu}tEt$l0PBZhg2=Qj z*MqCLZs)KssZt6c2fF9A_v@L44tgQ#Cwh4xJ44n@>iH)~v{N#RmZr5{PNH(Is?rUl zHM1ZJyg=WO+B`!Iwru^KF0lyMV~+fZ00_TV0}R=nsLtqu9NiAZ#Ly9;kEYuVq*K>Fh(jEG~lL4?_?#L z9kI&BOIc*img%aNCR%j(+kvA0@}co*;HiP!$9wU&p2ooi6t(`b0(cQH7{~ETxg`+; z_*t%Zf#!rnL+_I${&pBEi%DsFSdwGtyd)OUDJl|p7sB5D53ciUXr8q)Z*m(PN>BQ< zXoW&#ss(jQAD4*tw&(ppm~Vack7wYkbhlq$&JcA|-&SNzqN_LE>f-FHEA6s}J<{Iw z^q}HGw@Lvlwepj300_pDY2Z@9!RAI^q02M8d{l5JCE79OxXtrn!h`dIgHOlv<}Xrm z9?{xb5n}x158rWn1{~>&93eJ;gNinR!YQn+20W}~=r5{zS60T(?*UjQuBorp&vYm9 zMhFM4zO+X&YRxZV!Za9!AaNJ}EJH7lV_vm}EX=Vot7@q4{wh?O3e9)^AajN-*x&Huv!0Dyv}W0Y5CgaM>$q zYxe1zC^@e5(Zc#M<5QD>Y<<-8Z*Fv|x;k;?7FiCm`_trxG!2sSReZ;y*RqN(`y0Zh zEH=k`dQ~E2(_1djVe4rB+PnQ7i52bJH8z1J9LR;zyLoY!bYJ&uj*v_h$ zNnnWiONw8T8yL@E`!3JS?wShb`Cx);4X^Usg3a?%Ysnw{-wAUzjtit%^{7FJu%3)- zyewq5r=$|P6%rsNIF8|__3RF5-3-C3nni}Gha+o?1(N0D>Y9mZ(~r*PW^T42GUaRZ z!^q}HZUcVIE=-UP9JM)~qr{NEe5XN7kVVxA!E%k(`)tDQKcJ}j4C9fb0 z%B4a!LILZt-KD(-StzDXg%J8eiDd#F~doZ&8WpKdIlL*SH_sKlabLq ztkkw3rza8T@bg9)&55#OD>~ETSYbTJAUn8n#eEw^_72T=>8Nm-rBod$7eHyuj0~##PdRK$o zM?B{#?kzQrAMkbZk{I^h{rkzZTBxu^qk3-byc6-{nG5|Pw*VlpW^evYL@VKMziev^ zqnSa?9waeUSf1?x#PMpM+|BJlwFqww1w@W{lT(;M{g6S+4(wv1KzTt9dMdJ{N5)2# zy*YV*(N+K=vn6JX>cv$Vv9J+>YRI9%W_GhR*Qd{d(ST&_)Whb#MRRO2v?+=D>=#tyX17S3LAhEag&=ji7=2ifC018JVWQf zI_nvd<|F(p1xXV3eKalgL&3oE{-bNMg{!hH1JU-cZhq>j`3fg4Bts0 z%?(%pZWE^fyy#9?18_`L} z`WlVVNhG!_3;szz)oG^-bUBB(@1X^@nbiMsc_7)C;gN5Ry?595o*gP$tFVr-;@4kc z0A<L0)lOC#^}4Pw{pvra4q3xk*Y_s! zTjqbgViOh%$l)7<@u`Rvg9Z_8+sBevhi!^^>$p-kO3!-#ECw;L4Yjo{)7bcIAjtmwqWA3u>i_H`;w-D1r3rk8z?lr3bXFj>{W#i#mew%%wG)3d`i5dTJ$-u%15^F583_rF*bo^t7{rMoF zWKn_##Hn#AfkT&DaZHEn&j#}&=7I-bR~dn|nGs1zSzwQn?X6+sl?>eqA3Li?S*WMu z2Ipj$Ij(;$ztK!br~5RI&`i+YHF?q{B?PgM7}(~@kB<#dCSz=CCBhC2SPGXAPI;CM zw#3Hz*-+zpmey&PaBVlU4h}`kKuVb%3V_gsc>aK<=8wQ4Uq9y_yh~OX*fi*mUX2H^;d!_HVWJ zoZm#^e!dVrkE^u*4Y~7YDboLq+~H>X9|;HgCl0IJXm5nXZ^42Dr)OcP63TIUt76UT z>nGc7t5S~7xkv9J^9w{@%L;5)z=0|AX}Q$HSZPa0=z6u6BUr=RrhYd&@jQOY3;h7j z%8p{iIKw$N&et!7oSF5LGT4rvwdiee=g4I{WU5CpO27>p&@pLgLWpc|Fw3}@Ti@ZkEfkQMr4egV++2J3|e(yl=JTL(0qL6CkxhThR>0Bo`%L=A&(h)oB zn%~FwLyY_jwEIH}KYDIF z^bt|wq>hlNKi>pyKul1<$Z4w?*LBIF?)KQJ`3SRT;QXL4@NZO zB{)TIYSi?yry;=TGN8rI{vN+D5GG(dAlsxT;y#V(Z2C81WWeYrrdQAA8Mrr_qty2e+*h7u_s_ zr6X=y*7FckRohG^w1U8IV7^-A?SA2i5$|jvB^`d{KySr9kp(}|Y+N9tqPlclhuM&f zivWRBw03CIGt#F$wVF?!cBo|iAX(8~I?s#XXbde(1nVtjhfsmcpKU*g4n7g5=i~>0 zb=lqK3A4b3<~;FiPFt0>DYq}UmK_Fl&NTg{tdUF~O>7pvjIu$fVnVY2SPBwo>$+D&*#Zx-^*#36r`WZl0BZ43Gd`qu#3b&aAt+=PaUc-r z1c%(rXikXWWT@zH@tYdFNbG4A@=6kCO_cf0y|vu1`e~h_H6gmZxX=(h1lYF?*F#mg zY$J--D8@^N6WFD0+WWTpmWBsu_EMiMeQaTz<~BTrHX97TQ^SGHWX+8BQV9bVA?hA)AJWB*q>{8LDXh4(EvbQy})a zM!2Oy+gXZ4VpH=+?iXT!$xpUN!@RG%d0>>q_di7yIH$tg`SQoC%Xwy%P|x)3fI^yD z1yx4h#N;qNA+&H{dBQ`}Mn*6{e~jppDI6;1sl|079Jab$|I9Hj6(#eJkRfDF4;A)yNbxS1U@q+X4hf7GYyXW7?|YM9Me>vh7qKLF8`Re8w$L z*53*oE=&Ng;o!xg`B_NCZlyaX+ej^ePYfYkd-GFVSimyJ_9z?<4YTJ@m;e?uXCxdp z%*6l}RTBHmvB2SZLboD{CO8v-ysN@J32HiVU@rJI*LC#@=w5)oU)CCQXY36~dCEgI-in+-iq2#@oTMEe$aO{Db+dT|X>}ahHtd52quz0CWby))hu? zZ8Mf6L>QWB=t%}|a93-$P;9E|S|NllqXk^aV4$hWbRi30!W~yO(2w zKt87w=~nH=8|zc(M^GD0o2Cw!i%z^-kn88@O)s^btU>Gik>p4Xjlef)oq2ofMg8KS zW3`E|52Ef`Xr0p~Rb799srD0wfvY{`wIV?#o2zvS4~J^eOxN95)fM7-WEV=QrQZ$MFJ|p}j2E-zWwsnsF(io&tSYI308#bQkwS8XAHZ^{mZhr^19Z z?mF(KXR$ikK4jBu9aH|u^~isj2cg`jVw_)>+-DU_ZfC~)aOpcJ5)AD)!VNOOo7{eh z+cJ`l>KBkAF92o`6Y^zu9#~3@O%dzl2&a&za}^hzJRTm4poG}`O}+{NdJSv2Z4+6X zPF?i67eJ)DWyzT&9RGwC50YtEnoz7a6}I(23m$VYVgQTwb-h)4+V0D{F$&BOMNYlC zZ9hnY({PpbBmc-gbk^iU*IjwKPZ{}f_J?eobyVjv(v5UZd2yX~wz9V#6jS9kjgl$s z{L;$0lXUfmOJ7JzjF(;orXzE^me&k-mgq2;@o1Xm!epzJK!W>A?G`dA7z$|Y(X~qW zyU!sMwQVyx+a)r~`SXMYlS|mpoEeYD)bR0Uv1sWsU2E!KxCxJmt*n>)Q|n)JW8O;_ zZbXfnqZ9P1Ja@V|kTqGA!bb0)XD-;x+AN?WDV-xVLv!!1x-zs(2wj>|?j_3#?Oo}s zIzT<_No&!5HE_j|{jv3(k}wUJ^i?pS+2ZrL+{PbGWbx-x34<==6I}I0I@dC7YW0`c zW3)Tc4LG$!wF5+h5l@+Rb($*wm8=Vqs+ZuMkDjRTbB{A?ZxoX1pWEwPu0%CxY=fN+ zCi-bO-@4V}CtHjXNYaOfp#9|8mcB%1j^ASPXewrHT^!Gy{jhVOMowCuvX$k;9&Y7@ z=}ioQ)vb=xLw$cm>j;mECQqBSp&iO>CIwF$Zy7YTtAU)j4S1uyx;TfL)SA`ipCqN4~C>yymNb#LHH*&qu+qE$DjDqch3IV`A>($9lJ zB5;_BNcB<59IP^*+9g#~9PQBEJFyyUxgNSFQ%+GDhHbh+K^O z{qDzh*!n{7ma zqU#dzrz70w@FD>We^|`y1}TM&&f<(bf4d8(Q^wAIsEkpEj1#>%78W;eQzoa>F+7Pj z+5@eUR6t>~U(8svb#`+EVv#I&h6{=A+Sj8o$3s%ib-U|7{a_>dwkOz>~b<>KL+?QTq!MiN%?bS(VAlrmow~#AVs$%XP-ks`6-%*05 zC51H8yz$8ZTf%On%VrmA-(M=Xh%3^iu_GG+*J{(oyGyg6_~i{clCmJ~Z}^-)`>p&v zpTow%`aiN^8ufG?)|$}#xVGN92iJE}vED}{MRo_6QduFT_r;(UOWMji{TmIa@)gRD-R^6Mn&4umk>%%)a``#z99-Apv@rJ*%RndOAc(;6c*KkmC zGH&=v!Y9StDQlu`ceMBfKg#d@&Z?Q8bB{q*cC9}WGXMFuB02d@YFHQLC)+H1!j~uX zQ6yvo0|G+)E-VDvD#~zZFkCMLgP3hvGf68XOjXO9gPEFk$mqfI?+o-C8~B7ixV2Y? zr^j94(A7o_n@GqDiqCjWFwrK2$f03fGLN=MWYB_aDwiK2vnI(Oz>e9UtP(}ebj(Z{ z^u8g}Q^SZdrlO)gSP3*lyB{x?dsv*d1V1oYcnM~>*T3D_Ca0W+_d|ecwpU9qjP^5< z#jknVEY=>83&QinYGg=;MFu-2?TBIlm6ULZg<>d0IAjIUK(Tkp{4q8ucF6>YSw_(H zq8K8KxieU&whv~dAHmVkolKh!#sbz*z&IxsAjT+7Imj_*cAc5hsB7t1WOYS{<3}mQ z{DzX5)8TsyoI${5zoF=b!5I3PEq>LrOnYweXJ) zt%)PLIzr3d@beV<(yi8=Z^U&CZDvA_su{8qxDgx`!bUVeS_jZF=A%eokt!ulYN;iT9w+W>C=s(K5(AezZZa*2yd-QbUJv8ZXIX#+W1g2bL9TiFcE zm0!hzgw(kqKBcTF)@fg{2OHhUgt%{T#E5rSkyqtehpEbvwB&rM^d%}G=^oRx`+=s# z{Hz4ln=g^x9RzNIEU<>Y7cB;liA8Y$->@&iM?D`vKI34L2HvF`lzybO+BTLudyBie z+kGPu_#TPJAA)e08Emm4>fOoZ5YwK~=l2k6n(^QiFv6Ntkfo zWtNlT>1m^zkWcUq!Set)aGu`n5@Ha2K0EYj)X79165gCwhLj(y# zm$1(YNr=kf<-E%p%g>-@>pZAZ?;EJ8yT5r)b^d37C@`-1zS>^gb*&BPlGVv&g!6ZlIA;H?+TDrV4-ssXKF&02XboW?vA$DPz#fUv?OuwY|)a5!lw zJ#jj)NTPFd7jhPcMNS4=bjJ_&=(N2kB;8zM(D|A1?|U0Qp)ixyy?D_Ziqyz{NSI*7 z(w%^ALZaXZMwROtcUS528-7@0oL{l^FPN6$(+~N!UXYi{(MnLRVwZKvrX4>PwH=i@ zR{CwqixdQ0(XH0mE^DYfl3|T`dTNa5Kp5RS00%qpyXj#`3YxZI zymv9h6Wp@ znN>^U_oV}l1-6?*%3|JaY!hY2Tygb%O~c!qbbEO7MGCoY=s$%E8qJgB=yDI(#PAdb zSSJQKgtj`_OU?wv0*HMJR-^ffMmp6w+g-7DGVoHYG6v1=prgi(%7b;>D*PEt%Ari2 zL6Ny6Z2eLCB!kiKNbc=;(}=}U1yQ+CSwYRoOb&&(Ls&>2g560U255zt*KSSb)3H`K zzW0_{M-x`)%$DRpHHb-c+}<}&Rf#Zl?Bn>}-gA|ez&_WMRcwNlH;oflZ{&ey4$4ZF zAK#*OQ;3A5$rraG=~XOfOkb9tEi)s2{z zS)aWUvVx?}nHFw}ooUjSv#2o0yGxqE;()PBa;QU^&8M?G_CvW+Y$`|gG8?96fu!pf zQM;jd`b(G}rM#g|!IQfP<_0`zGmeU!3WEr z4Puh)LEE5Ay|vH=Q}QR_IE}+KV-5&(jE*tAF}?4gspCOBu5~}XLP%tLjH}i$#F}9# zZ(C4DgSu3eu;&6vv?vvs2hF7;xO9AeLO^xHL_jLlcURs^gTE8~mLe9M$v z$A_g8umu7OZm$JRWKMW1qhvUzio0E?@pil=a?gqu_u?MJo2ZJ@@{4e1Yt8%T*=khq zkCA&bDnuhM(CP~;1C?nU&gw1cj|UD$Ix!;Se9RF1OUAX*U8Gb*r@n-FlxbSfyySR* z<X5ZmC{XZjHrs+S$E43Nc& z$PNk)o!z#Dm>AWJs+gOQ9x+t(JC1H-ZgoFrh%=_v61Vf5eYadqR)wM383*pk zucmC4JWGODI%?;#RH=f@ZctZQU=Mh$GT9{kW=U_8`mx-B`miA=jn>(UFW;G@uW4tA zPnoK4fNv>A!aXv$W!r@y5HYuWl^Ei3SG@}*tRR@Y?%tR2`s+F;9)-_^>A<8~cGgX}!xImsa!KZ}=Ks*JM^ z8pLL*f|1m2&UG6yVtFc<5^OOg4ermxk4(19{SQ?erKyvFBt;+RGesPguAaBLW!56WQ4spC2Nh{ocfNafy! zpV>99DNi07n@Rd?ES)2=4E(GdRZ`(f7bQ<|d(hyP?3~q~HaBK?NUsoXC`$#}q^^7k98bmoHFtU%0$@()ar*{ZnZp@g99&_r`@`uv zrGAw40{uWkz~7=ct3M)##G*@>N>g@24@2L^MBSafN^YVgV-tXPEO(_p3dz|HME}h{gMYTQ{96q~ zR?goRyFm9(Mou1LR_?y45f#*UU&rpC6mUWUIdf&qq(E&w+( z2cZ3~nS-67y_+4-mDkV$U=MIM1}Z}Sg;3&u+S#)H+0F9rL*rm$NYr?yu1Zk|lQ zQwB$43xLb7BUUC0fa@R4!T5I~s2r^S*{lAn=<@gAxtST+S&7+xNw=~Q{}IYR0sm`k z8{3%xby|UAg3-!>$;8&dg2~a+k-^TD;TLcQM`z&R`E7yz|4&OCzqdvGdqDg-SMKkj z1N#jZ7cmbzBNxyIo8@;P{{;H~R_|>8nwS{b{;tRTR*?NS@;`3``+MX-|9BQ)rLzDR z2_W*{ar_hHzb8}&H{f*R{D-l*EwJmmIRluie%p+jdjW?r!+&|nGq}6i{#qAItZadL zx&RjjQ(%+)T2k!nfR`}=kImdnf8Fp$H)Hr?F>ztAmat%F{fj31Pak^LKQpF(A2?7N zm>oD&*f|-QfmYYQtKpvn{@0778NmHt&Gi>>|I>h;_5ZY{v#_!-aszP!yV$Q)rFE+*}#l{;27{noj?7nP&SxEz`hhh=mL2D$c{o$WHtR z^nU`@!phas&E)sGU}NWH^ZzYp|22^R>d%WmOt1fYGypZB|E(VVztHGB|8ra9{J*zF zg4cF~&g2=3E2B{ua)qC}8-}0fudc0!w=iSqzF%9>pdCKDM|$YmM4%Z)PH;Av{l=ly z@=4mspet=#)%#~&NfFmk3+ZHbc2`N6t51sj(EJm?@9qBO`o`D&r}qaw{epRR!!KRh z2AYrGi|I4X`uicD?)N?RdXFNc1YTrQ75UM=RZIJ*m=#mK{Ya+==B#$`3-G)JqEfef zMBQr&xgV$LcUK*4k928YB9~QPF;BgLqUzW>Is6d$_s*41E{}LK3)L8BvDt&HX zl2l~wVV2L^sK?$~&a=Z!*~p>!-CO*jdbuA8B?8T+Ay%gyHihJO)$OyS-CS`nG*w%> zm}nvwxPk32tX-75-vA$fU?i~59V7~ogLk!C1SW2npKF^X{MM6_Vik+W#lGx$95K$7 zFh`^9%jYmv#)}TznPk5+DBhszmdK{1DBzneXz|S**n<>!wElK9kFnB0JH)ExL46=<*oR;Pqv-`W|6G zEIs78)=%JbEr>%?cJfKyz5@1h8)Ld!9)c*}PvP|ueE zjv~8`F`GYFu1g{{9HLpc-uT0F;JKJwW|$=>{H2opp3V}jl5ej6$epxurPC2oM8q7 zom$O6JyP+4+@=W@uXUU>DRu_8i2V7vC-I#Dis$u-GiUEo=;AR&$!_(=P)&`a`hLnI z1|Iz-jlo)rg~`pZ$r?ZH6_{aS-zBU{mJuEVjqwc+C>DkOvany2Z%kelug4^DjtTsx zvFiBlwY|i*^fs?gIh5HW=e%9!aknS-4rp6okMg{^=KzhM^g)T>fM+-kMy4CcV_!w+ z<5)K0xyYsNP{ysQ(F-Ep5s|0hfFJ8x{-wlXN(&A2^>trNt0u5ONK4^^+TzbYk-i*? z)4&|dU2cOJYd22X@2PVp*a!Xy;oY)D`^@q~K}l1=!alEzK4%0id)&M)W((1kLz8<} z{j9<`BdTJh`@0m)`&Gu`ybAirRCS*;fIpn5r#l&G=B zU5-%P+0shsCDZIEpr++L-MzQvFf*BmFCuM;Gp0K8hxc|P%-?kr`Swtt}vs(Fl4Ubct8K-^vfG)4{evd2! z1P6p3PZFT)C*~&s{W@5#X{Pgul06AUIXy+zR-?{mF$7Ng5KC7VHG$nOF9wo5LS9l! z4N(4mXe}pSLxA|ub!R~)!@`BeJg_TUUn6`NqY~mxDd8N9>`=su%L6@5 z9Un@XHR$$b>?2z6@VVxC5N!&$F_%g>6lwzL6lh zv+9Y0!W5V*O5Z#MLz4>>W@IRlWx<|+Qb6pmq3dxgN@5l=(<%)T!U=h<-{W1s>zIUt z6N#oSEze7Adn0r-jpK<<8n{w-3FkVj5Xouomc7?Ygc_^fq9iJBf$-y&xY?(jd3wGN zYro?DSduWQj|)dwn(LX-c3BDyUW0;WGc^n~DWKSg?H!Tk3IiKIBmO-;VGZhY-wEnr zu72{(Ow3&U)sqiWvee0@>9l1YZ()LJC($b1SNw_IfO1>8vVdZY+$v++p;3*w+0oZ%b*n(fMo?8>k1KgkiN+Yy$4% zYv1Mcl(L0F*{oo-Jtze*S0@UJulp&GI&jcV1Wg2LqVJ$Za-!UTUjc$K6)AgY_+K!1 ztYV&c4h%+_@xLLZSin2M?0jxM#&iE zhXUqut_`ZHmPO5^U;Fw}Dmf&DqMaVv0%aoItrm>d#VcLQFp8oPZ&a%@m5 z$JWRu8&A>9qo4SGLfYQ+UD|GEdDR0_rb=5pP3;1S91a@Yip?iTk5p2EhioL#dI+2{*#><6trZ_nnE8TMYg>J6vd^q1fw zMABy>taqJT;KZl}N?Vp-+s3iql zUD)B(IabSXaKR~m%8(bHk7?My-XEu}-uI8RV)RI+E<#^3d|BExhI(|$rd$;P=E2~D zSRmrnGI3>Hkhe#8*z9Q&JL2UezkI%#ASA$Xaz;-RXy&vY7H5SyKGI8KmNe<1nbBQl z8Td}z+>f2QPW<*+v#@Q@)HCpA0oTxv^zvnPdVg@%/Z9(!M3$1HJLJ|P(Hyl8)_ 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

tBIWmMBC|ktVt~Rw|%6s{c##n!8+q9{9b7r^J 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 U(9f4Q|IEItt~>~/user-config.jam + b2 crypto=openssl cxxstd=14 release + +Mac OS:: + + brew install boost-build boost openssl@3 + echo "using darwin ;" >>~/user-config.jam + b2 crypto=openssl cxxstd=14 release + +Windows (assuming the boost package is saved to ``C:\boost_1_69_0``):: + + set BOOST_ROOT=c:\boost_1_69_0 + set BOOST_BUILD_PATH=%BOOST_ROOT%\tools\build + (cd %BOOST_ROOT% && .\bootstrap.bat) + echo using msvc ; >>%HOMEDRIVE%%HOMEPATH%\user-config.jam + %BOOST_ROOT%\b2.exe --hash cxxstd=14 release + +docker file +~~~~~~~~~~~ + +A Docker file is available that's used to build and run the fuzzers, at +OSS-Fuzz__. + +__ https://github.com/google/oss-fuzz/tree/master/projects/libtorrent + +Step 1: Download boost +~~~~~~~~~~~~~~~~~~~~~~ + +If you want to build against boost installed on your system, you can skip this +strep. Just make sure to have `BOOST_ROOT` unset for the `b2` invocation. + +You'll find boost here__. + +__ https://www.boost.org/users/download/#live + +Extract the archive to some directory where you want it. For the sake of this +guide, let's assume you extract the package to ``c:\boost_1_69_0``. You'll +need at least version 1.67 of the boost library in order to build libtorrent. + + +Step 2: Setup BBv2 +~~~~~~~~~~~~~~~~~~ + +If you have installed ``boost-build`` via a package manager, you can skip this +step. If not, you need to build boost build from the boost source package. + +First you need to build ``b2``. You do this by opening a terminal (In windows, +run ``cmd``). Change directory to ``c:\boost_1_69_0\tools\build``. Then run the +script called ``bootstrap.bat`` or ``bootstrap.sh`` on a Unix system. This will +build ``b2`` and place it in a directory ``src/engine/bin.``. +Copy the ``b2.exe`` (or ``b2`` on a Unix system) to a place that's in you +shell's ``PATH``. On Linux systems a place commonly used may be +``/usr/local/bin`` or on Windows ``c:\windows`` (you can also add directories +to the search paths by modifying the environment variable called ``PATH``). + +Now you have ``b2`` installed. ``b2`` can be considered an interpreter +that the boost-build system is implemented on. So boost-build uses ``b2``. +So, to complete the installation you need to make two more things. You need to +set the environment variable ``BOOST_BUILD_PATH``. This is the path that tells +``b2`` where it can find boost-build, your configuration file and all the +toolsets (descriptions used by boost-build to know how to use different +compilers on different platforms). Assuming the boost install path above, set +it to ``c:\boost_1_69_0\tools\build``. + +To set an environment variable in windows, type for example:: + + set BOOST_BUILD_PATH=c:\boost_1_69_0\tools\build\v2 + +In a terminal window. + +The last thing to do is to configure which compiler(s) to use. Create a file +``user-config.jam`` in your home directory. Depending on your platform and which +compiler you're using, you should add a line for each compiler and compiler +version you have installed on your system that you want to be able to use with +BBv2. For example, if you're using Microsoft Visual Studio 14.2 (2019), just +add a line:: + + using msvc : 14.2 ; + +If you use GCC, add the line:: + + using gcc ; + +If you have more than one version of GCC installed, you can add the +command line used to invoke g++ after the version number, like this:: + + using gcc : 6.0 : g++-6 ; + using gcc : 7.0 : g++-7 ; + +Another toolset worth mentioning is the ``darwin`` toolset (for macOS). +From Tiger (10.4) macOS comes with both GCC 3.3 and GCC 4.0. Then you can +use the following toolsets:: + + using darwin : 3.3 : g++-3.3 ; + using darwin : 4.0 : g++-4.0 ; + +Note that the spaces around the semi-colons and colons are important! + +Also see the `boost-build documentation`_. + +.. _`boost-build documentation`: https://boostorg.github.io/build/ + + +Step 3: Building libtorrent +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When building libtorrent, boost is either picked up from system installed +locations or from a boost source package, if the ``BOOST_ROOT`` environment +variable is set pointing to one. If you're building boost from source, set +``BOOST_ROOT`` to your boost directory, e.g. ``c:\boost_1_69_0``. + +Then the only thing left is simply to invoke ``b2``. If you want to specify +a specific toolset to use (compiler) you can just add that to the command line. +For example:: + + b2 msvc-14.2 + b2 gcc-7.0 + b2 darwin-4.0 + +.. note:: + + If the environment variable ``BOOST_ROOT`` is not set, the Jamfile will + attempt to link against "installed" boost libraries. i.e. assume the + headers and libraries are available in default search paths. + In this case it's critical that you build your project with the same version + of C++ and the same build flags as the system libraries were built with. + +.. note:: Also see the `Visual Studio versions`_. + +.. _`Visual Studio versions`: https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering + +To build different versions you can also just add the name of the build +variant. Some default build variants in BBv2 are ``release``, ``debug``, +``profile``. + +You can build libtorrent as a DLL too, by typing ``link=shared``, or +``link=static`` to build a static library. + +If you want to explicitly say how to link against the runtime library, you +can set the ``runtime-link`` feature on the command line, either to ``shared`` +or ``static``. Most operating systems will only allow linking shared against +the runtime, but on windows you can do both. Example:: + + b2 msvc-14.2 variant=release link=static runtime-link=static debug-symbols=on + +.. note:: + + When building on windows, the path boost-build puts targets in may be too + long. If you get an error message like: "The input line is long", try to + pass --hash on the ``b2`` command line. + +.. warning:: + + If you link statically to the runtime library, you cannot build libtorrent + as a shared library (DLL), since you will get separate heaps in the library + and in the client application. It will result in crashes and possibly link + errors. + +.. note:: + + When building on Solaris, you may have to specify ``stdlib=sun-stlport`` + on the b2 command line. + +The build targets are put in a directory called bin, and under it they are +sorted in directories depending on the toolset and build variant used. + +To build the examples, just change directory to the examples directory and +invoke ``b2`` from there. To build and run the tests, go to the test +directory and run ``b2``. + +Note that if you're building on windows using the ``msvc`` toolset, you cannot run it +from a cygwin terminal, you'll have to run it from a ``cmd`` terminal. The same goes for +cygwin, if you're building with gcc in cygwin you'll have to run it from a cygwin terminal. +Also, make sure the paths are correct in the different environments. In cygwin, the paths +(``BOOST_BUILD_PATH`` and ``BOOST_ROOT``) should be in the typical Unix-format (e.g. +``/cygdrive/c/boost_1_69_0``). In the windows environment, they should have the typical +windows format (``c:/boost_1_69_0``). + +.. note:: + In Jamfiles, spaces are separators. It's typically easiest to avoid spaces + in path names. If you want spaces in your paths, make sure to quote them + with double quotes ("). + +The ``Jamfile`` will define ``NDEBUG`` when it's building a release build. +For more build configuration flags see `Build configurations`_. + +Jamfile will look in some default directory for the openssl +headers and libraries. On macOS, it will look for the homebrew openssl package. +On Windows, it will look in ``C:\OpenSSL-Win32``, or ``C:\OpenSSL-Win64`` if +compiling in 64-bit. + +To customize the library path and include path for openssl, set the features +``openssl-lib`` and ``openssl-include`` respectively. + +The option to link with wolfSSL (by setting the ``crypto`` feature to +``wolfssl``), requires a custom build of wolfSSL using the following +options: ``--enable-asio --enable-sni --enable-nginx``. + +To customize the library path and include path for wolfSSL, set the features +``wolfssl-lib`` and ``wolfssl-include`` respectively. + +To disable linking against any SSL library, set the ``crypto`` build feature to +``built-in``. This will use an embedded version if SHA-1. + +Build features +~~~~~~~~~~~~~~ + ++--------------------------+----------------------------------------------------+ +| boost build feature | values | ++==========================+====================================================+ +| ``cxxstd`` | The version of C++ to use, e.g. ``11``, ``14``, | +| | ``17``, ``20``. The C++ version *may* affect the | +| | libtorrent ABI (the ambition is to avoid that). | ++--------------------------+----------------------------------------------------+ +| ``boost-link`` | * ``static`` - links statically against the boost | +| | libraries. | +| | * ``shared`` - links dynamically against the boost | +| | libraries. | ++--------------------------+----------------------------------------------------+ +| ``openssl-lib`` | can be used to specify the directory where libssl | +| | and libcrypto are installed (or the windows | +| | counterparts). | ++--------------------------+----------------------------------------------------+ +| ``openssl-include`` | can be used to specify the include directory where | +| | the openssl headers are installed. | ++--------------------------+----------------------------------------------------+ +| ``logging`` | * ``off`` - logging alerts disabled. The | +| | reason to disable logging is to keep the binary | +| | size low where that matters. | +| | * ``on`` - default. logging alerts available, | +| | still need to be enabled by the alert mask. | ++--------------------------+----------------------------------------------------+ +| ``lto`` | * ``on`` - enables link time optimization, also | +| | known as whole program optimization. | ++--------------------------+----------------------------------------------------+ +| ``alert-msg`` | * ``on`` - (default) return human readable | +| | messages from the ``alert::message()`` call. | +| | * ``off`` - Always return empty strings from | +| | ``alert::message()``, and save binary size. | ++--------------------------+----------------------------------------------------+ +| ``dht`` | * ``on`` - build with DHT support | +| | * ``off`` - build without DHT support. | ++--------------------------+----------------------------------------------------+ +| ``asserts`` | * ``auto`` - asserts are on if in debug mode | +| | * ``on`` - asserts are on, even in release mode | +| | * ``off`` - asserts are disabled | +| | * ``production`` - assertion failures are logged | +| | to ``asserts.log`` in the current working | +| | directory, but won't abort the process. | +| | The file they are logged to can be customized | +| | by setting the global pointer ``extern char | +| | const* libtorrent_assert_log`` to a different | +| | filename. | +| | * ``system`` use the libc assert macro | ++--------------------------+----------------------------------------------------+ +| ``encryption`` | * ``on`` - encrypted bittorrent connections | +| | enabled. (Message Stream encryption).(default) | +| | * ``off`` - turns off support for encrypted | +| | connections. The shipped public domain SHA-1 | +| | implementation is used. | ++--------------------------+----------------------------------------------------+ +| ``mutable-torrents`` | * ``on`` - mutable torrents are supported | +| | (`BEP 38`_) (default). | +| | * ``off`` - mutable torrents are not supported. | ++--------------------------+----------------------------------------------------+ +| ``crypto`` | * ``openssl`` - (default) links against openssl | +| | and libcrypto to use for SHA-1 hashing. | +| | This also enables HTTPS-tracker support and | +| | support for bittorrent over SSL. | +| | * ``built-in`` - (default) uses built-in SHA-1 | +| | implementation. In macOS/iOS it uses | +| | CommonCrypto SHA-1 implementation. | +| | * ``wolfssl`` - links against wolfssl to use it | +| | for SHA-1 hashing and HTTPS tracker support. | +| | * ``libcrypto`` - links against libcrypto | +| | to use the SHA-1 implementation. (no SSL support)| +| | * ``gcrypt`` - links against libgcrypt | +| | to use the SHA-1 implementation. (no SSL support)| ++--------------------------+----------------------------------------------------+ +| ``openssl-version`` | This can be used on windows to link against the | +| | special OpenSSL library names used on windows | +| | prior to OpenSSL 1.1. | +| | | +| | * ``1.1`` - link against the normal openssl | +| | library name. (default) | +| | * ``pre1.1`` - link against the old windows names | +| | (i.e. ``ssleay32`` and ``libeay32``. | ++--------------------------+----------------------------------------------------+ +| ``link`` | * ``static`` - builds libtorrent as a static | +| | library (.a / .lib) | +| | * ``shared`` - builds libtorrent as a shared | +| | library (.so / .dll). | ++--------------------------+----------------------------------------------------+ +| ``runtime-link`` | * ``static`` - links statically against the | +| | run-time library (if available on your | +| | platform). | +| | * ``shared`` - link dynamically against the | +| | run-time library (default). | ++--------------------------+----------------------------------------------------+ +| ``variant`` | * ``debug`` - builds libtorrent with debug | +| | information and invariant checks. | +| | * ``release`` - builds libtorrent in release mode | +| | without invariant checks and with optimization. | +| | * ``profile`` - builds libtorrent with profile | +| | information. | ++--------------------------+----------------------------------------------------+ +| ``invariant-checks`` | This setting only affects debug builds (where | +| | ``NDEBUG`` is not defined). It defaults to ``on``. | +| | | +| | * ``on`` - internal invariant checks are enabled. | +| | * ``off`` - internal invariant checks are | +| | disabled. The resulting executable will run | +| | faster than a regular debug build. | +| | * ``full`` - turns on extra expensive invariant | +| | checks. | ++--------------------------+----------------------------------------------------+ +| ``debug-symbols`` | * ``on`` - default for debug builds. This setting | +| | is useful for building release builds with | +| | symbols. | +| | * ``off`` - default for release builds. | ++--------------------------+----------------------------------------------------+ +| ``deprecated-functions`` | * ``on`` - default. Includes deprecated functions | +| | of the API (might produce warnings during build | +| | when deprecated functions are used). | +| | * ``off`` - excludes deprecated functions from the | +| | API. Generates build errors when deprecated | +| | functions are used. | ++--------------------------+----------------------------------------------------+ +| ``i2p`` | * ``on`` - default. build with I2P support | +| | * ``off`` - build without I2P support | ++--------------------------+----------------------------------------------------+ +| ``profile-calls`` | * ``off`` - default. No additional call profiling. | +| | * ``on`` - Enable logging of stack traces of | +| | calls into libtorrent that are blocking. On | +| | session shutdown, a file ``blocking_calls.txt`` | +| | is written with stack traces of blocking calls | +| | ordered by the number of them. | ++--------------------------+----------------------------------------------------+ +| ``utp-log`` | * ``off`` - default. Do not print verbose uTP | +| | log. | +| | * ``on`` - Print verbose uTP log, used to debug | +| | the uTP implementation. | ++--------------------------+----------------------------------------------------+ +| ``picker-debugging`` | * ``off`` - default. no extra invariant checks in | +| | piece picker. | +| | * ``on`` - include additional invariant checks in | +| | piece picker. Used for testing the piece picker. | ++--------------------------+----------------------------------------------------+ +| ``extensions`` | * ``on`` - enable extensions to the bittorrent | +| | protocol.(default) | +| | * ``off`` - disable bittorrent extensions. | ++--------------------------+----------------------------------------------------+ +| ``streaming`` | * ``on`` - enable streaming functionality. i.e. | +| | ``set_piece_deadline()``. (default) | +| | * ``off`` - disable streaming functionality. | ++--------------------------+----------------------------------------------------+ +| ``super-seeding`` | * ``on`` - enable super seeding feature. (default) | +| | * ``off`` - disable super seeding feature | ++--------------------------+----------------------------------------------------+ +| ``share-mode`` | * ``on`` - enable share-mode feature. (default) | +| | * ``off`` - disable share-mode feature | ++--------------------------+----------------------------------------------------+ +| ``predictive-pieces`` | * ``on`` - enable predictive piece announce | +| | feature. i.e. | +| | settings_pack::predictive_piece_announce | +| | (default) | +| | * ``off`` - disable feature. | ++--------------------------+----------------------------------------------------+ +| ``fpic`` | * ``off`` - default. Build without specifying | +| | ``-fPIC``. | +| | * ``on`` - Force build with ``-fPIC`` (useful for | +| | building a static library to be linked into a | +| | shared library). | ++--------------------------+----------------------------------------------------+ +| ``mmap-disk-io`` | * ``on`` - default. Enable mmap disk storage (if | +| | available. | +| | * ``off`` - disable mmap storage, and fall back to | +| | single-threaded, portable file operations. | ++--------------------------+----------------------------------------------------+ + +The ``variant`` feature is *implicit*, which means you don't need to specify +the name of the feature, just the value. + +When building the example client on windows, you need to build with +``link=static`` otherwise you may get unresolved external symbols for some +boost.program-options symbols. + +For more information, see the `Boost build v2 documentation`__, or more +specifically `the section on built-in features`__. + +__ https://boostorg.github.io/build/manual/develop/index.html +__ https://boostorg.github.io/build/manual/develop/index.html#bbv2.overview.builtins.features + + +Step 4: Installing libtorrent +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To install libtorrent run ``b2`` with the ``install`` target:: + + b2 install --prefix=/usr/local + +Change the value of the ``--prefix`` argument to install it in a different location. + + +Custom build flags +~~~~~~~~~~~~~~~~~~ + +Custom build flags can be passed to the command line via the ``cflags``, +``cxxflags`` and ``linkflags`` features. When specifying custom flags, make sure +to build everything from scratch, to not accidentally mix incompatible flags. +Example:: + + b2 cxxflags=-msse4.1 + +Custom flags can also be configured in the toolset, in ``~/user-config.jam``, +``Jamroot.jam`` or ``project-config.jam``. Example:: + + using gcc : sse41 : g++ : -msse4.1 ; + + +Cross compiling +~~~~~~~~~~~~~~~ + +To cross compile libtorrent, configure a new toolset for ``b2`` to use. Toolsets +can be configured in ``~/user-config.jam``, ``Jamroot.jam`` or +``project-config.jam``. The last two live in the libtorrent root directory. + +A toolset configuration is in this form: + +.. parsed-literal:: + + using *toolset* : *version* : *command-line* : *features* ; + +Toolset is essentially the family of compiler you're setting up, choose from `this list`__. + +__ https://boostorg.github.io/build/manual/master/index.html#bbv2.reference.tools.compilers + +Perhaps the most common ones would be ``gcc``, ``clang``, ``msvc`` and +``darwin`` (Apple's version of clang). + +The version can be left empty to be auto configured, or a custom name can be +used to identify this toolset. + +The *command-line* is what to execute to run the compiler. This is also an +opportunity to insert a call to ``ccache`` for example. + +*features* are boost-build features. Typical features to set here are +````, ```` and ````. For the ``gcc`` toolset, +the ```` can be set to specify which tool to use to create a static +library/archive. This is especially handy when cross compiling. + +Here's an example toolset for cross compiling for ARM Linux:: + + using gcc : arm : arm-linux-gnueabihf-g++ : arm-linux-gnueabihf-ar ; + +To build using this toolset, specify ``gcc-arm`` as the toolset on the ``b2`` command line. For example:: + + b2 toolset=gcc-arm + + +building with cmake +------------------- + +First of all, you need to install ``cmake``. Additionally you need a build +system to actually schedule builds, for example ``ninja``. + +Step 1: Generating the build system +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Create a build directory for out-of-source build inside the libtorrent root directory:: + + mkdir build + +and ``cd`` there:: + + cd build + +Run ``cmake`` in the build directory, like this:: + + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=14 -G Ninja .. + +The ``CMAKE_CXX_STANDARD`` has to be at least 14, but you may want to raise it +to ``17`` if your project use a newer version of the C++ standard. + +.. warning:: + + The detection of boost sometimes fail in subtle ways. If you have the + ``BOOST_ROOT`` environment variable set, it may find the pre-built system + libraries, but use the header files from your source package. To avoid this, + invoke ``cmake`` with ``BOOST_ROOT`` set to an empty string: + ``BOOST_ROOT="" cmake ...``. + +Other build options are: + ++-----------------------+---------------------------------------------------+ +| ``BUILD_SHARED_LIBS`` | Defaults ``ON``. Builds libtorrent as a shared | +| | library. | ++-----------------------+---------------------------------------------------+ +| ``static_runtime`` | Defaults ``OFF``. Link libtorrent statically | +| | against the runtime libraries. | ++-----------------------+---------------------------------------------------+ +| ``build_tests`` | Defaults ``OFF``. Also build the libtorrent | +| | tests. | ++-----------------------+---------------------------------------------------+ +| ``build_examples`` | Defaults ``OFF``. Also build the examples in the | +| | examples directory. | ++-----------------------+---------------------------------------------------+ +| ``build_tools`` | Defaults ``OFF``. Also build the tools in the | +| | tools directory. | ++-----------------------+---------------------------------------------------+ +| ``python-bindings`` | Defaults ``OFF``. Also build the python bindings | +| | in bindings/python directory. | ++-----------------------+---------------------------------------------------+ +| ``encryption`` | Defaults ``ON``. Support trackers and bittorrent | +| | over TLS, and obfuscated bittorrent connections. | ++-----------------------+---------------------------------------------------+ + +Options are set on the ``cmake`` command line with the ``-D`` option or later on using ``ccmake`` or ``cmake-gui`` applications. ``cmake`` run outputs a summary of all available options and their current values. + +Step 2: Building libtorrent +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In the terminal, run:: + + ninja + +If you enabled test in the configuration step, to run them, run:: + + ctest + +building with VCPKG +------------------- + +You can download and install libtorrent using the vcpkg_ dependency manager:: + + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + ./vcpkg install libtorrent + +.. _vcpkg: https://github.com/Microsoft/vcpkg/ + +The libtorrent port in vcpkg is kept up to date by Microsoft team members and community contributors. +If the version is out of date, please `create an issue or pull request`__ on the vcpkg repository. + +__ https://github.com/Microsoft/vcpkg + +build configurations +-------------------- + +By default libtorrent is built In debug mode, and will have pretty expensive +invariant checks and asserts built into it. If you want to disable such checks +(you want to do that in a release build) you can see the table below for which +defines you can use to control the build. Make sure to define the same macros in your +own code that compiles and links with libtorrent. + ++----------------------------------------+-------------------------------------------------+ +| macro | description | ++========================================+=================================================+ +| ``NDEBUG`` | If you define this macro, all asserts, | +| | invariant checks and general debug code will be | +| | removed. Since there is quite a lot of code in | +| | in header files in libtorrent, it may be | +| | important to define the symbol consistently | +| | across compilation units, including the clients | +| | files. Potential problems is different | +| | compilation units having different views of | +| | structs and class layouts and sizes. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_LOGGING`` | This macro will disable support for logging | +| | alerts, like log_alert, torrent_log_alert and | +| | peer_log_alert. With this build flag, you | +| | cannot enable those alerts. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_ALERT_MSG`` | Human readable messages returned from the alert | +| | ``message()`` member functions will return | +| | empty strings. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_SUPERSEEDING`` | This macro will disable support for super | +| | seeding. The settings will exist, but will not | +| | have an effect, when this macro is defined. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_SHARE_MODE`` | This macro will disable support for share-mode. | +| | i.e. the mode to maximize upload/download | +| | ratio for a torrent. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_MUTABLE_TORRENTS`` | Disables mutable torrent support (`BEP 38`_) | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_STREAMING`` | Disables set_piece_deadline() and associated | +| | functionality. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_PREDICTIVE_PIECES`` | Disables | +| | settings_pack::predictive_piece_announce | +| | feature. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_LINKING_SHARED`` | If this is defined when including the | +| | libtorrent headers, the classes and functions | +| | will be tagged with ``__declspec(dllimport)`` | +| | on msvc and default visibility on GCC 4 and | +| | later. Set this in your project if you're | +| | linking against libtorrent as a shared library. | +| | (This is set by the Jamfile when | +| | ``link=shared`` is set). | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_BUILDING_SHARED`` | If this is defined, the functions and classes | +| | in libtorrent are marked with | +| | ``__declspec(dllexport)`` on msvc, or with | +| | default visibility on GCC 4 and later. This | +| | should be defined when building libtorrent as | +| | a shared library. (This is set by the Jamfile | +| | when ``link=shared`` is set). | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_DHT`` | If this is defined, the support for trackerless | +| | torrents will be disabled. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_ENCRYPTION`` | This will disable any encryption support and | +| | the dependencies of a crypto library. | +| | Encryption support is the peer connection | +| | encrypted supported by clients such as | +| | uTorrent, Azureus and KTorrent. | +| | If this is not defined, either | +| | ``TORRENT_USE_LIBCRYPTO`` or | +| | ``TORRENT_USE_LIBGCRYPT`` must be defined. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_EXTENSIONS`` | When defined, libtorrent plugin support is | +| | disabled along with support for the extension | +| | handshake (BEP 10). | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_INVARIANT_CHECKS`` | If defined to non-zero, this will enable | +| | internal invariant checks in libtorrent. | +| | The invariant checks can sometimes | +| | be quite expensive, they typically don't scale | +| | very well. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_EXPENSIVE_INVARIANT_CHECKS`` | This will enable extra expensive invariant | +| | checks. Useful for finding particular bugs | +| | or for running before releases. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_NO_DEPRECATE`` | This will exclude all deprecated functions from | +| | the header files and source files. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_PRODUCTION_ASSERTS`` | Define to either 0 or 1. Enables assert logging | +| | in release builds. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_ASSERTS`` | Define as 0 to disable asserts unconditionally. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_SYSTEM_ASSERTS`` | Uses the libc assert macro rather then the | +| | custom one. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_HAVE_MMAP`` | Define as 0 to disable mmap support. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_OPENSSL`` | Link against ``libssl`` for SSL support. Must | +| | be combined with ``TORRENT_USE_LIBCRYPTO`` | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_GNUTLS`` | Link against ``libgnutls`` for SSL support. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_LIBCRYPTO`` | Link against ``libcrypto`` for SHA-1 support | +| | and other hashing algorithms. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_LIBGCRYPT`` | Link against ``libgcrypt`` for SHA-1 support | +| | and other hashing algorithms. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_SSL_PEERS`` | Define to enable support for SSL torrents, | +| | peers are connected over authenticated SSL | +| | streams. | ++----------------------------------------+-------------------------------------------------+ + +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html + +If you experience that libtorrent uses unreasonable amounts of CPU, it will +definitely help to define ``NDEBUG``, since it will remove the invariant checks +within the library. + +building openssl for windows +---------------------------- + +To build openssl for windows with Visual Studio 7.1 (2003) execute the following commands +in a command shell:: + + perl Configure VC-WIN32 --prefix="c:/openssl + call ms\do_nasm + call "C:\Program Files\Microsoft Visual Studio .NET 2003\vc7\bin\vcvars32.bat" + nmake -f ms\nt.mak + copy inc32\openssl "C:\Program Files\Microsoft Visual Studio .NET 2003\vc7\include\" + copy out32\libeay32.lib "C:\Program Files\Microsoft Visual Studio .NET 2003\vc7\lib" + copy out32\ssleay32.lib "C:\Program Files\Microsoft Visual Studio .NET 2003\vc7\lib" + +This will also install the headers and library files in the visual studio directories to +be picked up by libtorrent. + +list of macros +-------------- + +The following is a list of defines that libtorrent is built with: ``BOOST_ALL_NO_LIB``, +``BOOST_ASIO_ENABLE_CANCELIO``, ``BOOST_ASIO_HAS_STD_CHRONO``, +``BOOST_MULTI_INDEX_DISABLE_SERIALIZATION``, ``BOOST_NO_DEPRECATED``, +``BOOST_SYSTEM_NO_DEPRECATED`` + +Make sure you define the same at compile time for your code to avoid any runtime errors +and other issues. + +These might change in the future, so it's always best to verify these every time you +upgrade to a new version of libtorrent. The simplest way to see the full list of macros +defined is to build libtorrent with ``-n -a`` switches added to ``b2`` command line:: + + b2 -n -a toolset=msvc-14.2 link=static runtime-link=static boost-link=static variant=release + +This will output all compiler switches, including defines (such as ``-DBOOST_ASIO_ENABLE_CANCELIO``). diff --git a/docs/client_test.rst b/docs/client_test.rst new file mode 100644 index 0000000..04d4c2e --- /dev/null +++ b/docs/client_test.rst @@ -0,0 +1,49 @@ +=========================== +client_test example program +=========================== + +.. include:: header.rst + +Client test is a, more or less, complete bittorrent client. It lacks most +settings and you can't start or stop torrents once you've started it. All +the settings are hard coded. The command line arguments are:: + + client_test ... + +You can start any number of torrent downloads/seeds via the command line. +If one argument starts with ``http://`` it is interpreted as a tracker +announce url, and it expects an info-hash as the next argument. The info-hash +has to be hex-encoded. For example: ``2410d4554d5ed856d69f426c38791673c59f4418``. +If you pass an announce url and info-hash, a torrent-less download is started. +It relies on that at least one peer on the tracker is running a libtorrent based +client and has the metadata (.torrent file). The metadata extension in +libtorrent will then download it from that peer (or from those peers if more +than one). + +While running, the ``client_test`` sample will look something like this: + +.. image:: img/screenshot.png + :class: screenshot + :target: img/screenshot.png + +The commands available in the client are: + +* ``q`` quits the client (there will be a delay while the client waits + for tracker responses) +* ``l`` toggle log. Will display the log at the bottom, informing about + tracker and peer events. +* ``i`` toggles torrent info. Will show the peer list for each torrent. +* ``d`` toggle download info. Will show the block list for each torrent, + showing downloaded and requested blocks. +* ``p`` pause all torrents. +* ``u`` resume all torrents. +* ``r`` force tracker reannounce for all torrents. +* ``f`` toggle show file progress. Displays a list of all files and the + download progress for each file. + +The list at the bottom (shown if you press ``d``) shows which blocks has +been requested from which peer. The green background means that it has been +downloaded. It shows that fast peers will prefer to request whole pieces +instead of downloading parts of pieces. It may make it easier to determine +which peer that sent the corrupt data if a piece fails the hash test. + diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 index 0000000..dcdf015 --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1,63 @@ +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +contributing to libtorrent +========================== + +There are several ways to contribute to libtorrent at various levels. Any help is +much appreciated. If you're interested in something libtorrent related that's not +enumerated on this page, please contact arvid@libtorrent.org or the `mailing list`_. + +.. _`mailing list`: https://lists.sourceforge.net/lists/listinfo/libtorrent-discuss + +1. Testing + This is not just limited to finding bugs and ways to reproduce crashes, but also + sub-optimal behavior is certain scenarios and finding ways to reproduce those. Please + report any issue to the bug tracker at `github`_. + + New features that need testing are streaming (``set_piece_deadline()``), the different + choking algorithms (like the rate-based choker). + + Additional fuzzers are also always welcome. Find a libtorrent interface + that's not already covered by a fuzzer (see the ``fuzzers`` directory in the + root) and add a new fuzzer to it. Alternatively, improve an existing fuzzer by + producing inputs that gets coverage deeper in to libtorrent. + +.. _`github`: https://github.com/arvidn/libtorrent/issues + +2. Documentation + Finding typos or outdated sections in the documentation. Contributing documentation + based on your own experience and experimentation with the library or with BitTorrent + in general. Non-reference documentation is very much welcome as well, higher level + descriptions on how to configure libtorrent for various situations for instance. + The reference documentation for libtorrent is generated from the header files. + + Each heading in the online documentation has a short-cut link to file a new issue + against the documentation. + + For updates, please submit a `pull request`_. All documentation is in + restructured text (rst_). All documentation is spell checked with hunspell + which can be invoked via ``make spell-check`` in the docs directory. If + words are missing, please add them to ``docs/hunspell/libtorrent.dic`` + +3. Code + Contributing code for new features or bug-fixes is highly welcome. If you're interested + in adding a feature but not sure where to start, please contact the `mailing list`_ or + ``#libtorrent`` @ ``irc.freenode.net``. For proposed fixes or updates, please + submit a `pull request`_. + + New features might be better support for integrating with other services, new choking + algorithms, seeding policies, ports to new platforms etc. + +For an overview of the internals of libtorrent, see the hacking_ page. + +For outstanding things to do, see the `todo list`_. + +.. _hacking: hacking.html +.. _`pull request`: https://github.com/arvidn/libtorrent +.. _`todo list`: todo.html +.. _rst: https://docutils.sourceforge.io/rst.html + diff --git a/docs/dht_extensions.rst b/docs/dht_extensions.rst new file mode 100644 index 0000000..db4d94b --- /dev/null +++ b/docs/dht_extensions.rst @@ -0,0 +1,68 @@ +Mainline DHT extensions +======================= + +.. include:: header.rst + +libtorrent implements a few extensions to the Mainline DHT protocol. + +get_peers response +------------------ + +libtorrent always responds with ``nodes`` to a get_peers request. If it has +peers for the specified info-hash, it will return ``values`` as well. This is +because just because some peer announced to us, doesn't mean that we are +among the 8 closest nodes of the info hash. libtorrent also keeps traversing +nodes using get_peers until it has found the 8 closest ones, and then announces +to those nodes. + +forward compatibility +--------------------- + +In order to support future DHT messages, any message which is not recognized +but has either an ``info_hash`` or ``target`` argument is interpreted as +find node for that target. i.e. it returns nodes. This allows future messages +to be properly forwarded by clients that don't understand them instead of +being blocked. + +client identification +--------------------- + +In each DHT packet, an extra key is inserted named "v". This is a string +describing the client and version used. This can help a lot when debugging +and finding errors in client implementations. The string is encoded as four +characters, two characters describing the client and two characters interpreted +as a binary number describing the client version. + +Currently known clients: + ++---------------+--------+ +| uTorrent | ``UT`` | ++---------------+--------+ +| libtorrent | ``LT`` | ++---------------+--------+ +| MooPolice | ``MP`` | ++---------------+--------+ +| GetRight | ``GR`` | ++---------------+--------+ + +IPv6 support +------------ + +**This extension is superseded by** `BEP 32`_. + +.. _`BEP 32`: https://www.bittorrent.org/beps/bep_0032.html + +The DHT messages that don't support IPv6 are the ``nodes`` replies. +They encode all the contacts as 6 bytes packed together in sequence in a +string. The problem is that IPv6 endpoints cannot be encoded as 6 bytes, but +needs 18 bytes. The extension libtorrent applies is to add another key, called +``nodes2``. + +``nodes2`` may be present in replies that contains a ``nodes`` key. It is encoded +as a list of strings. Each string represents one contact and is encoded as 20 +bytes node-id and then a variable length encoded IP address (6 bytes in IPv4 case +and 18 bytes in IPv6 case). + +As an optimization, libtorrent does not include the extra key in case there are +only IPv4 nodes present. + diff --git a/docs/dht_rss.rst b/docs/dht_rss.rst new file mode 100644 index 0000000..6601c9c --- /dev/null +++ b/docs/dht_rss.rst @@ -0,0 +1,396 @@ +====================================== +BitTorrent extension for DHT RSS feeds +====================================== + +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +This proposal has been superseded by the dht_put_ feature. This may +still be implemented on top of that. + +.. _dht_put: dht_store.html + +This is a proposal for an extension to the BitTorrent DHT to allow +for decentralized RSS feed like functionality. + +The intention is to allow the creation of repositories of torrents +where only a single identity has the authority to add new content. For +this repository to be robust against network failures and resilient +to attacks at the source. + +The target ID under which the repository is stored in the DHT, is the +SHA-1 hash of a feed name and the 512 bit public key. This private key +in this pair MUST be used to sign every item stored in the repository. +Every message that contain signed items MUST also include this key, to +allow the receiver to verify the key itself against the target ID as well +as the validity of the signatures of the items. Every recipient of a +message with feed items in it MUST verify both the validity of the public +key against the target ID it is stored under, as well as the validity of +the signatures of each individual item. + +As with normal DHT announces, the write-token mechanism is used to +prevent IP spoof attacks. + +terminology +----------- + +In this document, a *storage node* refers to the node in the DHT to which +an item is being announce. A *subscribing node* refers to a node which +makes look ups in the DHT to find the storage nodes, to request items +from them. + +linked lists +------------ + +Items are chained together in a general singly linked list. A linked +list does not necessarily contain RSS items, and no RSS related items +are mandatory. However, RSS items will be used as examples in this BEP:: + + key = SHA1(name + key) + +---------+ + | head | key = SHA1(bencode(item)) + | +---------+ +---------+ + | | next |-------->| item | key = SHA1(bencode(item)) + | | key | | +---------+ +---------+ + | | name | | | next |------->| item | + | | seq | | | key | | +---------+ + | | ... | | | ... | | | next |--->0 + | +---------+ | +---------+ | | key | + | sig | | sig | | | ... | + +---------+ +---------+ | +---------+ + | sig | + +---------+ + +The ``next`` pointer is at least 20 byte ID in the DHT key space pointing to where the next +item in the list is announced. The list is terminated with an ID of all zeros. + +The ID an items is announced to is determined by the SHA1 hash of the bencoded representation +of the item itself. This contains all fields in the item, except the signature. +The only mandatory fields in an item are ``next``, ``key`` and ``sig``. + +The ``key`` field MUST match the public key of the list head node. The ``sig`` field +MUST be the signature of the bencoded representation of ``item`` or ``head`` (whichever +is included in the message). + +All subscribers MUST verify that the item is announced under the correct DHT key +and MUST verify the signature is valid and MUST verify the public key is the same +as the list-head. If a node fails any of these checks, it must be ignored and the +chain of items considered terminated. + +Each item holds a bencoded dictionary with arbitrary keys, except two mandatory keys: +``next`` and ``key``. The signature ``sig`` is transferred outside of this dictionary +and is the signature of all of it. An implementation should store any arbitrary keys that +are announced to an item, within reasonable restriction such as nesting, size and numeric +range of integers. + +skip lists +---------- + +The ``next`` key stored in the list head and the items is a string of at least length +20 bytes, it may be any length divisible by 20. Each 20 bytes are the ID of the next +item in the list, the item 2 hops away, 4 hops away, 8 hops away, and so on. For +simplicity, only the first ID (1 hop) in the ``next`` field is illustrated above. + +A publisher of an item SHOULD include as many IDs in the ``next`` field as the remaining +size of the list warrants, within reason. + +These skip lists allow for parallelized lookups of items and also makes it more efficient +to search for specific items. It also mitigates breaking lists missing some items. + +Figure of the skip list in the first list item:: + + n Item0 Item1 Item2 Item3 Item4 Item5 Item6 Item7 Item8 Item9 Item10 + 0 O-----> + 20 O------------> + 40 O--------------------------> + 60 O------------------------------------------------------> + +*n* refers to the byte offset into the ``next`` field. + +list-head +--------- + +The list head item is special in that it can be updated, without changing its +DHT key. This is required to prepend new items to the linked list. To authenticate +that only the original publisher can update the head, the whole linked list head +is signed. In order to avoid a malicious node to overwrite the list head with an old +version, the sequence number ``seq`` must be monotonically increasing for each update, +and a node hosting the list node MUST not downgrade a list head from a higher sequence +number to a lower one, only upgrade. + +The list head's DHT key (which it is announced to) MUST be the SHA1 hash of the name +(``n``) and ``key`` fields concatenated. + +Any node MUST reject any list head which is announced under any other ID. + +messages +-------- + +These are the messages to deal with linked lists. + +The ``id`` field in these messages has the same semantics as the standard DHT messages, +i.e. the node ID of the node sending the message, to maintain the structure of the DHT +network. + +The ``token`` field also has the same semantics as the standard DHT message ``get_peers`` +and ``announce_peer``, when requesting an item and to write an item respectively. + +``nodes`` and ``nodes6`` has the same semantics as in its ``get_peers`` response. + +requesting items +................ + +This message can be used to request both a list head and a list item. When requesting +a list head, the ``n`` (name) field MUST be specified. When requesting a list item the +``n`` field is not required. + +.. parsed-literal:: + + { + "a": + { + "id": *<20 byte ID of sending node>*, + "key": *<64 byte public curve25519 key for this list>*, + "n": ** + "target": ** + }, + "q": "get_item", + "t": **, + "y": "q", + } + +When requesting a list-head the ``target`` MUST always be SHA-1(*feed_name* + *public_key*). +``target`` is the target node ID the item was written to. + +The ``n`` field is the name of the list. If specified, It MUST be UTF-8 encoded string +and it MUST match the name of the feed in the receiving node. + +request item response +..................... + +This is the format of a response of a list head: + +.. parsed-literal:: + + { + "r": + { + "head": + { + "key": *<64 byte public curve25519 key for this list>*, + "next": *<20 bytes item ID>*, + "n": **, + "seq": ** + }, + "sig": **, + "id": *<20 byte id of sending node>*, + "token": **, + "nodes": **, + "nodes6": ** + }, + "t": **, + "y": "r", + } + +This is the format of a response of a list item: + +.. parsed-literal:: + + { + "r": + { + "item": + { + "key": *<64 byte public curve25519 key for this list>*, + "next": *<20 bytes item ID>*, + ... + }, + "sig": **, + "id": *<20 byte id of sending node>*, + "token": **, + "nodes": **, + "nodes6": ** + }, + "t": **, + "y": "r", + } + +A client receiving a ``get_item`` response MUST verify the signature in the ``sig`` +field against the bencoded representation of the ``item`` field, using the ``key`` as +the public key. The ``key`` MUST match the public key of the feed. + +The ``item`` dictionary MAY contain arbitrary keys, and all keys MUST be stored for +items. + +announcing items +................ + +The message format for announcing a list head: + +.. parsed-literal:: + + { + "a": + { + "head": + { + "key": *<64 byte public curve25519 key for this list>*, + "next": *<20 bytes item ID>*, + "n": **, + "seq": ** + }, + "sig": **, + "id": *<20 byte node-id of origin node>*, + "target": **, + "token": ** + }, + "y": "q", + "q": "announce_item", + "t": ** + } + +The message format for announcing a list item: + +.. parsed-literal:: + + { + "a": + { + "item": + { + "key": *<64 byte public curve25519 key for this list>*, + "next": *<20 bytes item ID>*, + ... + }, + "sig": **, + "id": *<20 byte node-id of origin node>*, + "target": **, + "token": ** + }, + "y": "q", + "q": "announce_item", + "t": ** + } + +A storage node MAY reject items and heads whose bencoded representation is +greater than 1024 bytes. + +re-announcing +------------- + +In order to keep feeds alive, subscriber nodes SHOULD help out in announcing +items they have downloaded to the DHT. + +Every subscriber node SHOULD store items in long term storage, across sessions, +in order to keep items alive for as long as possible, with as few sources as possible. + +Subscribers to a feed SHOULD also announce items that they know of, to the feed. +Since a feed may have many subscribers and many items, subscribers should re-announce +items according to the following algorithm. + +.. parsed-literal:: + + 1. pick one random item (*i*) from the local repository (except + items already announced this round) + 2. If all items in the local repository have been announced + 2.1 terminate + 3. look up item *i* in the DHT + 4. If fewer than 8 nodes returned the item + 4.1 announce *i* to the DHT + 4.2 goto 1 + +This ensures a balanced load on the DHT while still keeping items alive + +timeouts +-------- + +Items SHOULD be announced to the DHT every 30 minutes. A storage node MAY time +out an item after 60 minutes of no one announcing it. + +A storing node MAY extend the timeout when it receives a request for it. Since +items are immutable, the data doesn't go stale. Therefore it doesn't matter if +the storing node no longer is in the set of the 8 closest nodes. + +RSS feeds +--------- + +For RSS feeds, following keys are mandatory in the list item's ``item`` dictionary. + +ih + The torrent's info hash + +size + The size (in bytes) of all files the torrent + +n + name of the torrent + +example +....... + +This is an example of an ``announce_item`` message: + +.. parsed-literal:: + + { + "a": + { + "item": + { + "key": "6bc1de5443d1a7c536cdf69433ac4a7163d3c63e2f9c92d + 78f6011cf63dbcd5b638bbc2119cdad0c57e4c61bc69ba5e2c08 + b918c2db8d1848cf514bd9958d307", + "info-hash": "7ea94c240691311dc0916a2a91eb7c3db2c6f3e4", + "size": 24315329, + "n": "my stuff", + "next": "c68f29156404e8e0aas8761ef5236bcagf7f8f2e" + } + "sig": ** + "id": "b46989156404e8e0acdb751ef553b210ef77822e", + "target": "b4692ef0005639e86d7165bf378474107bf3a762" + "token": "23ba" + }, + "y": "q", + "q": "announce_item", + "t": "a421" + } + +Strings are printed in hex for printability, but actual encoding is binary. + +Note that ``target`` is in fact SHA1 hash of the same data the signature ``sig`` +is the signature of, i.e.:: + + d9:info-hash20:7ea94c240691311dc0916a2a91eb7c3db2c6f3e43:key64:6bc1de5443d1 + a7c536cdf69433ac4a7163d3c63e2f9c92d78f6011cf63dbcd5b638bbc2119cdad0c57e4c61 + bc69ba5e2c08b918c2db8d1848cf514bd9958d3071:n8:my stuff4:next20:c68f29156404 + e8e0aas8761ef5236bcagf7f8f2e4:sizei24315329ee + +(note that binary data is printed as hex) + +RSS feed URI scheme +-------------------- + +The proposed URI scheme for DHT feeds is: + +.. parsed-literal:: + + magnet:?xt=btfd:** &dn= ** + +Note that a difference from regular torrent magnet links is the **btfd** +versus **btih** used in regular magnet links to torrents. + +The *feed name* is mandatory since it is used in the request and when +calculating the target ID. + +rationale +--------- + +The reason to use curve25519_ instead of, for instance, RSA is compactness. According to +https://cr.yp.to/, curve25519 is free from patent claims and there are open implementations +in both C and Java. + +.. _curve25519: https://cr.yp.to/ecdh.html + diff --git a/docs/dht_sec.rst b/docs/dht_sec.rst new file mode 100644 index 0000000..67106df --- /dev/null +++ b/docs/dht_sec.rst @@ -0,0 +1,255 @@ +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +BitTorrent DHT security extension +--------------------------------- + +The purpose of this extension is to make it harder to launch a few +specific attacks against the BitTorrent DHT and also to make it harder +to snoop the network. + +Specifically the attack this extension intends to make harder is launching +8 or more DHT nodes which node-IDs selected close to a specific target +info-hash, in order to become the main nodes hosting peers for it. Currently +this is very easy to do and lets the attacker not only see all the traffic +related to this specific info-hash but also block access to it by other +peers. + +The proposed guard against this is to enforce restrictions on which node-ID +a node can choose, based on its external IP address. + +considerations +-------------- + +One straight forward scheme to tie the node ID to an IP would be to hash +the IP and force the node ID to share the prefix of that hash. One main +draw back of this approach is that an entities control over the DHT key +space grows linearly with its control over the IP address space. + +In order to successfully launch an attack, you just need to find 8 IPs +whose hash will be *closest* to the target info-hash. Given the current +size of the DHT, that is quite likely to be possible by anyone in control +of a /8 IP block. + +The size of the DHT is approximately 8.4 million nodes. This is estimated +by observing that a typical routing table typically has about 20 of its +top routing table buckets full. That means the key space is dense enough +to contain 8 nodes for every combination of the 20 top bits of node IDs. + + ``2^20 * 8 = 8388608`` + +By controlling that many IP addresses, an attacker could snoop any info-hash. +By controlling 8 times that many IP addresses, an attacker could actually +take over any info-hash. + +With IPv4, snooping would require a /8 IP block, giving access to 16.7 million +IPs. + +Another problem with hashing the IP is that multiple users behind a NAT are +forced to run their DHT nodes on the same node ID. + +Node ID restriction +------------------- + +In order to avoid the number node IDs controlled to grow linearly by the number +of IPs, as well as allowing more than one node ID per external IP, the node +ID can be restricted at each class level of the IP. + +Another important property of the restriction put on node IDs is that the +distribution of the IDs remain uniform. This is why CRC32C (Castagnoli) was +chosen as the hash function. + +The expression to calculate a valid ID prefix (from an IPv4 address) is:: + + crc32c((ip & 0x030f3fff) | (r << 29)) + +And for an IPv6 address (``ip`` is the high 64 bits of the address):: + + crc32c((ip & 0x0103070f1f3f7fff) | (r << 61)) + +``r`` is a random number in the range [0, 7]. The resulting integer, +representing the masked IP address is supposed to be big-endian before +hashed. The "|" operator means bit-wise OR. + +The details of implementing this is to evaluate the expression, store the +result in a big-endian 64 bit integer and hash those 8 bytes with CRC32C. + +The first (most significant) 21 bits of the node ID used in the DHT MUST +match the first 21 bits of the resulting hash. The last byte of the hash MUST +match the random number (``r``) used to generate the hash. + +.. image:: img/ip_id_v4.png + :class: bw +.. image:: img/ip_id_v6.png + :class: bw + +Example code code for calculating a valid node ID:: + + uint8_t* ip; // our external IPv4 or IPv6 address (network byte order) + int num_octets; // the number of octets to consider in ip (4 or 8) + uint8_t node_id[20]; // resulting node ID + + uint8_t v4_mask[] = { 0x03, 0x0f, 0x3f, 0xff }; + uint8_t v6_mask[] = { 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff }; + uint8_t* mask = num_octets == 4 ? v4_mask : v6_mask; + + for (int i = 0; i < num_octets; ++i) + ip[i] &= mask[i]; + + uint32_t rand = std::rand() & 0xff; + uint8_t r = rand & 0x7; + ip[0] |= r << 5; + + uint32_t crc = 0; + crc = crc32c(crc, ip, num_octets); + + // only take the top 21 bits from crc + node_id[0] = (crc >> 24) & 0xff; + node_id[1] = (crc >> 16) & 0xff; + node_id[2] = ((crc >> 8) & 0xf8) | (std::rand() & 0x7); + for (int i = 3; i < 19; ++i) node_id[i] = std::rand(); + node_id[19] = rand; + +test vectors: + +.. parsed-literal:: + + IP rand example node ID + ============ ===== ========================================== + 124.31.75.21 1 **5fbfbf** f10c5d6a4ec8a88e4c6ab4c28b95eee4 **01** + 21.75.31.124 86 **5a3ce9** c14e7a08645677bbd1cfe7d8f956d532 **56** + 65.23.51.170 22 **a5d432** 20bc8f112a3d426c84764f8c2a1150e6 **16** + 84.124.73.14 65 **1b0321** dd1bb1fe518101ceef99462b947a01ff **41** + 43.213.53.83 90 **e56f6c** bf5b7c4be0237986d5243b87aa6d5130 **5a** + +The bold parts of the node ID are the important parts. The rest are +random numbers. The last bold number of each row has only its most significant +bit pulled from the CRC32C function. The lower 3 bits are random. + +bootstrapping +------------- + +In order to set ones initial node ID, the external IP needs to be known. This +is not a trivial problem. With this extension, *all* DHT responses SHOULD include +a *top-level* field called ``ip``, containing a compact binary representation of +the requester's IP and port. That is big-endian IP followed by 2 bytes of big-endian +port. + +The IP portion is the same byte sequence used to verify the node ID. + +It is important that the ``ip`` field is in the top level dictionary. Nodes that +enforce the node-ID will respond with an error message ("y": "e", "e": { ... }), +whereas a node that supports this extension but without enforcing it will respond +with a normal reply ("y": "r", "r": { ... }). + +A DHT node which receives an ``ip`` result in a request SHOULD consider restarting +its DHT node with a new node ID, taking this IP into account. Since a single node +can not be trusted, there should be some mechanism to determine whether or +not the node has a correct understanding of its external IP or not. This could +be done by voting, or only restart the DHT once at least a certain number of +nodes, from separate searches, tells you your node ID is incorrect. + +rationale +--------- + +The choice of using CRC32C instead of a more traditional cryptographic hash +function is justified primarily of these reasons: + +1. it is a fast function +2. produces well distributed results +3. there is no need for the hash function to be one-way (the input set is + so small that any hash function could be reversed). +4. CRC32C (Castagnoli) is supported in hardware by SSE 4.2, which can + significantly speed up computation + +There are primarily two tests run on SHA-1 and CRC32C to establish the +distribution of results. The first one is the number of bits in the output +set that contain every possible combination of bits. The CRC32C function +has a longer such prefix in its output than SHA-1. This means nodes will still +have well uniformly distributed IDs, even when IP addresses in use are not +uniformly distributed. + +The following graph illustrate a few different hash functions with regard +to this property. + +.. image:: img/complete_bit_prefixes.png + :class: bw + +This test takes into account IP addresses that are not globally routable, i.e. +reserved for local networks, multicast and other things. It also takes into +account that some /8 blocks are not in use by end-users and extremely unlikely +to ever run a DHT node. This makes the results likely to be very similar to +what we would see in the wild. + +These results indicate that CRC32C provides the best uniformity in the results +in terms of bit prefixes where all possibilities are represented, and that +no more than 21 bits should be used from the result. If more than 21 bits +were to be used, there would be certain node IDs that would be impossible to +have, which would make routing sub-optimal. + +The second test is more of a sanity test for the uniform distribution property. +The target space (32 bit integer) is divided up into 1000 buckets. Every valid +IP and ``r`` input is run through the algorithm and the result is put in the +bucket it falls in. The expectation is that each bucket has roughly an equal +number of results falling into it. The following graph shows the resulting +histogram, comparing SHA-1 and CRC32C. + +.. image:: img/hash_distribution.png + :class: bw + +The source code for these tests can be found here_. + +.. _here: https://github.com/arvidn/hash_complete_prefix + +The reason to use CRC32C instead of the CRC32 implemented by zlib is that +Intel CPUs have hardware support for the CRC32C calculations. The input +being exactly 4 bytes is also deliberate, to make it fit in a single +instruction. + +enforcement +----------- + +Once enforced, write tokens from peers whose node ID does not match its external +IP should be considered dropped. In other words, a peer that uses a non-matching +ID MUST never be used to store information on, regardless of which request. In the +original DHT specification only ``announce_peer`` stores data in the network, +but any future extension which stores data in the network SHOULD use the same +restriction. + +Any peer on a local network address is exempt from this node ID verification. +This includes the following IP blocks: + +10.0.0.0/8 + reserved for local networks +172.16.0.0/12 + reserved for local networks +192.168.0.0/16 + reserved for local networks +169.254.0.0/16 + reserved for self-assigned IPs +127.0.0.0/8 + reserved for loopback + + +backwards compatibility and transition +-------------------------------------- + +During some transition period, this restriction should not be enforced, and +peers whose node ID does not match this formula relative to their external IP +should not be blocked. + +Requests from peers whose node ID does not match their external IP should +always be serviced, even after the transition period. The attack this protects +from is storing data on an attacker's node, not servicing an attackers request. + +forward compatibility +--------------------- + +If the total size of the DHT grows to the point where the inherent size limit +in this proposal is too small, the modulus constants can be updated in a new +proposal, and another transition period where both sets of modulus constants +are accepted. + diff --git a/docs/dht_store.rst b/docs/dht_store.rst new file mode 100644 index 0000000..195c4ca --- /dev/null +++ b/docs/dht_store.rst @@ -0,0 +1,480 @@ +============================================ +BitTorrent extension for arbitrary DHT store +============================================ + +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +This is a proposal for an extension to the BitTorrent DHT to allow +storing and retrieving of arbitrary data. + +It supports both storing *immutable* items, where the key is +the SHA-1 hash of the data itself, and *mutable* items, where +the key is the public key of the key pair used to sign the data. + +There are two new proposed messages, ``put`` and ``get``. + +terminology +----------- + +In this document, a *storage node* refers to the node in the DHT to which +an item is being announced and stored on. A *requesting node* refers to +a node which makes look-ups in the DHT to find the storage nodes, to +request items from them, and possibly re-announce those items to keep them +alive. + +messages +-------- + +The proposed new messages ``get`` and ``put`` are similar to the existing +``get_peers`` and ``announce_peer``. + +Responses to ``get`` should always include ``nodes`` and ``nodes6``. Those +fields have the same semantics as in its ``get_peers`` response. It should also +include a write token, ``token``, with the same semantics as int ``get_peers``. +The write token MAY be tied specifically to the key which ``get`` requested. +i.e. the ``token`` can only be used to store values under that one key. It may +also be tied to the node ID and IP address of the requesting node. + +The ``id`` field in these messages has the same semantics as the standard DHT +messages, i.e. the node ID of the node sending the message, to maintain the +structure of the DHT network. + +The ``token`` field also has the same semantics as the standard DHT message +``get_peers`` and ``announce_peer``, when requesting an item and to write an +item respectively. + +The ``k`` field is the 32 byte ed25519 public key, which the signature can be +authenticated with. When looking up a mutable item, the ``target`` field MUST be +the SHA-1 hash of this key concatenated with the ``salt``, if present. + +The distinction between storing mutable and immutable items is the inclusion of +a public key, a sequence number, signature and an optional salt (``k``, ``seq``, +``sig`` and ``salt``). + +``get`` requests for mutable items and immutable items cannot be distinguished +from each other. An implementation can either store mutable and immutable items +in the same hash table internally, or in separate ones and potentially do two +lookups for ``get`` requests. + +The ``v`` field is the *value* to be stored. It is allowed to be any bencoded +type (list, dict, string or integer). When it's being hashed (for verifying its +signature or to calculate its key), its flattened, bencoded, form is used. It is +important to use the verbatim bencoded representation as it appeared in the +message. decoding and then re-encoding bencoded structures is not necessarily an +identity operation. + +Storing nodes MAY reject ``put`` requests where the bencoded form of ``v`` is +longer than 1000 bytes. In other words, it's not safe to assume storing more +than 1000 bytes will succeed. + +immutable items +--------------- + +Immutable items are stored under their SHA-1 hash, and since they cannot be +modified, there is no need to authenticate the origin of them. This makes +immutable items simple. + +A node making a lookup SHOULD verify the data it receives from the network, to +verify that its hash matches the target that was looked up. + +put message +........... + +Request: + +.. parsed-literal:: + + { + "a": + { + "id": *<20 byte id of sending node (string)>*, + "v": ** + }, + "t": **, + "y": "q", + "q": "put" + } + +Response: + +.. parsed-literal:: + + { + "r": { "id": *<20 byte id of sending node (string)>* }, + "t": **, + "y": "r", + } + +get message +........... + +Request: + +.. parsed-literal:: + + { + "a": + { + "id": *<20 byte id of sending node (string)>*, + "target": **, + }, + "t": **, + "y": "q", + "q": "get" + } + +Response: + +.. parsed-literal:: + + { + "r": + { + "id": *<20 byte id of sending node (string)>*, + "token": **, + "v": **, + "nodes": **, + "nodes6": ** + }, + "t": **, + "y": "r", + } + + +mutable items +------------- + +Mutable items can be updated, without changing their DHT keys. To authenticate +that only the original publisher can update an item, it is signed by a private +key generated by the original publisher. The target ID mutable items are stored +under is the SHA-1 hash of the public key (as it appears in the ``put`` +message). + +In order to avoid a malicious node to overwrite the list head with an old +version, the sequence number ``seq`` must be monotonically increasing for each +update, and a node hosting the list node MUST not downgrade a list head from a +higher sequence number to a lower one, only upgrade. The sequence number SHOULD +not exceed ``MAX_INT64``, (i.e. ``0x7fffffffffffffff``. A client MAY reject any +message with a sequence number exceeding this. A client MAY also reject any +message with a negative sequence number. + +The signature is a 64 byte ed25519 signature of the bencoded sequence number +concatenated with the ``v`` key. e.g. something like this:: + + 3:seqi4e1:v12:Hello world! + +If the ``salt`` key is present and non-empty, the salt string must be included +in what's signed. Note that if ``salt`` is specified and an empty string, it is +as if it was not specified and nothing in addition to the sequence number and +the data is signed. The salt string may not be longer than 64 bytes. + +When a salt is included in what is signed, the key ``salt`` with the value of +the key is prepended in its bencoded form. For example, if ``salt`` is "foobar", +the buffer to be signed is:: + + 4:salt6:foobar3:seqi4e1:v12:Hello world! + +put message +........... + +Request: + +.. parsed-literal:: + + { + "a": + { + "cas": **, + "id": *<20 byte id of sending node (string)>*, + "k": **, + "salt": ** + "seq": **, + "sig": **, + "token": **, + "v": ** + }, + "t": **, + "y": "q", + "q": "put" + } + +Storing nodes receiving a ``put`` request where ``seq`` is lower than or equal +to what's already stored on the node, MUST reject the request. If the sequence +number is equal, and the value is also the same, the node SHOULD reset its +timeout counter. + +If the sequence number in the ``put`` message is lower than the sequence number +associated with the currently stored value, the storing node MAY return an error +message with code 302 (see error codes below). + +Note that this request does not contain a target hash. The target hash under +which this blob is stored is implied by the ``k`` argument. The key is the SHA-1 +hash of the key (``k``). + +In order to support a single key being used to store separate items in the DHT, +an optional ``salt`` can be specified in the ``put`` request of mutable items. +If the salt entry is not present, it can be assumed to be an empty string, and +its semantics should be identical as specifying a salt key with an empty string. +The salt can be any binary string (but probably most conveniently a hash of +something). This string is appended to the key, as specified in the ``k`` field, +when calculating the key to store the blob under (i.e. the key ``get`` requests +specify to retrieve this data). + +This lets a single entity, with a single key, publish any number of unrelated +items, with a single key that readers can verify. This is useful if the +publisher doesn't know ahead of time how many different items are to be +published. It can distribute a single public key for users to authenticate the +published blobs. + +Note that the salt is not returned in the response to a ``get`` request. This +is intentional. When issuing a ``get`` request for an item is expected to +know what the salt is (because it is part of what the target ID that is being +looked up is derived from). There is no need to repeat it back for bystanders +to see. + +CAS +... + +CAS is short for *compare and swap*, it has similar semantics as CAS CPU +instructions. It is used to avoid race conditions when multiple nodes are +writing to the same slot in the DHT. + +The ``cas`` field is optional. If present it specifies the sequence number of +the data blob being overwritten by the put. When present, the storing node +MUST compare this number to the current sequence number it has stored under +this key. Only if the ``cas`` matches the stored sequence number is the put +performed. If it mismatches, the store fails and an error is returned. +See errors_ below. + +The ``cas`` field only applies to mutable puts. If there is no current +value, the ``cas`` field SHOULD be ignored. + +When sending a ``put`` request to a node that did not return any data for the +``get``, the ``cas`` field SHOULD NOT be included. + +response +........ + +Response: + +.. parsed-literal:: + + { + "r": { "id": *<20 byte id of sending node (string)>* }, + "t": **, + "y": "r", + } + +errors +...... + +If the store fails for any reason an error message is returned instead of the +message template above, i.e. one where "y" is "e" and "e" is a tuple of +[error-code, message]). Failures include ``cas`` mismatches and the sequence +number is outdated. + +The error message (as specified by BEP5_) looks like this: + +.. _BEP5: https://www.bittorrent.org/beps/bep_0005.html + +.. parsed-literal:: + + { + "e": [ **, ** ], + "t": **, + "y": "e", + } + +In addition to the error codes defined in BEP5_, this specification defines +some additional error codes. + ++------------+-----------------------------+ +| error-code | description | ++============+=============================+ +| 205 | message (``v`` field) | +| | too big. | ++------------+-----------------------------+ +| 206 | invalid signature | ++------------+-----------------------------+ +| 207 | salt (``salt`` field) | +| | too big. | ++------------+-----------------------------+ +| 301 | the CAS hash mismatched, | +| | re-read value and try | +| | again. | ++------------+-----------------------------+ +| 302 | sequence number less than | +| | current. | ++------------+-----------------------------+ + +An implementation MUST emit 301 errors if the cas mismatches. This is a +critical feature in synchronization of multiple agents sharing an immutable +item. + +get message +........... + +Request: + +.. parsed-literal:: + + { + "a": + { + "id": *<20 byte id of sending node (string)>*, + "target:" *<20 byte SHA-1 hash of public key and salt (string)>* + }, + "t": **, + "y": "q", + "q": "get" + } + +Response: + +.. parsed-literal:: + + { + "r": + { + "id": *<20 byte id of sending node (string)>*, + "k": **, + "nodes": **, + "nodes6": **, + "seq": **, + "sig": **, + "token": **, + "v": ** + }, + "t": **, + "y": "r", + } + +signature verification +---------------------- + +In order to make it maximally difficult to attack the bencoding parser, signing +and verification of the value and sequence number should be done as follows: + +1. encode value and sequence number separately +2. concatenate ("4:salt" *length-of-salt* ":" *salt*) "3:seqi" *seq* + "e1:v" *len* ":" and the encoded value. + sequence number 1 of value "Hello World!" would be converted to: + "3:seqi1e1:v12:Hello World!". In this way it is not possible to convince a + node that part of the length is actually part of the sequence number even if + the parser contains certain bugs. Furthermore it is not possible to have a + verification failure if a bencoding serializer alters the order of entries in + the dictionary. The salt is in parenthesis because it is optional. It is only + prepended if a non-empty salt is specified in the ``put`` request. +3. sign or verify the concatenated string + +On the storage node, the signature MUST be verified before accepting the store +command. The data MUST be stored under the SHA-1 hash of the public key (as it +appears in the bencoded dict) and the salt (if present). + +On the requesting nodes, the key they get back from a ``get`` request MUST be +verified to hash to the target ID the lookup was made for, as well as verifying +the signature. If any of these fail, the response SHOULD be considered invalid. + +expiration +---------- + +Without re-announcement, these items MAY expire in 2 hours. In order +to keep items alive, they SHOULD be re-announced once an hour. + +Any node that's interested in keeping a blob in the DHT alive may announce it. +It would simply repeat the signature for a mutable put without having the +private key. + +test vectors +------------ + +test 1 (mutable) +................ + +value:: + + 12:Hello World! + +buffer being signed:: + + 3:seqi1e1:v12:Hello World! + +public key:: + + 77ff84905a91936367c01360803104f92432fcd904a43511876df5cdf3e7e548 + +private key:: + + e06d3183d14159228433ed599221b80bd0a5ce8352e4bdf0262f76786ef1c74d + b7e7a9fea2c0eb269d61e3b38e450a22e754941ac78479d6c54e1faf6037881d + +**target ID**:: + + 4a533d47ec9c7d95b1ad75f576cffc641853b750 + +**signature**:: + + 305ac8aeb6c9c151fa120f120ea2cfb923564e11552d06a5d856091e5e853cff + 1260d3f39e4999684aa92eb73ffd136e6f4f3ecbfda0ce53a1608ecd7ae21f01 + +test 2 (mutable with salt) +.......................... + +value:: + + 12:Hello World! + +salt:: + + foobar + +buffer being signed:: + + 4:salt6:foobar3:seqi1e1:v12:Hello World! + +public key:: + + 77ff84905a91936367c01360803104f92432fcd904a43511876df5cdf3e7e548 + +private key:: + + e06d3183d14159228433ed599221b80bd0a5ce8352e4bdf0262f76786ef1c74d + b7e7a9fea2c0eb269d61e3b38e450a22e754941ac78479d6c54e1faf6037881d + +**target ID**:: + + 411eba73b6f087ca51a3795d9c8c938d365e32c1 + +**signature**:: + + 6834284b6b24c3204eb2fea824d82f88883a3d95e8b4a21b8c0ded553d17d17d + df9a8a7104b1258f30bed3787e6cb896fca78c58f8e03b5f18f14951a87d9a08 + +test 3 (immutable) +.................. + +value:: + + 12:Hello World! + +**target ID**:: + + e5f96f6f38320f0f33959cb4d3d656452117aadb + +resources +--------- + +Libraries that implement ed25519 DSA: + +* NaCl_ +* libsodium_ +* `nightcracker's ed25519`_ + +.. _NaCl: https://nacl.cr.yp.to/ +.. _libsodium: https://github.com/jedisct1/libsodium +.. _`nightcracker's ed25519`: https://github.com/nightcracker/ed25519 + diff --git a/docs/examples.rst b/docs/examples.rst new file mode 100644 index 0000000..5baf4bf --- /dev/null +++ b/docs/examples.rst @@ -0,0 +1,46 @@ +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +examples +======== + +Except for the example programs in this manual, there's also a bigger example +of a (little bit) more complete client, ``client_test``. There are separate +instructions for how to use it here__ if you'd like to try it. + +__ client_test.html + +simple client +------------- + +This is a simple client. It doesn't have much output to keep it simple: + +.. include:: ../examples/simple_client.cpp + :code: c++ + :tab-width: 2 + :start-after: */ + +make_torrent +------------ + +Shows how to create a torrent from a directory tree: + +.. include:: ../examples/make_torrent.cpp + :code: c++ + :tab-width: 2 + :start-after: */ + +dump_torrent +------------ + +This is an example of a program that will take a torrent-file as a parameter and +print information about it to std out: + +.. include:: ../examples/dump_torrent.cpp + :code: c++ + :tab-width: 2 + :start-after: */ + diff --git a/docs/extension_protocol.rst b/docs/extension_protocol.rst new file mode 100644 index 0000000..cd6a637 --- /dev/null +++ b/docs/extension_protocol.rst @@ -0,0 +1,324 @@ +:Author: Arvid Norberg, arvid@libtorrent.org + Ludvig Strigeus, ludde@utorrent.com + +extension protocol for bittorrent +================================= + +The intention of this protocol is to provide a simple and thin transport +for extensions to the bittorrent protocol. Supporting this protocol makes +it easy to add new extensions without interfering with the standard +bittorrent protocol or clients that don't support this extension or the +one you want to add. + +To advertise to other clients that you support, one bit from the reserved +bytes is used. + +The bit selected for the extension protocol is bit 20 from the right (counting +starts at 0). So (reserved_byte[5] & 0x10) is the expression to use for checking +if the client supports extended messaging. + +Once support for the protocol is established, the client is supposed to +support 1 new message: + ++------------------------+----+ +|name | id | ++========================+====+ +|``extended`` | 20 | ++------------------------+----+ + +This message is sent as any other bittorrent message, with a 4 byte length +prefix and a single byte identifying the message (the single byte being 20 +in this case). At the start of the payload of the message, is a single byte +message identifier. This identifier can refer to different extension messages +and only one ID is specified, 0. If the ID is 0, the message is a handshake +message which is described below. The layout of a general ``extended`` message +follows (including the message headers used by the bittorrent protocol): + ++----------+---------------------------------------------------------+ +| size | description | ++==========+=========================================================+ +| uint32_t | length prefix. Specifies the number of bytes for the | +| | entire message. (big-endian) | ++----------+---------------------------------------------------------+ +| uint8_t | bittorrent message ID, = 20 | ++----------+---------------------------------------------------------+ +| uint8_t | extended message ID. 0 = handshake, >0 = extended | +| | message as specified by the handshake. | ++----------+---------------------------------------------------------+ + + +handshake message +----------------- + +The payload of the handshake message is a bencoded dictionary. All items +in the dictionary are optional. Any unknown names should be ignored +by the client. All parts of the dictionary are case sensitive. +This is the defined item in the dictionary: + ++-------+-----------------------------------------------------------+ +| name | description | ++=======+===========================================================+ +| m | Dictionary of supported extension messages which maps | +| | names of extensions to an extended message ID for each | +| | extension message. The only requirement on these IDs | +| | is that no extension message share the same one. Setting | +| | an extension number to zero means that the extension is | +| | not supported/disabled. The client should ignore any | +| | extension names it doesn't recognize. | +| | | +| | The extension message IDs are the IDs used to send the | +| | extension messages to the peer sending this handshake. | +| | i.e. The IDs are local to this particular peer. | ++-------+-----------------------------------------------------------+ + + +Here are some other items that an implementation may choose to support: + ++--------+-----------------------------------------------------------+ +| name | description | ++========+===========================================================+ +| p | Local TCP listen port. Allows each side to learn about | +| | the TCP port number of the other side. Note that there is | +| | no need for the receiving side of the connection to send | +| | this extension message, since its port number is already | +| | known. | ++--------+-----------------------------------------------------------+ +| v | Client name and version (as a utf-8 string). | +| | This is a much more reliable way of identifying the | +| | client than relying on the peer id encoding. | ++--------+-----------------------------------------------------------+ +| yourip | A string containing the compact representation of the ip | +| | address this peer sees you as. i.e. this is the | +| | receiver's external ip address (no port is included). | +| | This may be either an IPv4 (4 bytes) or an IPv6 | +| | (16 bytes) address. | ++--------+-----------------------------------------------------------+ +| ipv6 | If this peer has an IPv6 interface, this is the compact | +| | representation of that address (16 bytes). The client may | +| | prefer to connect back via the IPv6 address. | ++--------+-----------------------------------------------------------+ +| ipv4 | If this peer has an IPv4 interface, this is the compact | +| | representation of that address (4 bytes). The client may | +| | prefer to connect back via this interface. | ++--------+-----------------------------------------------------------+ +| reqq | An integer, the number of outstanding request messages | +| | this client supports without dropping any. The default in | +| | in libtorrent is 250. | ++--------+-----------------------------------------------------------+ + +The handshake dictionary could also include extended handshake +information, such as support for encrypted headers or anything +imaginable. + +An example of what the payload of a handshake message could look like: + ++------------------------------------------------------+ +| Dictionary | ++===================+==================================+ +| ``m`` | +--------------------------+ | +| | | Dictionary | | +| | +======================+===+ | +| | | ``LT_metadata`` | 1 | | +| | +----------------------+---+ | +| | | ``ut_pex`` | 2 | | +| | +----------------------+---+ | +| | | ++-------------------+----------------------------------+ +| ``p`` | 6881 | ++-------------------+----------------------------------+ +| ``v`` | "uTorrent 1.2" | ++-------------------+----------------------------------+ + +and in the encoded form: + +``d1:md11:LT_metadatai1e6:ut_pexi2ee1:pi6881e1:v12:uTorrent 1.2e`` + +To make sure the extension names do not collide by mistake, they should be +prefixed with the two (or one) character code that is used to identify the +client that introduced the extension. This applies for both the names of +extension messages, and for any additional information put inside the +top-level dictionary. All one and two byte identifiers are invalid to use +unless defined by this specification. + +This message should be sent immediately after the standard bittorrent handshake +to any peer that supports this extension protocol. It is valid to send the +handshake message more than once during the lifetime of a connection, +the sending client should not be disconnected. An implementation may choose +to ignore the subsequent handshake messages (or parts of them). + +Subsequent handshake messages can be used to enable/disable extensions +without restarting the connection. If a peer supports changing extensions +at run time, it should note that the ``m`` dictionary is additive. +It's enough that it contains the actual *CHANGES* to the extension list. +To disable the support for ``LT_metadata`` at run-time, without affecting +any other extensions, this message should be sent: +``d11:LT_metadatai0ee``. +As specified above, the value 0 is used to turn off an extension. + +The extension IDs must be stored for every peer, because every peer may have +different IDs for the same extension. + +This specification, deliberately, does not specify any extensions such as +peer-exchange or metadata exchange. This protocol is merely a transport +for the actual extensions to the bittorrent protocol and the extensions +named in the example above (such as ``p``) are just examples of possible +extensions. + +rationale +--------- + +The reason why the extension messages' IDs would be defined in the handshake +is to avoid having a global registry of message IDs. Instead the names of the +extension messages requires unique names, which is much easier to do without +a global registry. The convention is to use a two letter prefix on the +extension message names, the prefix would identify the client first +implementing the extension message. e.g. ``LT_metadata`` is implemented by +libtorrent, and hence it has the ``LT`` prefix. + +If the client supporting the extensions can decide which numbers the messages +it receives will have, it means they are constants within that client. i.e. +they can be used in ``switch`` statements. It's easy for the other end to +store an array with the ID's we expect for each message and use that for +lookups each time it sends an extension message. + +The reason for having a dictionary instead of having an array (using +implicitly assigned index numbers to the extensions) is that if a client +want to disable some extensions, the ID numbers would change, and it wouldn't +be able to use constants (and hence, not use them in a ``switch``). If the +messages IDs would map directly to bittorrent message IDs, It would also make +it possible to map extensions in the handshake to existing extensions with +fixed message IDs. + +The reasoning behind having a single byte as extended message identifier is +to follow the bittorrent spec. with its single byte message identifiers. +It is also considered to be enough. It won't limit the total number of +extensions, only the number of extensions used simultaneously. + +The reason for using single byte identifiers for the standardized handshake +identifiers is 1) The mainline DHT uses single byte identifiers. 2) Saves +bandwidth. The only advantage of longer messages is that it makes the +protocol more readable for a human, but the BT protocol wasn't designed to +be a human readable protocol, so why bother. + + +extensions +========== + +These extensions all operates within the `extension protocol`_. The name of the +extension is the name used in the extension-list packets, and the payload is +the data in the extended message (not counting the length-prefix, message-id +nor extension-id). + +.. _`extension protocol`: extension_protocol.html + +Note that since this protocol relies on one of the reserved bits in the +handshake, it may be incompatible with future versions of the mainline +bittorrent client. + +These are the extensions that are currently implemented. + +metadata from peers +------------------- + +Extension name: "LT_metadata" + +.. note:: + This extension is deprecated in favor of the more widely supported + ``ut_metadata`` extension, see `BEP 9`_. + +The point with this extension is that you don't have to distribute the +metadata (.torrent-file) separately. The metadata can be distributed +through the bittorrent swarm. The only thing you need to download such +a torrent is the tracker url and the info-hash of the torrent. + +It works by assuming that the initial seeder has the metadata and that the +metadata will propagate through the network as more peers join. + +There are three kinds of messages in the metadata extension. These packets are +put as payload to the extension message. The three packets are: + + * request metadata + * metadata + * don't have metadata + +request metadata: + ++-----------+---------------+----------------------------------------+ +| size | name | description | ++===========+===============+========================================+ +| uint8_t | msg_type | Determines the kind of message this is | +| | | 0 means *request metadata* | ++-----------+---------------+----------------------------------------+ +| uint8_t | start | The start of the metadata block that | +| | | is requested. It is given in 256ths | +| | | of the total size of the metadata, | +| | | since the requesting client don't know | +| | | the size of the metadata. | ++-----------+---------------+----------------------------------------+ +| uint8_t | size | The size of the metadata block that is | +| | | requested. This is also given in | +| | | 256ths of the total size of the | +| | | metadata. The size is given as size-1. | +| | | That means that if this field is set | +| | | 0, the request wants one 256:th of the | +| | | metadata. | ++-----------+---------------+----------------------------------------+ + +metadata: + ++-----------+---------------+----------------------------------------+ +| size | name | description | ++===========+===============+========================================+ +| uint8_t | msg_type | 1 means *metadata* | ++-----------+---------------+----------------------------------------+ +| int32_t | total_size | The total size of the metadata, given | +| | | in number of bytes. | ++-----------+---------------+----------------------------------------+ +| int32_t | offset | The offset of where the metadata block | +| | | in this message belongs in the final | +| | | metadata. This is given in bytes. | ++-----------+---------------+----------------------------------------+ +| uint8_t[] | metadata | The actual metadata block. The size of | +| | | this part is given implicit by the | +| | | length prefix in the bittorrent | +| | | protocol packet. | ++-----------+---------------+----------------------------------------+ + +Don't have metadata: + ++-----------+---------------+----------------------------------------+ +| size | name | description | ++===========+===============+========================================+ +| uint8_t | msg_type | 2 means "I don't have metadata". | +| | | This message is sent as a reply to a | +| | | metadata request if the the client | +| | | doesn't have any metadata. | ++-----------+---------------+----------------------------------------+ + +.. _`BEP 9`: https://www.bittorrent.org/beps/bep_0009.html + +dont_have +--------- + +Extension name: "lt_donthave" + +The ``dont_have`` extension message is used to tell peers that the client no +longer has a specific piece. The extension message should be advertised in the +``m`` dictionary as ``lt_donthave``. The message format mimics the regular +``HAVE`` bittorrent message. + +Just like all extension messages, the first 2 bytes in the message itself are 20 +(the bittorrent extension message) and the message ID assigned to this +extension in the ``m`` dictionary in the handshake. + ++-----------+---------------+----------------------------------------+ +| size | name | description | ++===========+===============+========================================+ +| uint32_t | piece | index of the piece the peer no longer | +| | | has. | ++-----------+---------------+----------------------------------------+ + +The length of this message (including the extension message prefix) is 6 bytes, +i.e. one byte longer than the normal ``HAVE`` message, because of the extension +message wrapping. + diff --git a/docs/features.rst b/docs/features.rst new file mode 100644 index 0000000..f719244 --- /dev/null +++ b/docs/features.rst @@ -0,0 +1,323 @@ +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +introduction +============ + +libtorrent is a feature complete C++ bittorrent implementation focusing +on efficiency and scalability. It runs on embedded devices as well as +desktops. It boasts a well documented library interface that is easy to +use. It comes with a simple bittorrent client demonstrating the use of +the library. + +BitTorrent v2 is supported as of libtorrent 2.0. This replaces the previous +merkle hash tree extension. + +features +======== + +libtorrent is an ongoing project under active development. Its +current state supports and includes the following features: + +BitTorrent v2 +------------- + +Starting with version 2.0, libtorrent supports BitTorrent V2 (as specified in +`BEP 52`_). BitTorrent V2 introduces a new format for .torrent files, which generally +has a smaller info-dict than the original format. The .torrent files still contain +piece hashes by default, but they can also be downloaded from peers. + +1. Files are organized in a directory structure, instead of listing full paths. + Torrents that have a lot of files in deep directory structures will use a lot + less space to represent that structure in a v2 torrent. + +2. Piece hashes are organized in a merkle hash trees per file, and only the + roots of the trees are included in the .torrent file. The actual hashes are + delivered by peers. + +The hash tree allows validating payload received from a peer immediately, down +to 16 kiB blocks. In the original bittorrent protocol a whole piece would have +to be downloaded before it could be validated against the hashes. + +The fact that each file has its own hash tree, and that its leaves are defined +to be 16 kiB, means that files with identical content will always have the same +merkle root. This enables finding matches of the same file across different +torrents. + +The new format for torrent files is compatible with the original torrent file +format, which enables *hybrid* torrents. Such torrents that can be used both as +V1 and V2 and will have two swarms, one with V1 and V2 clients and one with only +V2 clients. + +Another major feature of the BitTorrent V2 protocol is that the SHA-1 hash +function has been replaced by SHA-256. + +extensions +---------- + +* plugin interface for implementing custom bittorrent extensions + without having to modify libtorrent +* supports trackerless torrents (using the Mainline kademlia DHT protocol) with + some `DHT extensions`_. `BEP 5`_. +* supports the bittorrent `extension protocol`_. See extensions_. `BEP 10`_. +* supports the uTorrent metadata transfer protocol `BEP 9`_ (i.e. magnet links). +* supports the uTorrent peer exchange protocol (PEX). +* supports local peer discovery (multicast for peers on the same local network) +* multi-tracker extension support (supports both strict `BEP 12`_ and the + uTorrent interpretation). +* tracker scrapes +* supports lt_trackers extension, to exchange trackers between peers +* `HTTP seeding`_, as specified in `BEP 17`_ and `BEP 19`_. +* supports the UDP-tracker protocol. (`BEP 15`_). +* supports the ``no_peer_id=1`` extension that will ease the load off trackers. +* supports the ``compact=1`` tracker parameter. +* super seeding/initial seeding (`BEP 16`_). +* private torrents (`BEP 27`_). +* upload-only extension (`BEP 21`_). +* support for IPv6, including `BEP 7`_ and `BEP 24`_. +* share-mode. This is a special mode torrents can be put in to optimize share + ratio rather than downloading the torrent. +* supports the Magnet URI extension - Select specific file indices for + download. `BEP 53`_. + +.. _article: utp.html +.. _extensions: manual-ref.html#extensions +.. _`http seeding`: manual-ref.html#http-seeding + +disk management +--------------- + +* can use multiple disk I/O threads to not have the disk block network or + client interaction. +* supports verifying the SHA-1 hash of pieces in multiple threads, to take + advantage of multi core machines. +* supports files > 2 gigabytes. +* fast resume support, a way to avoid the costly piece check at the + start of a resumed torrent. Saves the storage state, piece_picker state + as well as all local peers in a fast-resume file. +* queues torrents for file check, instead of checking all of them in parallel. + resumes. This means it can resume a torrent downloaded by any client. +* seed mode, where the files on disk are assumed to be complete, and each + piece's hash is verified the first time it is requested. + +network +------- + +* a high quality uTP implementation (`BEP 29`_). A transport protocol with + delay based congestion control. See separate article_. +* adjusts the length of the request queue depending on download rate. +* serves multiple torrents on a single port and in a single thread +* piece picking on block-level (as opposed to piece-level). + This means it can download parts of the same piece from different peers. + It will also prefer to download whole pieces from single peers if the + download speed is high enough from that particular peer. +* supports http proxies and basic proxy authentication +* supports gzip tracker-responses +* can limit the upload and download bandwidth usage and the maximum number of + unchoked peers +* possibility to limit the number of connections. +* delays have messages if there's no other outgoing traffic to the peer, and + doesn't send have messages to peers that already has the piece. This saves + bandwidth. +* selective downloading. The ability to select which parts of a torrent you + want to download. +* ip filter to disallow ip addresses and ip ranges from connecting and + being connected. +* NAT-PMP, PCP and UPnP support (automatic port mapping on routers that supports it) +* implements automatic upload slots, to optimize download rate without spreading + upload capacity too thin. The number of upload slots is adjusted based on the + peers' download capacity to work even for connections that are orders of + magnitude faster than others. + + +.. _`DHT extensions`: dht_extensions.html +.. _`BEP 5`: https://www.bittorrent.org/beps/bep_0005.html +.. _`BEP 7`: https://www.bittorrent.org/beps/bep_0007.html +.. _`BEP 9`: https://www.bittorrent.org/beps/bep_0009.html +.. _`BEP 10`: https://www.bittorrent.org/beps/bep_0010.html +.. _`BEP 12`: https://www.bittorrent.org/beps/bep_0012.html +.. _`BEP 15`: https://www.bittorrent.org/beps/bep_0015.html +.. _`BEP 16`: https://www.bittorrent.org/beps/bep_0016.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _`BEP 21`: https://www.bittorrent.org/beps/bep_0021.html +.. _`BEP 24`: https://www.bittorrent.org/beps/bep_0024.html +.. _`BEP 27`: https://www.bittorrent.org/beps/bep_0027.html +.. _`BEP 29`: https://www.bittorrent.org/beps/bep_0029.html +.. _`BEP 52`: https://www.bittorrent.org/beps/bep_0052.html +.. _`BEP 53`: https://www.bittorrent.org/beps/bep_0053.html +.. _`extension protocol`: extension_protocol.html + +highlighted features +==================== + +disk I/O +-------- + +All disk I/O in libtorrent is done asynchronously to the network thread, by the +disk io threads. Files are mapped into memory and the kernel's page cache is +relied on for caching disk blocks. This has the advantage that the disk cache +size adapts to global system load and memory pressure, maximizing the cache +without bogging down the whole system. Since memory mapped I/O is inherently +synchronous, files can be accessed from multiple disk I/O threads. + +Similarly, for write requests, blocks are queued in a store-buffer while waiting +to be flushed to disk. Read requests that happen before a block has been +flushed, will short circuit by picking the block from the store buffer. + +Memory mapped files are available on Windows and posix 64 bit systems. When +building on other, simpler platforms, or 32 bits, a simple portable and +single-threaded disk I/O back-end is available, using `fopen()` and `fclose()` +family of functions. + +network buffers +--------------- + +On CPUs with small L2 caches, copying memory can be expensive operations. It is important +to keep copying to a minimum on such machines. This mostly applies to embedded systems. + +In order to minimize the number of times received data is copied, the receive buffer +for payload data is received directly into a page aligned disk buffer. If the connection +is encrypted, the buffer is decrypted in-place. The buffer is then moved into the disk +cache without being copied. Once all the blocks for a piece have been received, or the +cache needs to be flushed, all the blocks are passed directly to ``writev()`` to flush +them in a single system call. This means a single copy into user space memory, and a single +copy back into kernel memory, as illustrated by this figure: + +.. image:: img/write_disk_buffers.png + :width: 100% + :class: bw + +When seeding and uploading in general, unnecessary copying is avoided by caching blocks +in aligned buffers, that are copied once into the peer's send buffer. The peer's send buffer +is not guaranteed to be aligned, even though it is most of the time. The send buffer is +then encrypted with the peer specific key and chained onto the ``iovec`` for sending. +This means there is one user space copy in order to allow unaligned peer requests and +peer-specific encryption. This is illustrated by the following figure: + +.. image:: img/read_disk_buffers.png + :width: 100% + :class: bw + + +piece picker +------------ + +The piece picker is a central component in a bittorrent implementation. The piece picker +in libtorrent is optimized for quickly finding the rarest pieces. It keeps a list of all +available pieces sorted by rarity, and pieces with the same rarity, shuffled. The rarest +first mode is the dominant piece picker mode. Other modes are supported as well, and +used by peers in specific situations. + +The piece picker allows to combine the availability of a piece with a priority. Together +they determine the sort order of the piece list. Pieces with priority 0 will never be +picked, which is used for the selective download feature. + +In order to have as few partially finished pieces as possible, peers have an affinity +towards picking blocks from the same pieces as other peers in the same speed category. +The speed category is a coarse categorization of peers based on their download rate. This +makes slow peers pick blocks from the same piece, and fast peers pick from the same piece, +and hence decreasing the likelihood of slow peers blocking the completion of pieces. + +The piece picker can also be set to download pieces in sequential order. + +share mode +---------- + +The share mode feature in libtorrent is intended for users who are only interested in +helping out swarms, not downloading the torrents. + +It works by predicting the demand for pieces, and only download pieces if there is enough +demand. New pieces will only be downloaded once the share ratio has hit a certain target. + +This feature is especially useful when combined with RSS, so that a client can be set up +to provide additional bandwidth to an entire feed. + +customizable file I/O +--------------------- + +.. image:: img/storage.png + :align: right + :class: bw + +libtorrent's disk I/O implementation is customizable. That means a special +purpose bittorrent client can replace the default way to store files on disk. + +When implementing a bittorrent cache, it doesn't matter how the data is stored on disk, as +long as it can be retrieved and seeded. In that case a new disk I/O class can be implemented +(inheriting from the disk_interface) that avoids the unnecessary step of mapping +pieces to files and offsets. The storage can ignore the file boundaries and just store the +entire torrent in a single file (which will end up being all the files concatenated). The main +advantage of this, other than a slight CPU performance gain, is that all file operations would +be page (and sector) aligned. This enables efficient unbuffered I/O, and can potentially +lead to more efficient read caching (using the built in disk cache rather than relying on the +operating system's disk cache). + +easy to use API +--------------- + +One of the design goals of the libtorrent API is to make common operations simple, but still +have it possible to do complicated and advanced operations. This is best illustrated by example +code to implement a simple bittorrent client: + +.. code:: c++ + + #include + #include "libtorrent/session.hpp" + + // usage a.out [torrent-file] + int main(int argc, char* argv[]) try + { + lt::session s; + lt::add_torrent_params p; + p.save_path = "./"; + p.ti = std::make_shared(argv[1]); + lt::torrent_handle h = s.add_torrent(p); + + // wait for the user to end + char a; + std::cin.unsetf(std::ios_base::skipws); + std::cin >> a; + return 0; + } + catch (std::exception const& e) + { + std::cerr << ec.what() << std::endl; + return 1; + } + +This client doesn't give the user any status information or progress about the +torrent, but it is fully functional. + +libtorrent also comes with `python bindings`_. + +.. _`python bindings`: python_binding.html + + +portability +=========== + +libtorrent runs on most major operating systems including: + +* Windows +* macOS +* Linux +* BSD +* Solaris + +It uses Boost.Asio, Boost.Optional, Boost.System, Boost.Multiprecision, +Boost.Pool, Boost.Python (for bindings), Boost.CRC and various +other boost libraries. At least version 1.70 of boost is required. + +Since libtorrent uses Boost.Asio it will take full advantage of high performance +network APIs on the most popular platforms. I/O completion ports on windows, +epoll on Linux and kqueue on macOS and BSD. + +libtorrent requires a C++11 compiler and does not build with the following compilers: + +* GCC older than 5.4 +* Visual Studio older than Visual Studio 15 2017 (aka msvc-14.1) + diff --git a/docs/filter-rst.py b/docs/filter-rst.py new file mode 100644 index 0000000..997433c --- /dev/null +++ b/docs/filter-rst.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 +from __future__ import print_function + +import sys + + +def indent(line): + if line == '': + return None + end = 0 + for c in line: + end += 1 + if " \t" not in c: + return line[:end] + return line + + +start_block = False +filter_indent = None + +for line in open(sys.argv[1]): + + if line == '\n': + continue + + if filter_indent: + if line.startswith(filter_indent): + continue + else: + filter_indent = None + + if line.strip().startswith('.. '): + start_block = True + continue + + if line.endswith('::\n'): + start_block = True + continue + + if start_block: + filter_indent = indent(line) + start_block = False + continue + + sys.stdout.write(line) diff --git a/docs/fuzzing.rst b/docs/fuzzing.rst new file mode 100644 index 0000000..fa760bb --- /dev/null +++ b/docs/fuzzing.rst @@ -0,0 +1,99 @@ +================== +fuzzing libtorrent +================== + +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +Libtorrent comes with a set of fuzzers. They are not included in the distribution +tar ball, instead download the `repository snapshot`_ or clone the repository_. + +The fuzzers can be found in the `fuzzers` subdirectory and come with a `Jamfile` +to build them, and a `run.sh` bash script to run them. + +.. _`repository snapshot`: https://github.com/arvidn/libtorrent/releases +.. _repository: https://github.com/arvidn/libtorrent + +building +-------- + +The fuzzers use clang's libFuzzer, which means they can only be built with clang. +Clang must be configured in your `user-config.jam`, for example:: + + using clang : 7 : clang++-7 ; + +When building, you most likely want to stage the resulting binaries into a +well known location. Invoke `b2` like this:: + + b2 clang stage + +This will build and stage all fuzzers into the `fuzzers/fuzzers` directory. + +corpus +------ + +Fuzzers work best if they have a relevant seed corpus of example inputs. You +can either generate one using `fuzzers/tools/generate_initial_corpus.py` or download +the `corpus.zip` from the github `releases page`_. + +To run the script to generate initial corpus, run it with `fuzzers` as the +current working directory, like this:: + + python tools/generate_initial_corpus.py + +The corpus should be placed in the `fuzzers` directory, which should also be the +current working directory when invoking the fuzzer binaries. + +.. _`releases page`: https://github.com/arvidn/libtorrent/releases + +running fuzzers +--------------- + +The `run.sh` script will run all fuzzers in parallel for 48 hours. It can easily +be tweaked and mostly serve as an example of how to invoke them. + +large and small fuzzers +----------------------- + +Since APIs can have different complexity, fuzz targets will also explore +code of varying complexity. Some fuzzers cover a very small amount of code +(e.g. `parse_int`) where other fuzz targets cover very large amount of code and +can potentially go very deep into call stacks (e.g. `torrent_info`). + +Small fuzz targets can fairly quickly exhaust all possible code paths and have +quite limited utility after that, other than as regression tests. When putting +a lot of CPU into long running fuzzing, it is better spent on large fuzz targets. + +For this reason, there's another alias in the `Jamfile` to only build and stage +large fuzz targets. Call `b2` like this:: + + b2 clang stage-large + +fast+slow +--------- + +When building an initial corpus, it can be useful to quickly build a corpus with +a large code coverage. To speed up this process, you can build the fuzzers +without sanitizers, asserts and invariant checks. This won't find as many errors, +but build a good corpus which can then be run against a fully instrumented +fuzzer. + +To build the fuzzers in this "fast" mode, there's a build variant `build_coverage`. +Invoke `b2` like this:: + + b2 clang stage build_coverage + +For more details on "fast + slow" see `Paul Dreik's talk`_. + +.. _`Paul Dreik's talk`: https://youtu.be/e_Oc9SkCo5s?t=1679 + +sharing corpora +--------------- + +Before sharing your fuzz corpus, it should be minimized. There is a script +called `minimize.sh` which moves `corpus` to `prev-corpus` and copies over +a minimized set of inputs to a new `corpus` directory. + diff --git a/docs/gen_reference_doc.py b/docs/gen_reference_doc.py new file mode 100644 index 0000000..c9faf0f --- /dev/null +++ b/docs/gen_reference_doc.py @@ -0,0 +1,1557 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 +from __future__ import print_function + +import urllib.parse +import glob +import os +import sys + +verbose = '--verbose' in sys.argv +dump = '--dump' in sys.argv +internal = '--internal' in sys.argv +plain_output = '--plain-output' in sys.argv +single_page_output = '--single-page' in sys.argv +if plain_output: + plain_file = open('plain_text_out.txt', 'w+') +in_code = None + +paths = ['include/libtorrent/*.hpp', 'include/libtorrent/kademlia/*.hpp', 'include/libtorrent/extensions/*.hpp'] + +if internal: + paths.append('include/libtorrent/aux_/*.hpp') + +files = [] + +for p in paths: + files.extend(glob.glob(os.path.join('..', p))) + +functions = [] +classes = [] +enums = [] +constants = {} + +# maps filename to overview description +overviews = {} + +# maps names -> URL +symbols = {} + +# some files that need pre-processing to turn symbols into +# links into the reference documentation +preprocess_rst = \ + { + 'manual.rst': 'manual-ref.rst', + 'tuning.rst': 'tuning-ref.rst', + 'tutorial.rst': 'tutorial-ref.rst', + 'features.rst': 'features-ref.rst', + 'upgrade_to_1.2.rst': 'upgrade_to_1.2-ref.rst', + 'upgrade_to_2.0.rst': 'upgrade_to_2.0-ref.rst', + 'settings.rst': 'settings-ref.rst' + } + +# some pre-defined sections from the main manual +symbols = \ + { + "queuing_": "manual-ref.html#queuing", + "fast-resume_": "manual-ref.html#fast-resume", + "storage-allocation_": "manual-ref.html#storage-allocation", + "alerts_": "manual-ref.html#alerts", + "upnp-and-nat-pmp_": "manual-ref.html#upnp-and-nat-pmp", + "http-seeding_": "manual-ref.html#http-seeding", + "metadata-from-peers_": "manual-ref.html#metadata-from-peers", + "magnet-links_": "manual-ref.html#magnet-links", + "ssl-torrents_": "manual-ref.html#ssl-torrents", + "dynamic-loading-of-torrent-files_": "manual-ref.html#dynamic-loading-of-torrent-files", + "session-statistics_": "manual-ref.html#session-statistics", + "peer-classes_": "manual-ref.html#peer-classes", + "BitTorrent-v2-torrents_": "manual-ref.html#bittorrent-v2-torrents", + } + +# parse out names of settings, and add them to the symbols list, to get cross +# references working +with open('../src/settings_pack.cpp') as f: + for line in f: + line = line.strip() + if not line.startswith('SET('): + continue + + name = line.split('(')[1].split(',')[0] + symbols['settings_pack::' + name] = 'reference-Settings.html#' + name + +static_links = \ + { + ".. _`BEP 3`: https://www.bittorrent.org/beps/bep_0003.html", + ".. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html", + ".. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html", + ".. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html", + ".. _`BEP 42`: https://www.bittorrent.org/beps/bep_0042.html", + ".. _`BEP 52`: https://www.bittorrent.org/beps/bep_0052.html", + ".. _`rate based choking`: manual-ref.html#rate-based-choking", + ".. _extensions: manual-ref.html#extensions", + } + +anon_index = 0 + +category_mapping = { + 'ed25519.hpp': 'ed25519', + 'session.hpp': 'Session', + 'session_handle.hpp': 'Session', + 'torrent_handle.hpp': 'Torrent Handle', + 'torrent_info.hpp': 'Torrent Info', + 'announce_entry.hpp': 'Trackers', + 'peer_class_type_filter.hpp': 'PeerClass', + 'peer_class.hpp': 'PeerClass', + 'torrent_status.hpp': 'Torrent Status', + 'session_stats.hpp': 'Stats', + 'performance_counters.hpp': 'Stats', + 'read_resume_data.hpp': 'Resume Data', + 'write_resume_data.hpp': 'Resume Data', + 'add_torrent_params.hpp': 'Add Torrent', + 'client_data.hpp': 'Add Torrent', + 'session_status.hpp': 'Session', + 'session_params.hpp': 'Session', + 'error_code.hpp': 'Error Codes', + 'storage_defs.hpp': 'Storage', + 'file_storage.hpp': 'Storage', + 'disk_interface.hpp': 'Custom Storage', + 'disk_observer.hpp': 'Custom Storage', + 'mmap_disk_io.hpp': 'Storage', + 'disabled_disk_io.hpp': 'Storage', + 'posix_disk_io.hpp': 'Storage', + 'extensions.hpp': 'Plugins', + 'ut_metadata.hpp': 'Plugins', + 'ut_pex.hpp': 'Plugins', + 'ut_trackers.hpp': 'Plugins', + 'smart_ban.hpp': 'Plugins', + 'peer_connection_handle.hpp': 'Plugins', + 'create_torrent.hpp': 'Create Torrents', + 'alert.hpp': 'Alerts', + 'alert_types.hpp': 'Alerts', + 'bencode.hpp': 'Bencoding', + 'bdecode.hpp': 'Bdecoding', + 'entry.hpp': 'Bencoding', + 'time.hpp': 'Time', + 'escape_string.hpp': 'Utility', + 'enum_net.hpp': 'Network', + 'socket.hpp': 'Network', + 'address.hpp': 'Network', + 'socket_io.hpp': 'Network', + 'bitfield.hpp': 'Utility', + 'sha1_hash.hpp': 'Utility', + 'hasher.hpp': 'Utility', + 'identify_client.hpp': 'Utility', + 'ip_filter.hpp': 'Filter', + 'session_settings.hpp': 'Settings', + 'settings_pack.hpp': 'Settings', + 'fingerprint.hpp': 'Settings', + 'operations.hpp': 'Alerts', + 'disk_buffer_holder.hpp': 'Custom Storage', + 'alert_dispatcher.hpp': 'Alerts', +} + +category_fun_mapping = { + 'min_memory_usage()': 'Settings', + 'high_performance_seed()': 'Settings', + 'default_disk_io_constructor()': 'Storage', + 'settings_interface': 'Custom Storage', +} + + +def categorize_symbol(name, filename): + f = os.path.split(filename)[1] + + if name.endswith('_category()') \ + or name.endswith('_error_code') \ + or name.endswith('error_code_enum') \ + or name.endswith('errors'): + return 'Error Codes' + + if name in category_fun_mapping: + return category_fun_mapping[name] + + if f in category_mapping: + return category_mapping[f] + + if filename.startswith('libtorrent/kademlia/'): + return 'DHT' + + return 'Core' + + +def suppress_warning(filename, name): + f = os.path.split(filename)[1] + if f != 'alert_types.hpp': + return False + + # if name.endswith('_alert') or name == 'message()': + return True + + # return False + + +def first_item(itr): + for i in itr: + return i + return None + + +def is_visible(desc): + if desc.strip().startswith('hidden'): + return False + if internal: + return True + if desc.strip().startswith('internal'): + return False + return True + + +def highlight_signature(s): + name = s.split('(', 1) + name2 = name[0].split(' ') + if len(name2[-1]) == 0: + return s + + # make the name of the function bold + name2[-1] = '**' + name2[-1] + '** ' + + # if there is a return value, make sure we preserve pointer types + if len(name2) > 1: + name2[0] = name2[0].replace('*', '\\*') + name[0] = ' '.join(name2) + + # we have to escape asterisks, since this is rendered into + # a parsed literal in rst + name[1] = name[1].replace('*', '\\*') + + # we also have to escape colons + name[1] = name[1].replace(':', '\\:') + + # escape trailing underscores + name[1] = name[1].replace('_', '\\_') + + # comments in signatures are italic + name[1] = name[1].replace('/\\*', '*/\\*') + name[1] = name[1].replace('\\*/', '\\*/*') + return '('.join(name) + + +def highlight_name(s): + if '=' in s: + splitter = ' = ' + elif '{' in s: + splitter = '{' + else: + return s + + name = s.split(splitter, 1) + name2 = name[0].split(' ') + if len(name2[-1]) == 0: + return s + + name2[-1] = '**' + name2[-1] + '** ' + name[0] = ' '.join(name2) + return splitter.join(name) + + +def html_sanitize(s): + ret = '' + for i in s: + if i == '<': + ret += '<' + elif i == '>': + ret += '>' + elif i == '&': + ret += '&' + else: + ret += i + return ret + + +def looks_like_namespace(line): + line = line.strip() + if line.startswith('namespace'): + return True + return False + + +def looks_like_blank(line): + line = line.split('//')[0] + line = line.replace('{', '') + line = line.replace('}', '') + line = line.replace('[', '') + line = line.replace(']', '') + line = line.replace(';', '') + line = line.strip() + return len(line) == 0 + + +def looks_like_variable(line): + line = line.split('//')[0] + line = line.strip() + if ' ' not in line and '\t' not in line: + return False + if line.startswith('friend '): + return False + if line.startswith('enum '): + return False + if line.startswith(','): + return False + if line.startswith(':'): + return False + if line.startswith('typedef'): + return False + if line.startswith('using'): + return False + if ' = ' in line: + return True + if line.endswith(';'): + return True + return False + + +def looks_like_constant(line): + line = line.strip() + if line.startswith('inline'): + line = line.split('inline')[1] + line = line.strip() + if not line.startswith('constexpr'): + return False + line = line.split('constexpr')[1] + return looks_like_variable(line) + + +def looks_like_forward_decl(line): + line = line.split('//')[0] + line = line.strip() + if not line.endswith(';'): + return False + if '{' in line: + return False + if '}' in line: + return False + if line.startswith('friend '): + return True + if line.startswith('struct '): + return True + if line.startswith('class '): + return True + return False + + +def looks_like_function(line): + line = line.split('//')[0] + if line.startswith('friend class '): + return False + if line.startswith('friend struct '): + return False + if '::' in line.split('(')[0].split(' ')[-1]: + return False + if line.startswith(','): + return False + if line.startswith(':'): + return False + return '(' in line + + +def parse_function(lno, lines, filename): + + start_paren = 0 + end_paren = 0 + signature = '' + + global orphaned_export + orphaned_export = False + + while lno < len(lines): + line = lines[lno].strip() + lno += 1 + if line.startswith('//'): + continue + + start_paren += line.count('(') + end_paren += line.count(')') + + sig_line = line.replace('TORRENT_EXPORT ', '') \ + .replace('TORRENT_EXTRA_EXPORT', '') \ + .replace('TORRENT_V3_EXPLICIT', '') \ + .replace('TORRENT_COUNTER_NOEXCEPT', '') \ + .split('//')[0].strip() + if signature != '': + sig_line = '\n ' + sig_line + signature += sig_line + if verbose: + print('fun %s' % line) + + if start_paren > 0 and start_paren == end_paren: + if signature[-1] != ';': + # we also need to consume the function body + start_paren = 0 + end_paren = 0 + for i in range(len(signature)): + if signature[i] == '(': + start_paren += 1 + elif signature[i] == ')': + end_paren += 1 + + if start_paren > 0 and start_paren == end_paren: + for k in range(i, len(signature)): + if signature[k] == ':' or signature[k] == '{': + signature = signature[0:k].strip() + break + break + + lno = consume_block(lno - 1, lines) + signature += ';' + ret = [{'file': filename[11:], 'signatures': set([signature]), 'names': set( + [signature.split('(')[0].split(' ')[-1].strip() + '()'])}, lno] + if first_item(ret[0]['names']) == '()': + return [None, lno] + return ret + if len(signature) > 0: + print('\x1b[31mFAILED TO PARSE FUNCTION\x1b[0m %s\nline: %d\nfile: %s' % (signature, lno, filename)) + return [None, lno] + + +def add_desc(line): + # plain output prints just descriptions and filters out c++ code. + # it's used to run spell checker over + if plain_output: + for s in line.split('\n'): + # if the first character is a space, strip it + if len(s) > 0 and s[0] == ' ': + s = s[1:] + global in_code + if in_code is not None and not s.startswith(in_code) and len(s) > 1: + in_code = None + + if s.strip().startswith('.. code::'): + in_code = s.split('.. code::')[0] + '\t' + + # strip out C++ code from the plain text output since it's meant for + # running spell checking over + if not s.strip().startswith('.. ') and in_code is None: + plain_file.write(s + '\n') + + +def parse_class(lno, lines, filename): + start_brace = 0 + end_brace = 0 + + name = '' + funs = [] + fields = [] + enums = [] + state = 'public' + context = '' + class_type = 'struct' + blanks = 0 + decl = '' + + while lno < len(lines): + line = lines[lno].strip() + decl += lines[lno].replace('TORRENT_EXPORT ', '') \ + .replace('TORRENT_EXTRA_EXPORT', '') \ + .replace('TORRENT_V3_EXPLICIT', '') \ + .replace('TORRENT_COUNTER_NOEXCEPT', '').split('{')[0].strip() + if '{' in line: + break + if verbose: + print('class %s' % line) + lno += 1 + + if decl.startswith('class'): + state = 'private' + class_type = 'class' + + name = decl.split(':')[0].replace('class ', '').replace('struct ', '').replace('final', '').strip() + + global orphaned_export + orphaned_export = False + + while lno < len(lines): + line = lines[lno].strip() + lno += 1 + + if line == '': + blanks += 1 + context = '' + continue + + if line.startswith('/*'): + lno = consume_comment(lno - 1, lines) + continue + + if line.startswith('#'): + lno = consume_ifdef(lno - 1, lines, True) + continue + + if 'TORRENT_DEFINE_ALERT' in line: + if verbose: + print('xx %s' % line) + blanks += 1 + continue + if 'TORRENT_DEPRECATED' in line: + if verbose: + print('xx %s' % line) + blanks += 1 + continue + + if line.startswith('//'): + if verbose: + print('desc %s' % line) + + line = line[2:] + if len(line) and line[0] == ' ': + line = line[1:] + context += line + '\n' + continue + + start_brace += line.count('{') + end_brace += line.count('}') + + if line == 'private:': + state = 'private' + elif line == 'protected:': + state = 'protected' + elif line == 'public:': + state = 'public' + + if start_brace > 0 and start_brace == end_brace: + return [{'file': filename[11:], 'enums': enums, 'fields':fields, + 'type': class_type, 'name': name, 'decl': decl, 'fun': funs}, lno] + + if state != 'public' and not internal: + if verbose: + print('private %s' % line) + blanks += 1 + continue + + if start_brace - end_brace > 1: + if verbose: + print('scope %s' % line) + blanks += 1 + continue + + if looks_like_function(line): + current_fun, lno = parse_function(lno - 1, lines, filename) + if current_fun is not None and is_visible(context): + if context == '' and blanks == 0 and len(funs): + funs[-1]['signatures'].update(current_fun['signatures']) + funs[-1]['names'].update(current_fun['names']) + else: + if 'TODO: ' in context: + print('TODO comment in public documentation: %s:%d' % (filename, lno)) + sys.exit(1) + current_fun['desc'] = context + add_desc(context) + if context == '' and not suppress_warning(filename, first_item(current_fun['names'])): + print('WARNING: member function "%s" is not documented: \x1b[34m%s:%d\x1b[0m' + % (name + '::' + first_item(current_fun['names']), filename, lno)) + funs.append(current_fun) + context = '' + blanks = 0 + continue + + if looks_like_variable(line): + if 'constexpr static' in line: + print('ERROR: found "constexpr static", use "static constexpr" instead for consistency!\n%s:%d\n%s' + % (filename, lno, line)) + sys.exit(1) + if verbose: + print('var %s' % line) + if not is_visible(context): + continue + line = line.split('//')[0].strip() + # the name may look like this: + # std::uint8_t fails : 7; + # int scrape_downloaded = -1; + # static constexpr peer_flags_t interesting{0x1}; + n = line.split('=')[0].split('{')[0].strip().split(' : ')[0].split(' ')[-1].split(':')[0].split(';')[0] + if context == '' and blanks == 0 and len(fields): + fields[-1]['names'].append(n) + fields[-1]['signatures'].append(line) + else: + if context == '' and not suppress_warning(filename, n): + print('WARNING: field "%s" is not documented: \x1b[34m%s:%d\x1b[0m' + % (name + '::' + n, filename, lno)) + add_desc(context) + fields.append({'signatures': [line], 'names': [n], 'desc': context}) + context = '' + blanks = 0 + continue + + if line.startswith('enum '): + if verbose: + print('enum %s' % line) + if not is_visible(context): + consume_block(lno - 1, lines) + else: + enum, lno = parse_enum(lno - 1, lines, filename) + if enum is not None: + if 'TODO: ' in context: + print('TODO comment in public documentation: %s:%d' % (filename, lno)) + sys.exit(1) + enum['desc'] = context + add_desc(context) + if context == '' and not suppress_warning(filename, enum['name']): + print('WARNING: enum "%s" is not documented: \x1b[34m%s:%d\x1b[0m' + % (name + '::' + enum['name'], filename, lno)) + enums.append(enum) + context = '' + continue + + context = '' + + if verbose: + if looks_like_forward_decl(line) \ + or looks_like_blank(line) \ + or looks_like_namespace(line): + print('-- %s' % line) + else: + print('?? %s' % line) + + if len(name) > 0: + print('\x1b[31mFAILED TO PARSE CLASS\x1b[0m %s\nfile: %s:%d' % (name, filename, lno)) + return [None, lno] + + +def parse_constant(lno, lines, filename): + line = lines[lno].strip() + if verbose: + print('const %s' % line) + line = line.split('=')[0] + if 'constexpr' in line: + line = line.split('constexpr')[1] + if '{' in line and '}' in line: + line = line.split('{')[0] + t, name = line.strip().rsplit(' ', 1) + return [{'file': filename[11:], 'type': t, 'name': name}, lno + 1] + + +def parse_enum(lno, lines, filename): + start_brace = 0 + end_brace = 0 + global anon_index + + line = lines[lno].strip() + name = line.replace('enum ', '').replace('class ', '').split(':')[0].split('{')[0].strip() + if len(name) == 0: + if not internal: + print('WARNING: anonymous enum at: \x1b[34m%s:%d\x1b[0m' % (filename, lno)) + lno = consume_block(lno - 1, lines) + return [None, lno] + name = 'anonymous_enum_%d' % anon_index + anon_index += 1 + + values = [] + context = '' + if '{' not in line: + if verbose: + print('enum %s' % lines[lno]) + lno += 1 + + val = 0 + while lno < len(lines): + line = lines[lno].strip() + lno += 1 + + if line.startswith('//'): + if verbose: + print('desc %s' % line) + line = line[2:] + if len(line) and line[0] == ' ': + line = line[1:] + context += line + '\n' + continue + + if line.startswith('#'): + lno = consume_ifdef(lno - 1, lines) + continue + + start_brace += line.count('{') + end_brace += line.count('}') + + if '{' in line: + line = line.split('{')[1] + line = line.split('}')[0] + + if len(line): + if verbose: + print('enumv %s' % lines[lno - 1]) + for v in line.split(','): + v = v.strip() + if v.startswith('//'): + break + if v == '': + continue + valstr = '' + try: + if '=' in v: + val = int(v.split('=')[1].strip(), 0) + valstr = str(val) + except Exception: + pass + + if '=' in v: + v = v.split('=')[0].strip() + if is_visible(context): + add_desc(context) + values.append({'name': v.strip(), 'desc': context, 'val': valstr}) + if verbose: + print('enumv %s' % valstr) + context = '' + val += 1 + else: + if verbose: + print('?? %s' % lines[lno - 1]) + + if start_brace > 0 and start_brace == end_brace: + return [{'file': filename[11:], 'name': name, 'values': values}, lno] + + if len(name) > 0: + print('\x1b[31mFAILED TO PARSE ENUM\x1b[0m %s\nline: %d\nfile: %s' % (name, lno, filename)) + return [None, lno] + + +def consume_block(lno, lines): + start_brace = 0 + end_brace = 0 + + while lno < len(lines): + line = lines[lno].strip() + if verbose: + print('xx %s' % line) + lno += 1 + + start_brace += line.count('{') + end_brace += line.count('}') + + if start_brace > 0 and start_brace == end_brace: + break + return lno + + +def consume_comment(lno, lines): + while lno < len(lines): + line = lines[lno].strip() + if verbose: + print('xx %s' % line) + lno += 1 + if '*/' in line: + break + + return lno + + +def trim_define(line): + return line.replace('#ifndef', '').replace('#ifdef', '') \ + .replace('#if', '').replace('defined', '') \ + .replace('TORRENT_ABI_VERSION == 1', '') \ + .replace('TORRENT_ABI_VERSION <= 2', '') \ + .replace('TORRENT_ABI_VERSION < 3', '') \ + .replace('||', '').replace('&&', '').replace('(', '').replace(')', '') \ + .replace('!', '').replace('\\', '').strip() + + +def consume_ifdef(lno, lines, warn_on_ifdefs=False): + line = lines[lno].strip() + lno += 1 + + start_if = 1 + end_if = 0 + + if verbose: + print('prep %s' % line) + + if warn_on_ifdefs and line.strip().startswith('#if'): + while line.endswith('\\'): + lno += 1 + line += lines[lno].strip() + if verbose: + print('prep %s' % lines[lno].trim()) + define = trim_define(line) + if 'TORRENT_' in define and 'TORRENT_ABI_VERSION' not in define: + print('\x1b[31mWARNING: possible ABI breakage in public struct! "%s" \x1b[34m %s:%d\x1b[0m' % + (define, filename, lno)) + elif define != '': + print('\x1b[33msensitive define in public struct: "%s"\x1b[34m %s:%d\x1b[0m' % (define, filename, lno)) + + if (line.startswith('#if') and ( + ' TORRENT_USE_ASSERTS' in line or + ' TORRENT_USE_INVARIANT_CHECKS' in line or + ' TORRENT_ASIO_DEBUGGING' in line) or + line == '#if TORRENT_ABI_VERSION == 1' or + line == '#if TORRENT_ABI_VERSION <= 2' or + line == '#if TORRENT_ABI_VERSION < 3'): + while lno < len(lines): + line = lines[lno].strip() + lno += 1 + if verbose: + print('prep %s' % line) + if line.startswith('#endif'): + end_if += 1 + if line.startswith('#if'): + start_if += 1 + if line == '#else' and start_if - end_if == 1: + break + if start_if - end_if == 0: + break + return lno + else: + while line.endswith('\\') and lno < len(lines): + line = lines[lno].strip() + lno += 1 + if verbose: + print('prep %s' % line) + + return lno + + +for filename in files: + h = open(filename) + lines = h.read().split('\n') + + if verbose: + print('\n=== %s ===\n' % filename) + + blanks = 0 + lno = 0 + orphaned_export = False + + while lno < len(lines): + line = lines[lno].strip() + + if orphaned_export: + print('ERROR: TORRENT_EXPORT without function or class!\n%s:%d\n%s' % (filename, lno, line)) + sys.exit(1) + + lno += 1 + + if line == '': + blanks += 1 + context = '' + continue + + if 'TORRENT_EXPORT' in line.split() \ + and 'ifndef TORRENT_EXPORT' not in line \ + and 'define TORRENT_DEPRECATED_EXPORT TORRENT_EXPORT' not in line \ + and 'define TORRENT_EXPORT' not in line \ + and 'for TORRENT_EXPORT' not in line \ + and 'TORRENT_EXPORT TORRENT_CFG' not in line \ + and 'extern TORRENT_EXPORT ' not in line \ + and 'struct TORRENT_EXPORT ' not in line: + orphaned_export = True + if verbose: + print('maybe orphaned: %s\n' % line) + + if line.startswith('//') and line[2:].strip() == 'OVERVIEW': + # this is a section overview + current_overview = '' + while lno < len(lines): + line = lines[lno].strip() + lno += 1 + if not line.startswith('//'): + # end of overview + overviews[filename[11:]] = current_overview + current_overview = '' + break + line = line[2:] + if line.startswith(' '): + line = line[1:] + current_overview += line + '\n' + + if line.startswith('//'): + if verbose: + print('desc %s' % line) + line = line[2:] + if len(line) and line[0] == ' ': + line = line[1:] + context += line + '\n' + continue + + if line.startswith('/*'): + lno = consume_comment(lno - 1, lines) + continue + + if line.startswith('#'): + lno = consume_ifdef(lno - 1, lines) + continue + + if (line == 'namespace aux {' or + line == 'namespace ssl {' or + line == 'namespace libtorrent { namespace aux {') \ + and not internal: + lno = consume_block(lno - 1, lines) + context = '' + continue + + if 'namespace aux' in line and \ + '//' not in line.split('namespace')[0] and \ + '}' not in line.split('namespace')[1]: + print('ERROR: whitespace preceding namespace declaration: %s:%d' % (filename, lno)) + sys.exit(1) + + if 'TORRENT_DEPRECATED' in line: + if ('class ' in line or 'struct ' in line) and ';' not in line: + lno = consume_block(lno - 1, lines) + context = '' + blanks += 1 + if verbose: + print('xx %s' % line) + continue + + if looks_like_constant(line): + if 'constexpr static' in line: + print('ERROR: found "constexpr static", use "static constexpr" instead for consistency!\n%s:%d\n%s' + % (filename, lno, line)) + sys.exit(1) + current_constant, lno = parse_constant(lno - 1, lines, filename) + if current_constant is not None and is_visible(context): + if 'TODO: ' in context: + print('TODO comment in public documentation: %s:%d' % (filename, lno)) + sys.exit(1) + current_constant['desc'] = context + add_desc(context) + if context == '': + print('WARNING: constant "%s" is not documented: \x1b[34m%s:%d\x1b[0m' + % (current_constant['name'], filename, lno)) + t = current_constant['type'] + if t in constants: + constants[t].append(current_constant) + else: + constants[t] = [current_constant] + continue + + if 'TORRENT_EXPORT ' in line or line.startswith('inline ') or line.startswith('template') or internal: + if line.startswith('class ') or line.startswith('struct '): + if not line.endswith(';'): + current_class, lno = parse_class(lno - 1, lines, filename) + if current_class is not None and is_visible(context): + if 'TODO: ' in context: + print('TODO comment in public documentation: %s:%d' % (filename, lno)) + sys.exit(1) + current_class['desc'] = context + add_desc(context) + if context == '': + print('WARNING: class "%s" is not documented: \x1b[34m%s:%d\x1b[0m' + % (current_class['name'], filename, lno)) + classes.append(current_class) + context = '' + blanks += 1 + continue + + if looks_like_function(line): + current_fun, lno = parse_function(lno - 1, lines, filename) + if current_fun is not None and is_visible(context): + if context == '' and blanks == 0 and len(functions): + functions[-1]['signatures'].update(current_fun['signatures']) + functions[-1]['names'].update(current_fun['names']) + else: + if 'TODO: ' in context: + print('TODO comment in public documentation: %s:%d' % (filename, lno)) + sys.exit(1) + current_fun['desc'] = context + add_desc(context) + if context == '': + print('WARNING: function "%s" is not documented: \x1b[34m%s:%d\x1b[0m' + % (first_item(current_fun['names']), filename, lno)) + functions.append(current_fun) + context = '' + blanks = 0 + continue + + if ('enum class ' not in line and 'class ' in line or 'struct ' in line) and ';' not in line: + lno = consume_block(lno - 1, lines) + context = '' + blanks += 1 + continue + + if line.startswith('enum '): + if not is_visible(context): + consume_block(lno - 1, lines) + else: + current_enum, lno = parse_enum(lno - 1, lines, filename) + if current_enum is not None and is_visible(context): + if 'TODO: ' in context: + print('TODO comment in public documentation: %s:%d' % (filename, lno)) + sys.exit(1) + current_enum['desc'] = context + add_desc(context) + if context == '': + print('WARNING: enum "%s" is not documented: \x1b[34m%s:%d\x1b[0m' + % (current_enum['name'], filename, lno)) + enums.append(current_enum) + context = '' + blanks += 1 + continue + + blanks += 1 + if verbose: + if looks_like_forward_decl(line) \ + or looks_like_blank(line) \ + or looks_like_namespace(line): + print('-- %s' % line) + else: + print('?? %s' % line) + + context = '' + h.close() + +# ==================================================================== +# +# RENDER PART +# +# ==================================================================== + + +def new_category(cat): + return {'classes': [], 'functions': [], 'enums': [], + 'filename': 'reference-%s.rst' % cat.replace(' ', '_'), + 'constants': {}} + + +if dump: + + if verbose: + print('\n===============================\n') + + for c in classes: + print('\x1b[4m%s\x1b[0m %s\n{' % (c['type'], c['name'])) + for f in c['fun']: + for s in f['signatures']: + print(' %s' % s.replace('\n', '\n ')) + + if len(c['fun']) > 0 and len(c['fields']) > 0: + print('') + + for f in c['fields']: + for s in f['signatures']: + print(' %s' % s) + + if len(c['fields']) > 0 and len(c['enums']) > 0: + print('') + + for e in c['enums']: + print(' \x1b[4menum\x1b[0m %s\n {' % e['name']) + for v in e['values']: + print(' %s' % v['name']) + print(' };') + print('};\n') + + for f in functions: + print('%s' % f['signature']) + + for e in enums: + print('\x1b[4menum\x1b[0m %s\n{' % e['name']) + for v in e['values']: + print(' %s' % v['name']) + print('};') + + for t, c in constants: + print('\x1b[4mconstant\x1b[0m %s %s\n' % (e['type'], e['name'])) + +categories = {} + +for c in classes: + cat = categorize_symbol(c['name'], c['file']) + if cat not in categories: + categories[cat] = new_category(cat) + + if c['file'] in overviews: + categories[cat]['overview'] = overviews[c['file']] + + filename = categories[cat]['filename'].replace('.rst', '.html') + '#' + categories[cat]['classes'].append(c) + symbols[c['name']] = filename + c['name'] + for f in c['fun']: + for n in f['names']: + symbols[n] = filename + n + symbols[c['name'] + '::' + n] = filename + n + + for f in c['fields']: + for n in f['names']: + symbols[c['name'] + '::' + n] = filename + n + + for e in c['enums']: + symbols[e['name']] = filename + e['name'] + symbols[c['name'] + '::' + e['name']] = filename + e['name'] + for v in e['values']: + # symbols[v['name']] = filename + v['name'] + symbols[e['name'] + '::' + v['name']] = filename + v['name'] + symbols[c['name'] + '::' + v['name']] = filename + v['name'] + +for f in functions: + cat = categorize_symbol(first_item(f['names']), f['file']) + if cat not in categories: + categories[cat] = new_category(cat) + + if f['file'] in overviews: + categories[cat]['overview'] = overviews[f['file']] + + for n in f['names']: + symbols[n] = categories[cat]['filename'].replace('.rst', '.html') + '#' + n + categories[cat]['functions'].append(f) + +for e in enums: + cat = categorize_symbol(e['name'], e['file']) + if cat not in categories: + categories[cat] = new_category(cat) + categories[cat]['enums'].append(e) + filename = categories[cat]['filename'].replace('.rst', '.html') + '#' + symbols[e['name']] = filename + e['name'] + for v in e['values']: + symbols[e['name'] + '::' + v['name']] = filename + v['name'] + +for t, c in constants.items(): + for const in c: + cat = categorize_symbol(t, const['file']) + if cat not in categories: + categories[cat] = new_category(cat) + if t not in categories[cat]['constants']: + categories[cat]['constants'][t] = [const] + else: + categories[cat]['constants'][t].append(const) + filename = categories[cat]['filename'].replace('.rst', '.html') + '#' + symbols[t + '::' + const['name']] = filename + t + '::' + const['name'] + symbols[t] = filename + t + + +def print_declared_in(out, o): + out.write('Declared in "%s"\n\n' % print_link(o['file'], 'include/%s' % o['file'])) + print(dump_link_targets(), file=out) + +# returns RST marked up string + + +def linkify_symbols(string): + lines = string.split('\n') + ret = [] + in_literal = False + lno = 0 + return_string = '' + for line in lines: + lno += 1 + # don't touch headlines, i.e. lines whose + # next line entirely contains one of =, - or . + if (lno < len(lines) - 1): + next_line = lines[lno] + else: + next_line = '' + + if '.. include:: ' in line: + return_string += '\n'.join(ret) + ret = [line] + return_string += dump_link_targets() + '\n' + continue + + if len(next_line) > 0 and lines[lno].replace('=', ''). \ + replace('-', '').replace('.', '') == '': + ret.append(line) + continue + + if line.startswith('|'): + ret.append(line) + continue + if in_literal and not line.startswith('\t') and not line == '': + # print(' end literal: "%s"' % line) + in_literal = False + if in_literal: + # print(' literal: "%s"' % line) + ret.append(line) + continue + if line.strip() == '.. parsed-literal::' or \ + line.strip().startswith('.. code::') or \ + (not line.strip().startswith('..') and line.endswith('::')): + # print(' start literal: "%s"' % line) + in_literal = True + words = line.split(' ') + + for i in range(len(words)): + # it's important to preserve leading + # tabs, since that's relevant for + # rst markup + + leading = '' + w = words[i] + + if len(w) == 0: + continue + + while len(w) > 0 and \ + w[0] in ['\t', ' ', '(', '[', '{']: + leading += w[0] + w = w[1:] + + # preserve commas and dots at the end + w = w.strip() + trailing = '' + + if len(w) == 0: + continue + + while len(w) > 1 and w[-1] in ['.', ',', ')'] and w[-2:] != '()': + trailing = w[-1] + trailing + w = w[:-1] + + link_name = w + + # print(w) + + if len(w) == 0: + continue + + if link_name[-1] == '_': + link_name = link_name[:-1] + + if w in symbols: + link_name = link_name.replace('-', ' ') + # print(' found %s -> %s' % (w, link_name)) + words[i] = leading + print_link(link_name, symbols[w]) + trailing + ret.append(' '.join(words)) + return_string += '\n'.join(ret) + return return_string + + +link_targets = [] + + +def print_link(name, target): + global link_targets + link_targets.append(target) + return "`%s`__" % name + + +def dump_link_targets(indent=''): + global link_targets + ret = '\n' + for link in link_targets: + ret += '%s__ %s\n' % (indent, link) + link_targets = [] + return ret + + +def heading(string, c, indent=''): + string = string.strip() + return '\n' + indent + string + '\n' + indent + (c * len(string)) + '\n' + + +def render_enums(out, enums, print_declared_reference, header_level): + for e in enums: + print('.. raw:: html\n', file=out) + print('\t' % e['name'], file=out) + print('', file=out) + dump_report_issue('enum ' + e['name'], out) + print(heading('enum %s' % e['name'], header_level), file=out) + + print_declared_in(out, e) + + width = [len('name'), len('value'), len('description')] + + for i in range(len(e['values'])): + e['values'][i]['desc'] = linkify_symbols(e['values'][i]['desc']) + + for v in e['values']: + width[0] = max(width[0], len(v['name'])) + width[1] = max(width[1], len(v['val'])) + for d in v['desc'].split('\n'): + width[2] = max(width[2], len(d)) + + print('+-' + ('-' * width[0]) + '-+-' + ('-' * width[1]) + '-+-' + ('-' * width[2]) + '-+', file=out) + print('| ' + 'name'.ljust(width[0]) + ' | ' + 'value'.ljust(width[1]) + ' | ' + + 'description'.ljust(width[2]) + ' |', file=out) + print('+=' + ('=' * width[0]) + '=+=' + ('=' * width[1]) + '=+=' + ('=' * width[2]) + '=+', file=out) + for v in e['values']: + d = v['desc'].split('\n') + if len(d) == 0: + d = [''] + print('| ' + v['name'].ljust(width[0]) + ' | ' + v['val'].ljust(width[1]) + ' | ' + + d[0].ljust(width[2]) + ' |', file=out) + for s in d[1:]: + print('| ' + (' ' * width[0]) + ' | ' + (' ' * width[1]) + ' | ' + s.ljust(width[2]) + ' |', file=out) + print('+-' + ('-' * width[0]) + '-+-' + ('-' * width[1]) + '-+-' + ('-' * width[2]) + '-+', file=out) + print('', file=out) + + print(dump_link_targets(), file=out) + + +sections = \ + { + 'Core': 0, + 'DHT': 0, + 'Session': 0, + 'Torrent Handle': 0, + 'Torrent Info': 0, + 'Trackers': 0, + 'Settings': 0, + 'Torrent Status': 0, + 'Stats': 0, + 'Resume Data': 0, + 'Add Torrent': 0, + + 'Bencoding': 1, + 'Bdecoding': 1, + 'Filter': 1, + 'Error Codes': 1, + 'Create Torrents': 1, + + 'PeerClass': 2, + 'ed25519': 2, + 'Utility': 2, + 'Storage': 2, + 'Custom Storage': 2, + 'Plugins': 2, + + 'Alerts': 3 + } + + +def print_toc(out, categories, s): + + main_toc = False + + for cat in categories: + if (s != 2 and cat not in sections) or \ + (cat in sections and sections[cat] != s): + continue + + if not main_toc: + out.write('.. container:: main-toc\n\n') + main_toc = True + + print('\t.. rubric:: %s\n' % cat, file=out) + + if 'overview' in categories[cat]: + print('\t| overview__', file=out) + + for c in categories[cat]['classes']: + print('\t| ' + print_link(c['name'], symbols[c['name']]), file=out) + for f in categories[cat]['functions']: + for n in f['names']: + print('\t| ' + print_link(n, symbols[n]), file=out) + for e in categories[cat]['enums']: + print('\t| ' + print_link(e['name'], symbols[e['name']]), file=out) + for t, c in categories[cat]['constants'].items(): + print('\t| ' + print_link(t, symbols[t]), file=out) + print('', file=out) + + if 'overview' in categories[cat]: + print('\t__ %s#overview' % categories[cat]['filename'].replace('.rst', '.html'), file=out) + print(dump_link_targets('\t'), file=out) + + +def dump_report_issue(h, out): + print(('.. raw:: html\n\n\t[report issue]\n\n').format( + urllib.parse.quote_plus(h), + urllib.parse.quote_plus('Documentation under heading "' + h + '" could be improved')), file=out) + + +def render(out, category): + + classes = category['classes'] + functions = category['functions'] + enums = category['enums'] + constants = category['constants'] + + if 'overview' in category: + out.write('%s\n' % linkify_symbols(category['overview'])) + + for c in classes: + + print('.. raw:: html\n', file=out) + print('\t' % c['name'], file=out) + print('', file=out) + + dump_report_issue('class ' + c['name'], out) + out.write('%s\n' % heading(c['name'], '-')) + print_declared_in(out, c) + c['desc'] = linkify_symbols(c['desc']) + out.write('%s\n' % c['desc']) + print(dump_link_targets(), file=out) + + print('\n.. parsed-literal::\n\t', file=out) + + block = '\n%s\n{\n' % c['decl'] + for f in c['fun']: + for s in f['signatures']: + block += ' %s\n' % highlight_signature(s.replace('\n', '\n ')) + + if len(c['fun']) > 0 and len(c['enums']) > 0: + block += '\n' + + first = True + for e in c['enums']: + if not first: + block += '\n' + first = False + block += ' enum %s\n {\n' % e['name'] + for v in e['values']: + block += ' %s,\n' % v['name'] + block += ' };\n' + + if len(c['fun']) + len(c['enums']) > 0 and len(c['fields']): + block += '\n' + + for f in c['fields']: + for s in f['signatures']: + block += ' %s\n' % highlight_name(s) + + block += '};' + + print(block.replace('\n', '\n\t') + '\n', file=out) + + for f in c['fun']: + if f['desc'] == '': + continue + print('.. raw:: html\n', file=out) + for n in f['names']: + print('\t' % n, file=out) + print('', file=out) + h = ' '.join(f['names']) + dump_report_issue('%s::[%s]' % (c['name'], h), out) + print(heading(h, '.'), file=out) + + block = '.. parsed-literal::\n\n' + + for s in f['signatures']: + block += highlight_signature(s.replace('\n', '\n ')) + '\n' + print('%s\n' % block.replace('\n', '\n\t'), file=out) + f['desc'] = linkify_symbols(f['desc']) + print('%s' % f['desc'], file=out) + + print(dump_link_targets(), file=out) + + render_enums(out, c['enums'], False, '.') + + for f in c['fields']: + if f['desc'] == '': + continue + + print('.. raw:: html\n', file=out) + for n in f['names']: + print('\t' % n, file=out) + print('', file=out) + h = ' '.join(f['names']) + dump_report_issue('%s::[%s]' % (c['name'], h), out) + print(h, file=out) + f['desc'] = linkify_symbols(f['desc']) + print('\t%s' % f['desc'].replace('\n', '\n\t'), file=out) + + print(dump_link_targets(), file=out) + + for f in functions: + print('.. raw:: html\n', file=out) + for n in f['names']: + print('\t' % n, file=out) + print('', file=out) + h = ' '.join(f['names']) + dump_report_issue(h, out) + print(heading(h, '-'), file=out) + print_declared_in(out, f) + + block = '.. parsed-literal::\n\n' + for s in f['signatures']: + block += highlight_signature(s) + '\n' + + print('%s\n' % block.replace('\n', '\n\t'), file=out) + print(linkify_symbols(f['desc']), file=out) + + print(dump_link_targets(), file=out) + + render_enums(out, enums, True, '-') + + for t, c in constants.items(): + print('.. raw:: html\n', file=out) + print('\t\n' % t, file=out) + dump_report_issue(t, out) + print(heading(t, '-'), file=out) + print_declared_in(out, c[0]) + + for v in c: + print('.. raw:: html\n', file=out) + print('\t\n' % (t, v['name']), file=out) + print(v['name'], file=out) + v['desc'] = linkify_symbols(v['desc']) + print('\t%s' % v['desc'].replace('\n', '\n\t'), file=out) + print(dump_link_targets('\t'), file=out) + + print('', file=out) + + print(dump_link_targets(), file=out) + + for i in static_links: + print(i, file=out) + + +if single_page_output: + + out = open('single-page-ref.rst', 'w+') + out.write('''.. include:: header.rst + +`home`__ + +__ reference.html + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +''') + + for cat in categories: + render(out, categories[cat]) + + out.close() + +else: + + out = open('reference.rst', 'w+') + out.write('''======================= +reference documentation +======================= + +''') + + out.write('`single-page version`__\n\n__ single-page-ref.html\n\n') + + for i in range(4): + + print_toc(out, categories, i) + + out.close() + + for cat in categories: + out = open(categories[cat]['filename'], 'w+') + + out.write('''.. include:: header.rst + +`home`__ + +__ reference.html + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +''') + + render(out, categories[cat]) + + out.close() + +# for s in symbols: +# print(s) + + for i, o in list(preprocess_rst.items()): + f = open(i, 'r') + out = open(o, 'w+') + print('processing %s -> %s' % (i, o)) + link = linkify_symbols(f.read()) + print(link, end=' ', file=out) + + print(dump_link_targets(), file=out) + + out.close() + f.close() diff --git a/docs/gen_settings_doc.py b/docs/gen_settings_doc.py new file mode 100755 index 0000000..a16e2d2 --- /dev/null +++ b/docs/gen_settings_doc.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +from __future__ import print_function + +f = open('../include/libtorrent/settings_pack.hpp') + +out = open('settings.rst', 'w+') +all_names = set() + + +def print_field(str, width): + return '%s%s' % (str, ' ' * (width - len(str))) + + +def render_section(names, description, type, default_values): + max_name_len = max(len(max(names, key=len)), len('name')) + max_type_len = max(len(type), len('type')) + max_val_len = max(len(max(default_values, key=len)), len('default')) + + # add link targets for the rest of the manual to reference + for n in names: + print('.. _%s:\n' % n, file=out) + for w in n.split('_'): + all_names.add(w) + + if len(names) > 0: + print('.. raw:: html\n', file=out) + for n in names: + print('\t' % n, file=out) + print('', file=out) + + separator = '+-' + ('-' * max_name_len) + '-+-' + ('-' * max_type_len) + '-+-' + ('-' * max_val_len) + '-+' + + # build a table for the settings, their type and default value + print(separator, file=out) + print( + '| %s | %s | %s |' % + (print_field( + 'name', max_name_len), print_field( + 'type', max_type_len), print_field( + 'default', max_val_len)), file=out) + print(separator.replace('-', '='), file=out) + for i in range(len(names)): + print( + '| %s | %s | %s |' % + (print_field( + names[i], max_name_len), print_field( + type, max_type_len), print_field( + default_values[i], max_val_len)), file=out) + print(separator, file=out) + print(file=out) + print(description, file=out) + + +mode = '' + +# parse out default values for settings +f2 = open('../src/settings_pack.cpp') +def_map = {} +for line in f2: + line = line.strip() + if not line.startswith('SET(') \ + and not line.startswith('SET_NOPREV(') \ + and not line.startswith('DEPRECATED_SET('): + continue + + line = line.split('(')[1].split(',') + if line[1].strip()[0] == '"': + default = ','.join(line[1:]).strip()[1:].split('"')[0].strip() + else: + default = line[1].strip() + def_map[line[0]] = default + print('%s = %s' % (line[0], default)) + +description = '' +names = [] + +for line in f: + if 'enum string_types' in line: + mode = 'string' + if 'enum bool_types' in line: + mode = 'bool' + if 'enum int_types' in line: + mode = 'int' + if '#if TORRENT_ABI_VERSION == 1' in line: + mode += 'skip' + if '#if TORRENT_ABI_VERSION <= 2' in line: + mode += 'skip' + if '#endif' in line: + mode = mode[0:-4] + + if mode == '': + continue + if mode[-4:] == 'skip': + continue + + line = line.lstrip() + + if line == '' and len(names) > 0: + if description == '': + for n in names: + print('WARNING: no description for "%s"' % n) + elif description.strip() != 'hidden': + default_values = [] + for n in names: + default_values.append(def_map[n]) + render_section(names, description, mode, default_values) + description = '' + names = [] + + if line.startswith('};'): + mode = '' + continue + + if line.startswith('//'): + if line[2] == ' ': + description += line[3:] + else: + description += line[2:] + continue + + line = line.strip() + if line.endswith(','): + line = line[:-1] # strip trailing comma + if '=' in line: + line = line.split('=')[0].strip() + if line.endswith('_internal'): + continue + + names.append(line) + +dictionary = open('hunspell/settings.dic', 'w+') +for w in all_names: + dictionary.write(w + '\n') +dictionary.close() +out.close() +f.close() diff --git a/docs/gen_stats_doc.py b/docs/gen_stats_doc.py new file mode 100755 index 0000000..c32e017 --- /dev/null +++ b/docs/gen_stats_doc.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +from __future__ import print_function + +counter_types = {} + +f = open('../include/libtorrent/performance_counters.hpp') + +counter_type = '' + +for line in f: + + # ignore anything after // + if '//' in line: + line = line.split('//')[0] + + line = line.strip() + + if line.startswith('#'): + continue + if line == '': + continue + + if 'enum stats_counter_t' in line: + counter_type = 'counter' + continue + + if 'enum stats_gauge_t' in line: + counter_type = 'gauge' + continue + + if '{' in line or '}' in line or 'struct' in line or 'namespace' in line: + continue + if counter_type == '': + continue + if not line.endswith(','): + continue + + # strip off trailing comma + line = line[:-1] + if '=' in line: + line = line[:line.index('=')].strip() + + counter_types[line] = counter_type + +f.close() + +f = open('../src/session_stats.cpp') + +out = open('stats_counters.rst', 'w+') + + +def print_field(str, width): + return '%s%s' % (str, ' ' * (width - len(str))) + + +def render_section(names, description, types): + max_name_len = max(len(max(names, key=len)), len('name')) + max_type_len = max(len(max(types, key=len)), len('type')) + + if description == '': + for n in names: + print('WARNING: no description for "%s"' % n) + + # add link targets for the rest of the manual to reference + for n in names: + print('.. _%s:\n' % n, file=out) + + if len(names) > 0: + print('.. raw:: html\n', file=out) + for n in names: + print('\t' % n, file=out) + print('', file=out) + + separator = '+-' + ('-' * max_name_len) + '-+-' + ('-' * max_type_len) + '-+' + + # build a table for the settings, their type and default value + print(separator, file=out) + print('| %s | %s |' % (print_field('name', max_name_len), print_field('type', max_type_len)), file=out) + print(separator.replace('-', '='), file=out) + for i in range(len(names)): + print('| %s | %s |' % (print_field(names[i], max_name_len), print_field(types[i], max_type_len)), file=out) + print(separator, file=out) + print(file=out) + print(description, file=out) + print('', file=out) + + +mode = '' + +description = '' +names = [] +types = [] + +for line in f: + description_line = line.lstrip().startswith('//') + + line = line.strip() + + if mode == 'ignore': + if '#endif' in line: + mode = '' + continue + + if 'TORRENT_ABI_VERSION == 1' in line: + mode = 'ignore' + continue + + if description_line: + if len(names) > 0: + render_section(names, description, types) + description = '' + names = [] + types = [] + + description += '\n' + line[3:] + + if '#define' in line: + continue + + if 'METRIC(' in line: + args = line.split('(')[1].split(')')[0].split(',') + + # args: category, name, type + + args[1] = args[1].strip() + names.append(args[0].strip() + '.' + args[1].strip()) + types.append(counter_types[args[1]]) + +if len(names) > 0: + render_section(names, description, types) + +out.close() +f.close() diff --git a/docs/gen_todo.py b/docs/gen_todo.py new file mode 100755 index 0000000..d188475 --- /dev/null +++ b/docs/gen_todo.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 + +import glob +import os +import sys + +paths = [ + 'test/*.cpp', + 'src/*.cpp', + 'src/kademlia/*.cpp', + 'include/libtorrent/*.hpp', + 'include/libtorrent/kademlia/*.hpp', + 'include/libtorrent/aux_/*.hpp', + 'include/libtorrent/extensions/*.hpp'] + +os.system('(cd .. ; ctags %s 2>/dev/null)' % ' '.join(paths)) + +files = [] + +for p in paths: + files.extend(glob.glob(os.path.join('..', p))) + +items = [] + +# keeps 20 non-comment lines, used to get more context around +# todo-items +context = [] + +priority_count = [0, 0, 0, 0, 0] + + +def html_sanitize(s): + ret = '' + for i in s: + if i == '<': + ret += '<' + elif i == '>': + ret += '>' + elif i == '&': + ret += '&' + else: + ret += i + return ret + + +for f in files: + h = open(f) + + state = '' + line_no = 0 + context_lines = 0 + + for orig_line in h: + line_no += 1 + line = orig_line.strip() + if 'TODO:' in line and line.startswith('//'): + line = line.split('TODO:')[1].strip() + state = 'todo' + items.append({}) + items[-1]['location'] = '%s:%d' % (f, line_no) + items[-1]['priority'] = 0 + if line[0] in '0123456789': + items[-1]['priority'] = int(line[0]) + if int(line[0]) > 5: + print('priority too high: ' + line) + sys.exit(1) + + line = line[1:].strip() + items[-1]['todo'] = line + prio = items[-1]['priority'] + if prio >= 0 and prio <= 4: + priority_count[prio] += 1 + continue + + if state == '': + context.append(html_sanitize(orig_line)) + if len(context) > 20: + context.pop(0) + continue + + if state == 'todo': + if line.strip().startswith('//'): + items[-1]['todo'] += '\n' + items[-1]['todo'] += line[2:].strip() + else: + state = 'context' + items[-1]['context'] = ''.join(context) + \ + '

' + html_sanitize(orig_line) + '
' + context_lines = 1 + + context.append(html_sanitize(orig_line)) + if len(context) > 20: + context.pop(0) + continue + + if state == 'context': + items[-1]['context'] += html_sanitize(orig_line) + context_lines += 1 + + context.append(html_sanitize(orig_line)) + if len(context) > 20: + context.pop(0) + if context_lines > 30: + state = '' + + h.close() + +items.sort(key=lambda x: x['priority'], reverse=True) + +# for i in items: +# print('\n\n', i['todo'], '\n') +# print(i['location'], '\n') +# print('prio: ', i['priority'], '\n') +# if 'context' in i: +# print(i['context'], '\n') + +out = open('todo.html', 'w+') +out.write(''' + + + +

libtorrent todo-list

+%d urgent +%d important +%d relevant +%d feasible +%d notes + ''' # noqa + % (priority_count[4], priority_count[3], priority_count[2], priority_count[1], priority_count[0])) + +prio_colors = ['#ccc', '#ccf', '#cfc', '#fcc', '#f44'] + +index = 0 +for i in items: + if 'context' not in i: + i['context'] = '' + out.write(('' + '') + % (prio_colors[i['priority']], i['priority'], index, i['location'], i['todo'].replace('\n', ' '))) + + out.write( + ('') % + (index, i['todo'], i['location'], i['context'])) + index += 1 + +out.write('
relevance %d%s%s
') +out.close() diff --git a/docs/hacking.rst b/docs/hacking.rst new file mode 100644 index 0000000..ebd15ee --- /dev/null +++ b/docs/hacking.rst @@ -0,0 +1,123 @@ +================== +libtorrent hacking +================== + +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +This describe some of the internals of libtorrent. If you're looking for +something to contribute, please take a look at the `todo list`_. + +.. _`todo list`: todo.html + +terminology +=========== + +This section describes some of the terminology used throughout the +libtorrent source. Having a good understanding of some of these keywords +helps understanding what's going on. + +A *piece* is a part of the data of a torrent that has a SHA-1 hash in +the .torrent file. Pieces are almost always a power of two in size, but not +necessarily. Each piece is split up in *blocks*, which is a 16 kiB. A block +never spans two pieces. If a piece is smaller than 16 kiB or not divisible +by 16 kiB, there are blocks smaller than that. + +16 kiB is a de-facto standard of the largest transfer unit in the bittorrent +protocol. Clients typically reject any request for larger pieces than this. + +The *piece picker* is the part of a bittorrent client that is responsible for +the logic to determine which requests to send to peers. It doesn't actually +pick full pieces, but blocks (from pieces). + +The file layout of a torrent is represented by *file storage* objects. This +class contains a list of all files in the torrent (in a well defined order), +the size of the pieces and implicitly the total size of the whole torrent and +number of pieces. The file storage determines the mapping from *pieces* +to *files*. This representation may be quite complex in order to keep it extremely +compact. This is useful to load very large torrents without exploding in memory +usage. + +A *torrent* object represents all the state of swarm download. This includes +a piece picker, a list of peer connections, file storage (torrent file). One +important distinction is between a connected peer (*peer_connection*) and a peer +we just know about, and may have been connected to, and may connect to in the +future (*torrent_peer*). The list of (not connected) peers may grow very large +if not limited (through tracker responses, DHT and peer exchange). This list +is typically limited to a few thousand peers. + +The *peer_list* maintains a potentially large list of known peers for a swarm +(not necessarily connected). + +structure +========= + +This is the high level structure of libtorrent. Bold types are part of the public +interface: + + +.. image:: img/hacking.png + :class: bw + +session_impl +------------ + +This is the session state object, containing all session global information, such as: + + * the list of all torrents ``m_torrent``. + * the list of all peer connections ``m_connections``. + * the global rate limits ``m_settings``. + * the DHT state ``m_dht``. + * the port mapping state, ``m_upnp`` and ``m_natpmp``. + +session +------- + +This is the public interface to the session. It implements pimpl (pointer to implementation) +in order to hide the internal representation of the ``session_impl`` object from the user and +make binary compatibility simpler to maintain. + +torrent_handle +-------------- + +This is the public interface to a ``torrent``. It holds a weak reference to the internal +``torrent`` object and manipulates it by sending messages to the network thread. + +torrent +------- + +peer_connection +--------------- + +peer_list +--------- + +piece_picker +------------ + +torrent_info +------------ + +threads +======= + +libtorrent starts at least 3 threads, but likely more, depending on the +settings_pack::aio_threads setting. The kinds of threads are: + + * The main network thread that manages all sockets; + sending and receiving messages and maintaining all session, torrent and peer + state. In an idle session, this thread will mostly be blocked in a system call, + waiting for socket activity, such as ``epoll()``. + + * A disk I/O thread. There may be multiple disk threads. All disk read and + write operations are passed to this thread and messages are passed back to + the main thread when the operation completes. This kind of thread also performs + the SHA-1/SHA-256 calculations to verify pieces. Some disk threads may have an + affinity for those jobs, to avoid starvation of the disk. + + * At least one thread is spawned by boost.asio on systems that don't support + asynchronous host name resolution, in order to simulate non-blocking ``getaddrinfo()``. + diff --git a/docs/header.rst b/docs/header.rst new file mode 100644 index 0000000..db4711f --- /dev/null +++ b/docs/header.rst @@ -0,0 +1 @@ +:Version: 2.0.11 diff --git a/docs/hunspell/en_US.aff b/docs/hunspell/en_US.aff new file mode 100644 index 0000000..bb2fcbc --- /dev/null +++ b/docs/hunspell/en_US.aff @@ -0,0 +1,466 @@ +SET ISO8859-1 +KEY qwertyuiop|asdfghjkl|zxcvbnm +TRY esianrtolcdugmphbyfvkwzESIANRTOLCDUGMPHBYFVKWZ'- +NOSUGGEST ! + +# ordinal numbers (1st, 2nd, 3th, 11th) and decads (0s, 10s, 1990s) +COMPOUNDMIN 1 +# only in compounds: 1th, 2th, 3th +ONLYINCOMPOUND c +# compound rules: +# 1. [0-9]*1[0-9]th (10th, 11th, 12th, 56714th, etc.) +# 2. [0-9]*[02-9](1st|2nd|3rd|[4-9]th) (21st, 22nd, 123rd, 1234th, etc.) +COMPOUNDRULE 2 +COMPOUNDRULE n*1t +COMPOUNDRULE n*mp +WORDCHARS 0123456789' + +PFX A Y 1 +PFX A 0 re . + +PFX I Y 1 +PFX I 0 in . + +PFX U Y 1 +PFX U 0 un . + +PFX C Y 1 +PFX C 0 de . + +PFX E Y 1 +PFX E 0 dis . + +PFX F Y 1 +PFX F 0 con . + +PFX K Y 1 +PFX K 0 pro . + +SFX V N 2 +SFX V e ive e +SFX V 0 ive [^e] + +SFX N Y 3 +SFX N e ion e +SFX N y ication y +SFX N 0 en [^ey] + +SFX X Y 3 +SFX X e ions e +SFX X y ications y +SFX X 0 ens [^ey] + +SFX H N 2 +SFX H y ieth y +SFX H 0 th [^y] + +SFX Y Y 1 +SFX Y 0 ly . + +SFX G Y 2 +SFX G e ing e +SFX G 0 ing [^e] + +SFX J Y 2 +SFX J e ings e +SFX J 0 ings [^e] + +SFX D Y 4 +SFX D 0 d e +SFX D y ied [^aeiou]y +SFX D 0 ed [^ey] +SFX D 0 ed [aeiou]y + +SFX T N 4 +SFX T 0 st e +SFX T y iest [^aeiou]y +SFX T 0 est [aeiou]y +SFX T 0 est [^ey] + +SFX R Y 4 +SFX R 0 r e +SFX R y ier [^aeiou]y +SFX R 0 er [aeiou]y +SFX R 0 er [^ey] + +SFX Z Y 4 +SFX Z 0 rs e +SFX Z y iers [^aeiou]y +SFX Z 0 ers [aeiou]y +SFX Z 0 ers [^ey] + +SFX S Y 4 +SFX S y ies [^aeiou]y +SFX S 0 s [aeiou]y +SFX S 0 es [sxzh] +SFX S 0 s [^sxzhy] + +SFX P Y 3 +SFX P y iness [^aeiou]y +SFX P 0 ness [aeiou]y +SFX P 0 ness [^y] + +SFX M Y 1 +SFX M 0 's . + +SFX B Y 3 +SFX B 0 able [^aeiou] +SFX B 0 able ee +SFX B e able [^aeiou]e + +SFX L Y 1 +SFX L 0 ment . + +REP 97 +REP nt n't +REP alot a_lot +REP avengence a_vengeance +REP ninties 1990s +REP teached taught +REP rised rose +REP a ei +REP ei a +REP a ey +REP ey a +REP ai ie +REP ie ai +REP are air +REP are ear +REP are eir +REP air are +REP air ere +REP ere air +REP ere ear +REP ere eir +REP ear are +REP ear air +REP ear ere +REP eir are +REP eir ere +REP ch te +REP te ch +REP ch ti +REP ti ch +REP ch tu +REP tu ch +REP ch s +REP s ch +REP ch k +REP k ch +REP f ph +REP ph f +REP gh f +REP f gh +REP i igh +REP igh i +REP i uy +REP uy i +REP i ee +REP ee i +REP j di +REP di j +REP j gg +REP gg j +REP j ge +REP ge j +REP s ti +REP ti s +REP s ci +REP ci s +REP k cc +REP cc k +REP k qu +REP qu k +REP kw qu +REP o eau +REP eau o +REP o ew +REP ew o +REP oo ew +REP ew oo +REP ew ui +REP ui ew +REP oo ui +REP ui oo +REP ew u +REP u ew +REP oo u +REP u oo +REP u oe +REP oe u +REP u ieu +REP ieu u +REP ue ew +REP ew ue +REP uff ough +REP oo ieu +REP ieu oo +REP ier ear +REP ear ier +REP ear air +REP air ear +REP w qu +REP qu w +REP z ss +REP ss z +REP shun tion +REP shun sion +REP shun cion +REP tion ssion +REP ys ies +REP u ough + +# PHONEtic_english.h - #PHONEtic transformation rules for use with #PHONEtic.c +# Copyright (C) 2000 Björn Jacke +# +# This rule set is based on Lawrence Phillips original metaPHONE +# algorithm with modifications made by Michael Kuhn in his +# C implantation, more modifications by Björn Jacke when +# converting the algorithm to a rule set and minor +# touch ups by Kevin Atkinson +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License version 2.1 as published by the Free Software Foundation; +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Björn Jacke may be reached by email at bjoern.jacke@gmx.de +# +# Changelog: +# +# 2000-01-05 Björn Jacke +# - first version with translation rules derived from +# metaPHONE.cc distributed with aspell 0.28.3 +# - "TH" is now representated as "@" because "0" is a +# meta character +# - removed TH(!vowel) --> T; always use TH --> # instead +# - dropped "^AE" -> "E" (redundant) +# - "ing" is transformed to "N", not "NK" +# - "SCH(EO)" transforms to "SK" now +# - added R --> SILENT if (after a vowel) and no (vowel or +# "y" follows) like in "Marcy" or "abort" +# - H is SILENT in RH at beginning of words +# - H is SILENT if vowel leads and "Y" follows +# - some ".OUGH.." --> ...F exceptions added +# - "^V" transforms to "W" +# 2000-01-07 Kevin Atkinson +# Converted from header to data file. +# +# 2007-08-23 László Németh +# Add PHONE header and #PHONE keywords +# +# version 1.1 + +# Documentation: http://aspell.net/man-html/PHONEtic-Code.html + +PHONE 105 +PHONE AH(AEIOUY)-^ *H +PHONE AR(AEIOUY)-^ *R +PHONE A(HR)^ * +PHONE A^ * +PHONE AH(AEIOUY)- H +PHONE AR(AEIOUY)- R +PHONE A(HR) _ +PHONE BB- _ +PHONE B B +PHONE CQ- _ +PHONE CIA X +PHONE CH X +PHONE C(EIY)- S +PHONE CK K +PHONE COUGH^ KF +PHONE CC< C +PHONE C K +PHONE DG(EIY) K +PHONE DD- _ +PHONE D T +PHONE É< E +PHONE EH(AEIOUY)-^ *H +PHONE ER(AEIOUY)-^ *R +PHONE E(HR)^ * +PHONE ENOUGH^$ *NF +PHONE E^ * +PHONE EH(AEIOUY)- H +PHONE ER(AEIOUY)- R +PHONE E(HR) _ +PHONE FF- _ +PHONE F F +PHONE GN^ N +PHONE GN$ N +PHONE GNS$ NS +PHONE GNED$ N +PHONE GH(AEIOUY)- K +PHONE GH _ +PHONE GG9 K +PHONE G K +PHONE H H +PHONE IH(AEIOUY)-^ *H +PHONE IR(AEIOUY)-^ *R +PHONE I(HR)^ * +PHONE I^ * +PHONE ING6 N +PHONE IH(AEIOUY)- H +PHONE IR(AEIOUY)- R +PHONE I(HR) _ +PHONE J K +PHONE KN^ N +PHONE KK- _ +PHONE K K +PHONE LAUGH^ LF +PHONE LL- _ +PHONE L L +PHONE MB$ M +PHONE MM M +PHONE M M +PHONE NN- _ +PHONE N N +PHONE OH(AEIOUY)-^ *H +PHONE OR(AEIOUY)-^ *R +PHONE O(HR)^ * +PHONE O^ * +PHONE OH(AEIOUY)- H +PHONE OR(AEIOUY)- R +PHONE O(HR) _ +PHONE PH F +PHONE PN^ N +PHONE PP- _ +PHONE P P +PHONE Q K +PHONE RH^ R +PHONE ROUGH^ RF +PHONE RR- _ +PHONE R R +PHONE SCH(EOU)- SK +PHONE SC(IEY)- S +PHONE SH X +PHONE SI(AO)- X +PHONE SS- _ +PHONE S S +PHONE TI(AO)- X +PHONE TH @ +PHONE TCH-- _ +PHONE TOUGH^ TF +PHONE TT- _ +PHONE T T +PHONE UH(AEIOUY)-^ *H +PHONE UR(AEIOUY)-^ *R +PHONE U(HR)^ * +PHONE U^ * +PHONE UH(AEIOUY)- H +PHONE UR(AEIOUY)- R +PHONE U(HR) _ +PHONE V^ W +PHONE V F +PHONE WR^ R +PHONE WH^ W +PHONE W(AEIOU)- W +PHONE X^ S +PHONE X KS +PHONE Y(AEIOU)- Y +PHONE ZZ- _ +PHONE Z S + +#The rules in a different view: +# +# Exceptions: +# +# Beginning of word: "gn", "kn-", "pn-", "wr-" ----> drop first letter +# "Aebersold", "Gnagy", "Knuth", "Pniewski", "Wright" +# +# Beginning of word: "x" ----> change to "s" +# as in "Deng Xiaopeng" +# +# Beginning of word: "wh-" ----> change to "w" +# as in "Whalen" +# Beginning of word: leading vowels are transformed to "*" +# +# "[crt]ough" and "enough" are handled separately because of "F" sound +# +# +# A --> A at beginning +# _ otherwise +# +# B --> B unless at the end of word after "m", as in "dumb", "McComb" +# +# C --> X (sh) if "-cia-" or "-ch-" +# S if "-ci-", "-ce-", or "-cy-" +# SILENT if "-sci-", "-sce-", or "-scy-", or "-cq-" +# K otherwise, including in "-sch-" +# +# D --> K if in "-dge-", "-dgy-", or "-dgi-" +# T otherwise +# +# E --> A at beginnig +# _ SILENT otherwise +# +# F --> F +# +# G --> SILENT if in "-gh-" and not at end or before a vowel +# in "-gn" or "-gned" or "-gns" +# in "-dge-" etc., as in above rule +# K if before "i", or "e", or "y" if not double "gg" +# +# K otherwise (incl. "GG"!) +# +# H --> SILENT if after vowel and no vowel or "Y" follows +# or after "-ch-", "-sh-", "-ph-", "-th-", "-gh-" +# or after "rh-" at beginning +# H otherwise +# +# I --> A at beginning +# _ SILENT otherwise +# +# J --> K +# +# K --> SILENT if after "c" +# K otherwise +# +# L --> L +# +# M --> M +# +# N --> N +# +# O --> A at beginning +# _ SILENT otherwise +# +# P --> F if before "h" +# P otherwise +# +# Q --> K +# +# R --> SILENT if after vowel and no vowel or "Y" follows +# R otherwise +# +# S --> X (sh) if before "h" or in "-sio-" or "-sia-" +# SK if followed by "ch(eo)" (SCH(EO)) +# S otherwise +# +# T --> X (sh) if "-tia-" or "-tio-" +# 0 (th) if before "h" +# silent if in "-tch-" +# T otherwise +# +# U --> A at beginning +# _ SILENT otherwise +# +# V --> V if first letter of word +# F otherwise +# +# W --> SILENT if not followed by a vowel +# W if followed by a vowel +# +# X --> KS +# +# Y --> SILENT if not followed by a vowel +# Y if followed by a vowel +# +# Z --> S diff --git a/docs/hunspell/en_US.dic b/docs/hunspell/en_US.dic new file mode 100644 index 0000000..d3e620e --- /dev/null +++ b/docs/hunspell/en_US.dic @@ -0,0 +1,62155 @@ +62154 +0/nm +0s/pt +0th/pt +1/n1 +1990s +1st/p +1th/tc +2/nm +2nd/p +2th/tc +3/nm +3rd/p +3th/tc +4/nm +4th/pt +5/nm +5th/pt +6/nm +6th/pt +7/nm +7th/pt +8/nm +8th/pt +9/nm +9th/pt +A +A's +AA +AAA +AB +ABC/M +ABM/S +ABS +AC +ACLU +ACM +ACT +ACTH +AD +ADC +ADP +AF +AFAIK +AFB +AFC +AFDC +AI +AIDS +AK +AL +ALGOL +ALU +AM +AMA +ANSI/M +AOL/M +AP +APB +APO +APR +AR +ARC +ARCO/M +ARPA/M +ARPANET/M +ASAP +ASCII +ASL +ASPCA +ATM/M +ATP +ATV/S +AV +AWACS +AWOL +AZ +AZT +Aachen/M +Aaren/M +Aarhus/M +Aarika/M +Aaron/M +Ab/M +Abagael/M +Abagail/M +Abba/M +Abbe/M +Abbey/M +Abbi/M +Abbie/M +Abbot/M +Abbott/M +Abby/M +Abbye/M +Abdel/M +Abdul/M +Abe/M +Abel/M +Abelard/M +Abelson/M +Aberdeen/M +Abernathy/M +Abeu/M +Abey/M +Abidjan/M +Abie/M +Abigael/M +Abigail/M +Abigale/M +Abilene/M +Abner/M +Abo/SM! +Aborigine/SM +Abra/M +Abraham/M +Abrahan/M +Abram/SM +Abramo/M +Abramson/M +Abran/M +Abuja +Abyssinia/M +Abyssinian +Ac/M +Acadia/M +Acapulco/M +Accra/M +Acevedo/M +Achaean/M +Achebe/M +Achernar/M +Acheson/M +Achilles +Ackerman/M +Aconcagua/M +Acosta/M +Acropolis/M +Acrux/M +Acta/M +Actaeon/M +Acton/M +Acts +Ad/MN +Ada/M +Adah/M +Adair/M +Adaline/M +Adam/SM +Adamo/M +Adamson/M +Adan/M +Adana/M +Adara/M +Adda/M +Addams +Addi/M +Addia/M +Addie/M +Addison/M +Addressograph/M +Addy/M +Ade/M +Adel/M +Adela/M +Adelaida/M +Adelaide/M +Adelbert/M +Adele/M +Adelheid/M +Adelice/M +Adelina/M +Adelind/M +Adeline/M +Adella/M +Adelle/M +Aden/M +Adena/M +Adenauer/M +Adey/M +Adham/M +Adhara/M +Adi/M +Adiana/M +Adidas/M +Adina/M +Adirondack/SM +Adkins/M +Adlai/M +Adler/M +Adm/M +Admiralty/S +Ado/M +Adolf/M +Adolfo/M +Adolph/M +Adolphe/M +Adolpho/M +Adolphus/M +Adonis/SM +Adora/M +Adore/M +Adoree/M +Adorne/M +Adrea/M +Adrenalin/MS +Adria/MX +Adrian/M +Adriana/M +Adriane/M +Adrianna/M +Adrianne/M +Adriano/M +Adriatic +Adrien/M +Adriena/M +Adrienne/M +Advent/SM +Adventist/M +Advil/M +Aegean +Aelfric/M +Aeneas +Aeneid/M +Aeolus/M +Aeriel/M +Aeriela/M +Aeriell/M +Aeschylus/M +Aesculapius/M +Aesop/M +Afghan/SM +Afghani/SM +Afghanistan/M +Afr +Africa/M +African/MS +Afrikaans/M +Afrikaner/SM +Afro/MS +Afrocentric +Afrocentrism/S +Afton/M +Ag/M +Agace/M +Agamemnon/M +Agassiz/M +Agata/M +Agatha/M +Agathe/M +Aggi/M +Aggie/M +Aggy/SM +Aglaia/M +Agna/M +Agnella/M +Agnes/M +Agnese/M +Agnesse/M +Agneta/M +Agnew/M +Agni/M +Agnola/M +Agosto/M +Agra/M +Agretha/M +Agricola/M +Agrippa/M +Agrippina/M +Aguascalientes/M +Aguie/M +Aguilar/M +Aguinaldo/M +Aguirre/M +Aguistin/M +Aguste/M +Agustin/M +Ahab/M +Aharon/M +Ahmad/M +Ahmadabad +Ahmed/M +Ahriman/M +Aida/M +Aidan/M +Aigneis/M +Aiken/M +Aila/M +Ailbert/M +Aile/M +Ailee/M +Aileen/M +Ailene/M +Ailey/M +Aili/SM +Ailina/M +Ailsun/M +Ailyn/M +Aime/M +Aimee/M +Aimil/M +Aindrea/M +Ainslee/M +Ainsley/M +Ainslie/M +Ainu/M +Airbus/M +Airedale/SM +Aires +Aisha/M +Ajax/M +Ajay/M +Akbar/M +Akihito/M +Akim/M +Akita/M +Akkad/M +Akron/M +Aksel/M +Al/MRY +Ala/MS +Alabama/M +Alabaman/S +Alabamian/MS +Aladdin/M +Alain/M +Alaine/M +Alair/M +Alameda/M +Alamo/SM +Alamogordo/M +Alan/M +Alana/M +Alanah/M +Aland/M +Alane/M +Alanna/M +Alano/M +Alanson/M +Alar/M +Alard/M +Alaric/M +Alasdair/M +Alaska/M +Alaskan/S +Alastair/M +Alasteir/M +Alaster/M +Alayne/M +Alba/M +Albania/M +Albanian/SM +Albany/M +Albee/M +Alberich/M +Alberik/M +Alberio/M +Albert/M +Alberta/M +Albertan/S +Albertina/M +Albertine/M +Alberto/M +Albie/M +Albigensian +Albina/M +Albion/M +Albireo/M +Albrecht/M +Albuquerque/M +Alcatraz/M +Alcestis/M +Alcibiades/M +Alcmena/M +Alcoa/M +Alcott/M +Alcuin/M +Alcyone/M +Aldan/M +Aldebaran/M +Alden/M +Alderamin/M +Aldin/M +Aldis/M +Aldo/M +Aldon/M +Aldous/M +Aldric/M +Aldrich/M +Aldridge/M +Aldrin/M +Aldus/M +Aldwin/M +Alec/M +Alecia/M +Aleck/M +Aleda/M +Aleece/M +Aleen/M +Aleichem/M +Alejandra/M +Alejandrina/M +Alejandro/M +Alejoa/M +Aleksandr/M +Alembert/M +Alena/M +Alene/M +Aleppo/M +Aler/M +Alessandra/M +Alessandro/M +Aleta/M +Alethea/M +Aleut/SM +Aleutian/S +Alex/M +Alexa/M +Alexander/SM +Alexandr/M +Alexandra/M +Alexandre/M +Alexandria/M +Alexandrian/S +Alexandrina/M +Alexandro/MS +Alexei/M +Alexi/SM +Alexia/M +Alexina/M +Alexine/M +Alexio/M +Alf/M +Alfa/M +Alfi/M +Alfie/M +Alfons/M +Alfonse/M +Alfonso/M +Alfonzo/M +Alford/M +Alfred/M +Alfreda/M +Alfredo/M +Alfy/M +Algenib/M +Alger/M +Algeria/M +Algerian/MS +Algernon/M +Algieba/M +Algiers/M +Algol/M +Algonquian/SM +Algonquin/SM +Alhambra/M +Alhena/M +Ali/MS +Alia/M +Alic/M +Alica/M +Alice/M +Alicea/M +Alicia/M +Alick/M +Alida/M +Alidia/M +Alie/M +Alighieri/M +Alika/M +Alikee/M +Alina/M +Aline/M +Alioth/M +Alisa/M +Alisander/M +Alisha/M +Alison/M +Alissa/M +Alistair/M +Alister/M +Alisun/M +Alix/M +Aliza/M +Alkaid/M +Alla/M +Allah/M +Allahabad/M +Allan/M +Allard/M +Allayne/M +Alleen/M +Allegheny/MS +Allegra/M +Allen/M +Allendale/M +Allende/M +Allene/M +Allentown/M +Alley/M +Alleyn/M +Allhallows +Alli/MS +Allianora/M +Allie/M +Allin/M +Allina/M +Allison/M +Allissa/M +Allister/M +Allistir/M +Allix/M +Allstate/M +Allsun/M +Allx/M +Ally/MS +Allyce/M +Allyn/M +Allys +Allyson/M +Alma/M +Almach/M +Almaden/M +Almaty/M +Almeda/M +Almeria/M +Almeta/M +Almighty/M +Almira/M +Almire/M +Alnilam/M +Alnitak/M +Aloin/M +Aloise/M +Aloisia/M +Alon/M +Alonso/M +Alonzo/M +Aloysia/M +Aloysius/M +Alpert/M +Alphard/M +Alphecca/M +Alpheratz/M +Alphonse/M +Alphonso/M +Alpine +Alps +Alric/M +Alsace/M +Alsatian/MS +Alsop/M +Alston/M +Alta/M +Altai/M +Altaic/M +Altair/M +Althea/M +Altiplano/M +Alton/M +Altos/M +Aludra/M +Aluin/M +Aluino/M +Alva/M +Alvan/M +Alvarado/M +Alvarez/M +Alvaro/M +Alvera/M +Alverta/M +Alvie/M +Alvin/M +Alvina/M +Alvinia/M +Alvira/M +Alvis/M +Alvy/M +Alwin/M +Alwyn/M +Alyce/M +Alyda/M +Alyosha/M +Alys/M +Alysa/M +Alyse/M +Alysia/M +Alyson/M +Alyss +Alyssa/M +Alzheimer/M +Am/MR +Amabel/M +Amabelle/M +Amadeus/M +Amado/M +Amalea/M +Amalee/M +Amaleta/M +Amalia/M +Amalie/M +Amalita/M +Amalle/M +Amanda/M +Amandi/M +Amandie/M +Amandy/M +Amara/M +Amargo/M +Amarillo/M +Amata/M +Amati/M +Amazon/SM +Amazonian +Amber/YM +Amberly/M +Amble/M +Ambros/M +Ambrose/M +Ambrosi/M +Ambrosio/M +Ambrosius/M +Ambur/M +Amby/M +Amdahl/M +Ame/SM +Amelia/M +Amelie/M +Amelina/M +Ameline/M +Amelita/M +Amenhotep/M +Amer/M +Amerada/M +Amerasian/S +America/SM +American/MS +Americana/M +Americanism/SM +Americanization/SM +Americanize/SDG +Amerigo/M +Amerind/MS +Amerindian/MS +Amery/M +Ameslan/M +Amharic/M +Amherst/M +Ami/M +Amie/M +Amiga/M +Amii/M +Amil/M +Amish +Amitie/M +Amity/M +Ammamaria/M +Amman/M +Ammerman/M +Amoco/M +Amontillado/M +Amory/M +Amos +Amparo/M +Ampere/M +Ampex/M +Amritsar/M +Amsterdam/M +Amtrak/M +Amundsen/M +Amur/M +Amway/M +Amy/M +Amye/M +Ana/M +Anabal/M +Anabaptist/SM +Anabel/M +Anabella/M +Anabelle/M +Anacin/M +Anacreon/M +Anaheim/M +Analects/M +Analiese/M +Analise/M +Anallese/M +Anallise/M +Ananias/M +Anastasia/M +Anastasie/M +Anastassia/M +Anatol/M +Anatola/M +Anatole/M +Anatolia/M +Anatolian +Anatollo/M +Anaxagoras/M +Ancell/M +Anchorage/M +Andalusia/M +Andalusian +Andaman +Andean/M +Andee/M +Andeee/M +Anderea/M +Anders/N +Andersen/M +Anderson/M +Andes +Andi/M +Andie/M +Andonis/M +Andorra/M +Andover/M +Andra/SM +Andre/SM +Andrea/MS +Andreana/M +Andree/M +Andrei/M +Andrej/M +Andrew/MS +Andrey/M +Andria/M +Andriana/M +Andriette/M +Andris +Andromache/M +Andromeda/M +Andropov/M +Andros/M +Andrus/M +Andy/M +Anestassia/M +Anet/M +Anett/M +Anetta/M +Anette/M +Angara/M +Ange/M +Angel/M +Angela/M +Angele/SM +Angeleno/SM +Angeli/M +Angelia/M +Angelica/M +Angelico/M +Angelika/M +Angelina/M +Angeline/M +Angelique/M +Angelita/M +Angelle/M +Angelo/M +Angelou/M +Angevin/M +Angie/M +Angil/M +Angkor/M +Angles +Anglia/M +Anglican/MS +Anglicanism/MS +Anglicism/SM +Anglicization/MS +Anglicize/SDG +Anglo/MS +Anglophile/SM +Anglophilia/M +Anglophobe/MS +Anglophobia/M +Angola/M +Angolan/S +Angora/MS +Anguilla/M +Angus/M +Angy/M +Anheuser/M +Ania/M +Aniakchak/M +Anibal/M +Anica/M +Anissa/M +Anita/M +Anitra/M +Anjanette/M +Anjela/M +Ankara/M +Ann/M +Anna/M +Annabal/M +Annabel/M +Annabela/M +Annabell/M +Annabella/M +Annabelle/M +Annadiana/M +Annadiane/M +Annalee/M +Annaliese/M +Annalise/M +Annamaria/M +Annamarie/M +Annapolis/M +Annapurna/M +Anne/M +Annecorinne/M +Anneliese/M +Annelise/M +Annemarie/M +Annetta/M +Annette/M +Anni/MS +Annice/M +Annie/M +Annissa/M +Annmaria/M +Annmarie/M +Annnora/M +Annora/M +Annunciation/S +Anny/M +Anouilh/M +Ansel/M +Ansell/M +Anselm/M +Anselma/M +Anselmo/M +Anshan/M +Ansley/M +Anson/M +Anstice/M +Antaeus/M +Antananarivo/M +Antarctic/M +Antarctica/M +Antares +Anthe/M +Anthea/M +Anthia/M +Anthiathia/M +Anthony/M +Antichrist/MS +Antietam/M +Antigone/M +Antigua/M +Antillean +Antilles +Antin/M +Antioch/M +Antipas/M +Antipodes +Antofagasta/M +Antoine/M +Antoinette/M +Anton/MS +Antone/M +Antonella/M +Antonetta/M +Antoni/M +Antonia/M +Antonie/M +Antonietta/M +Antonin/M +Antonina/M +Antonino/M +Antoninus/M +Antonio/M +Antonius/M +Antonovics/M +Antony/M +Antwan/M +Antwerp/M +Anubis/M +Any/M +Anya/M +Apache/MS +Apalachicola/M +Apennines +Aphrodite/M +Apia/M +Apocalypse/M +Apocrypha/M +Apollinaire/M +Apollo/SM +Apollonian +Appalachia/M +Appalachian/MS +Appaloosa/MS +Appia/M +Appian/M +Apple/M +Appleseed/M +Appleton/M +Appolonia/M +Appomattox/M +Apr/M +April/MS +Aprilette/M +Apuleius/M +Aquarius/MS +Aquila/M +Aquinas/M +Aquino/M +Aquitaine/M +Ar/MY +Ara/M +Arab/MS +Arabel/M +Arabela/M +Arabele/M +Arabella/M +Arabelle/M +Arabia/M +Arabian/MS +Arabic/M +Arabist/MS +Araby/M +Araceli/M +Arafat/M +Araguaya/M +Aral/M +Araldo/M +Aramaic/M +Aramco/M +Arapaho/MS +Arapahoe's +Arapahoes +Ararat/M +Araucanian/M +Arawak/M +Arawakan/M +Arcadia/M +Arcadian +Arch/MR +Archaimbaud/M +Archambault/M +Archean +Archer/M +Archibald/M +Archibaldo/M +Archibold/M +Archie/M +Archimedes/M +Archy/M +Arctic/M +Arcturus/M +Arda/MH +Ardabil +Ardath/M +Ardeen/M +Ardelia/M +Ardelis/M +Ardella/M +Ardelle/M +Arden/M +Ardene/M +Ardenia/M +Ardine/M +Ardis/M +Ardisj/M +Ardith/M +Ardra/M +Ardyce/M +Ardys +Ardyth/M +Arel/M +Arequipa/M +Ares +Aretha/M +Argentina/M +Argentine/SM +Argentinean/S +Argentinian/S +Argo/SM +Argonaut/MS +Argonne/M +Argus/M +Ari/M +Ariadne/M +Ariana/M +Arianism/M +Arianist/SM +Aridatha/M +Arie/SM +Ariel/M +Ariela/M +Ariella/M +Arielle/M +Aries/S +Arin/M +Ario/M +Ariosto/M +Aristarchus/M +Aristides +Aristophanes/M +Aristotelean +Aristotelian/M +Aristotle/M +Arius/M +Ariz/M +Arizona/M +Arizonan/S +Arizonian/S +Arjuna/M +Ark/M +Arkansan/MS +Arkansas/M +Arkhangelsk/M +Arkwright/M +Arlan/M +Arlana/M +Arlee/M +Arleen/M +Arlen/M +Arlena/M +Arlene/M +Arleta/M +Arlette/M +Arley/M +Arleyne/M +Arlie/M +Arliene/M +Arlin/M +Arlina/M +Arlinda/M +Arline/M +Arlington/M +Arluene/M +Arly/M +Arlyn/M +Arlyne/M +Armada/M +Armageddon/SM +Armagnac/M +Arman/M +Armand/M +Armando/M +Armata/M +Armco/M +Armenia/M +Armenian/MS +Armin/M +Arminius/M +Armonk/M +Armour/M +Armstrong/M +Arnaldo/M +Arne/M +Arneb/M +Arney/M +Arnhem/M +Arni/M +Arnie/M +Arno/M +Arnold/M +Arnoldo/M +Arnuad/M +Arnulfo/M +Arny/M +Aron/M +Arpanet/M +Arragon/M +Arrhenius/M +Arri/M +Arron/M +Art/M +Artair/M +Artaxerxes/M +Arte/M +Artemas +Artemis/M +Artemus/M +Arther/M +Arthur/M +Arthurian +Artie/M +Artur/M +Arturo/M +Artus/M +Arty/M +Aruba/M +Arv/M +Arvie/M +Arvin/M +Arvy/M +Aryan/MS +Aryn/M +As +Asa/M +Asama/M +Ascella/M +Ascension/M +Ase/M +Asgard/M +Ash/MRY +Ashanti/M +Ashbey/M +Ashby/M +Ashely/M +Asher/M +Asheville/M +Ashia/M +Ashien/M +Ashil/M +Ashkenazim +Ashkhabad/M +Ashla/M +Ashlan/M +Ashland/M +Ashlee/M +Ashleigh/M +Ashlen/M +Ashley/M +Ashli/M +Ashlie/M +Ashlin/M +Ashly/M +Ashmolean/M +Ashton/M +Ashurbanipal/M +Asia/M +Asian/MS +Asiatic/SM +Asilomar/M +Asimov +Asmara/M +Asoka/M +Aspell/M +Aspen/M +Aspidiske/M +Asquith/M +Assad/M +Assam/M +Assamese/M +Assembly/MS +Assisi/M +Assyria/M +Assyrian/SM +Assyriology/M +Astaire/SM +Astarte/M +Aston/M +Astor/M +Astoria/M +Astra/M +Astrakhan/M +Astrid/M +Astrix/M +AstroTurf/S +Astroturf/M +Asturias/M +Asunción/M +Aswan/M +At/M +Atacama/M +Atahualpa/M +Atalanta/M +Atari/M +Atatürk/M +Athabasca/M +Athabascan's +Athabaska's +Athabaskan/MS +Athena/M +Athene/M +Athenian/SM +Athens/M +Atkins/M +Atkinson/M +Atlanta/M +Atlante/MS +Atlantic/M +Atlantis/M +Atlas/SM +Atman +Atreus/M +Atria/M +Atropos/M +Ats +Attic +Attica/M +Attila/M +Attlee/M +Attn +Attucks +Atwood/M +Au/M +Aube/M +Auberge/M +Auberon/M +Aubert/M +Auberta/M +Aubine/M +Aubree/M +Aubrette/M +Aubrey/M +Aubrie/M +Aubry/M +Auckland/M +Auden/M +Audi/M +Audie/M +Audra/M +Audre/M +Audrey/M +Audrie/M +Audry/M +Audrye/M +Audubon/M +Audy/M +Auerbach/M +Aug/M +Augean +Augie/M +August/SM +Augusta/M +Augustan/S +Auguste/M +Augustin/M +Augustina/M +Augustine/M +Augustinian/S +Augusto/M +Augustus/M +Augy/M +Aundrea/M +Aura/M +Aurea/M +Aurel/M +Aurelea/M +Aurelia/M +Aurelie/M +Aurelio/M +Aurelius/M +Aureomycin/M +Auria/M +Aurie/M +Auriga/M +Aurilia/M +Aurlie/M +Auroora/M +Aurora/M +Aurore/M +Aurthur/M +Auschwitz/M +Aussie/MS +Austen/M +Austin/SM +Austina/M +Austine/M +Australasia/M +Australasian/S +Australia/M +Australian/MS +Australis/M +Australoid +Australopithecus/M +Austria/M +Austrian/SM +Austronesian +Autumn/M +Ava/M +Avalon/M +Ave/MS +Aveline/M +Aventine/M +Aventino/M +Averell/M +Averil/M +Averill/M +Avernus/M +Averroes/M +Avery/M +Averyl/M +Avesta/M +Avicenna/M +Avictor/M +Avie/M +Avigdor/M +Avignon/M +Avila/M +Avior/M +Avis +Aviv/M +Aviva/M +Avivah/M +Avogadro/M +Avon/M +Avram/M +Avril/M +Avrit/M +Avrom/M +Ax/M +Axe/M +Axel/M +Ayala/M +Ayers +Aylmar/M +Aylmer/M +Aymara/M +Aymer/M +Ayn/M +Azania/M +Azazel/M +Azerbaijan/M +Azores +Azov/M +Aztec/MS +Aztecan +B's +B/GT +BA +BASIC +BB +BBB +BBC +BBQ +BBS +BC +BCD +BIOS +BITNET/M +BLT +BM +BMW/M +BO +BR +BS +BSA +BSD +BTU +BTW +BYOB +Ba/M +Baal/SM +Bab/SM +Babar's +Babara/M +Babb/M +Babbage/M +Babbette/M +Babbie/M +Babbitt/M +Babcock/M +Babel/MS +Babette/M +Babita/M +Babka/M +Babylon/MS +Babylonia/M +Babylonian/SM +Bacall/M +Bacardi/M +Bacchanalia/M +Bacchic +Bacchus/M +Bach/M +Backus/M +Bacon/M +Bactria/M +Baden/M +Badlands/M +Baedeker/SM +Baez/M +Baffin/M +Baghdad/M +Bagrodia/MS +Baguio/M +Baha'i +Baha'ullah +Bahama/MS +Bahamanian/S +Bahamian/MS +Bahia/M +Bahrain/M +Baikal/M +Bail/M +Bailey/SM +Bailie/M +Baillie/M +Baily/M +Baird/M +Baja/M +Bakelite/M +Baker/M +Bakersfield/M +Baku/M +Bakunin/M +Balanchine/M +Balboa/M +Bald/MR +Balder/M +Balduin/M +Baldwin/M +Bale/M +Balearic/M +Balfour/M +Bali/M +Balinese +Balkan/SM +Balkhash/M +Ball/M +Ballard/SM +Balthazar/M +Baltic/M +Baltimore/M +Baluchistan/M +Balzac/M +Bamako/M +Bamberger/M +Bambi/M +Bambie/M +Bamby/M +Ban/M +Banach/M +Bancroft/M +Bandung/M +Bangalore/M +Bangkok/M +Bangladesh/M +Bangladeshi/S +Bangor/M +Bangui/M +Banjarmasin/M +Banjul/M +Bank/MS +Banky/M +Banneker/M +Bannister/M +Banting/M +Bantu/SM +Baotou/M +Baptist/MS +Baptiste/M +Bar/MH +Barabbas/M +Barb/RM +Barbabas/M +Barbabra/M +Barbadian/S +Barbados/M +Barbara/M +Barbaraanne/M +Barbarella/M +Barbarossa/M +Barbary/M +Barbe/M +Barbee/M +Barber/M +Barbette/M +Barbey/M +Barbi/M +Barbie/M +Barbour/M +Barbra/M +Barbuda/M +Barby/M +Barcelona/M +Barclay/M +Bard/M +Barde/M +Bardeen/M +Barents/M +Bari/M +Barker/M +Barkley/M +Barlow/M +Barn/M +Barnabas +Barnabe/M +Barnaby/M +Barnard/M +Barnaul/M +Barnebas/M +Barnes +Barnett/M +Barney/M +Barnhard/M +Barnie/M +Barnum/M +Barny/M +Baroda/M +Baron/M +Barquisimeto/M +Barr/M +Barranquilla/M +Barrera/M +Barret/M +Barrett/M +Barri/SM +Barrie/M +Barron/M +Barry/M +Barrymore/MS +Barstow/M +Bart/M +Bartel/M +Barth/M +Barthel/M +Bartholdi/M +Bartholemy/M +Bartholomeo/M +Bartholomeus/M +Bartholomew/M +Bartie/M +Bartlet/M +Bartlett/M +Bartolemo/M +Bartolomeo/M +Barton/M +Bartram/M +Barty/M +Bartók/M +Bary/M +Baryram/M +Baryshnikov/M +Bascom/M +Base/M +Basel/M +Basho/M +Basia/M +Basie/M +Basil/M +Basile/M +Basilio/M +Basilius/M +Basque/SM +Basra/M +Bass/M +Basseterre/M +Bassett/M +Bastian/M +Bastien/M +Bastille/M +Basutoland/M +Bat/M +Bataan/M +Batavia/M +Bates +Batholomew/M +Bathsheba/M +Batista/M +Batman/M +Batsheva/M +Battle/M +Batu/M +Baudelaire/M +Baudoin/M +Baudouin/M +Bauer/M +Bauhaus/M +Bausch/M +Bavaria/M +Bavarian/S +Bax/M +Baxie/M +Baxter/M +Baxy/M +Bay/MR +Bayamon +Bayard/M +Bayda/M +Bayer/M +Bayes +Bayesian +Baylor/M +Bayonne/M +Bayreuth/M +Be/MH +Bea/M +Beach/M +Beadle/M +Beale/M +Bealle/M +Bean/M +Bear/M +Beard/M +Beardmore/M +Beardsley/M +Bearnaise/M +Bearnard/M +Beasley/M +Beatlemania/M +Beatles/M +Beatrice/M +Beatrisa/M +Beatrix/M +Beatriz/M +Beau/M +Beauchamps +Beaufort/M +Beaujolais/M +Beaumarchais/M +Beaumont/M +Beauregard/M +Beauvoir/M +Beaverton/M +Bebe/M +Becca/M +Bechtel/M +Beck/RM +Becka/M +Becker/M +Becket/M +Beckett/M +Becki/M +Beckie/M +Becky/M +Becquerel/M +Bede/M +Bedford/M +Bedouin/SM +Bee/M +Beebe/M +Beecher/M +Beelzebub/M +Beerbohm/M +Beethoven/M +Beeton/M +Begin/M +Behan/M +Behring/M +Beiderbecke/M +Beijing +Beilul/M +Beirut/M +Beitris/M +Bekesy/M +Bekki/M +Bel/M +Bela/M +Belarus +Belau/M +Belem/M +Belfast/M +Belg/M +Belgian/MS +Belgium/M +Belgrade/M +Belia/M +Belicia/M +Belinda/M +Belita/M +Belize/M +Bell/M +Bella/M +Bellamy/M +Bellanca/M +Bellatrix/M +Belle/M +Belleville/M +Bellina/M +Bellini/M +Bellovin/M +Bellow/M +Bellwood/M +Belmont/M +Belmopan/M +Beloit/M +Belorussia's +Belorussian/S +Belshazzar/M +Belton/M +Beltran/M +Beltsville/M +Belushi/M +Belva/M +Belvia/M +Ben/M +Benacerraf/M +Benares's +Bender/M +Bendick/M +Bendicty/M +Bendite/M +Bendix/M +Benedetta/M +Benedetto/M +Benedick/M +Benedict/M +Benedicta/M +Benedictine/MS +Benedicto/M +Benedikt/M +Benedikta/M +Benelux/M +Benet/M +Benetta/M +Benetton/M +Bengal/SM +Bengali/M +Benghazi/M +Bengt/M +Beniamino/M +Benin/M +Beninese +Benita/M +Benito/M +Benjamen/M +Benjamin/M +Benji/M +Benjie/M +Benjy/M +Benn/M +Bennett/M +Benni/M +Bennie/M +Bennington/M +Benny/M +Benoit/M +Benoite/M +Benson/M +Bent/M +Bentham/M +Bentlee/M +Bentley/MS +Benton/M +Benyamin/M +Benz/M +Benzedrine/M +Beograd's +Beowulf/M +Ber/MG +Berber/MS +Berenice/M +Beret/M +Berg/NRM +Bergen/M +Berger/M +Bergerac/M +Berget/M +Berglund/M +Bergman/M +Bergson/M +Bergsten/M +Bergstrom/M +Bering/RM +Beringer/M +Berk/YM +Berke/M +Berkeley/M +Berkie/M +Berkley/M +Berkly/M +Berkowitz/M +Berkshire/SM +Berky/M +Berle/M +Berlin/SZRM +Berliner/M +Berlioz/M +Berlitz/M +Berman/M +Bermuda/MS +Bermudan/S +Bermudian/S +Bern/M +Berna/M +Bernadene/M +Bernadette/M +Bernadina/M +Bernadine/M +Bernard/M +Bernardina/M +Bernardine/M +Bernardino/M +Bernardo/M +Bernarr/M +Bernays/M +Bernbach/M +Berne's +Bernelle/M +Bernese +Bernete/M +Bernetta/M +Bernette/M +Bernhard/M +Bernhardt/M +Berni/M +Bernice/M +Bernie/M +Berniece/M +Bernini/M +Bernita/M +Bernoulli/M +Bernstein/M +Berny/M +Berra/M +Berri/M +Berrie/M +Berry/M +Bert/M +Berta/M +Berte/M +Bertha/M +Berthe/M +Berti/M +Bertie/M +Bertillon/M +Bertina/M +Bertine/M +Berton/M +Bertram/M +Bertrand/M +Bertrando/M +Berty/M +Beryl/M +Beryle/M +Berzelius/M +Bess +Bessel/M +Bessemer/M +Bessie/M +Bessy/M +Best/M +Betelgeuse/M +Beth/M +Bethanne/M +Bethany/M +Bethe/M +Bethena/M +Bethesda/M +Bethina/M +Bethlehem/M +Bethune +Betsey/M +Betsy/M +Betta/M +Bette/M +Betteann/M +Betteanne/M +Betti/M +Bettie/M +Bettina/M +Bettine/M +Betty/SM +Bettye/M +Beulah/M +Bev's +Bevan/M +Beverie/M +Beverlee/M +Beverley/M +Beverlie/M +Beverly/M +Bevin/M +Bevon/M +Bevvy/M +Bhopal/M +Bhutan/M +Bhutanese +Bhutto/M +Bi/M +Bialystok/M +Bianca/M +Bianco/M +Bianka/M +Bib/M +Bibbie/M +Bibby/M +Bibbye/M +Bibi/M +Bible/MS +Biddie/M +Biddle/M +Biddy/M +Bidget/M +Bienville/M +Bierce/M +Bigelow/M +Bigfoot +Biko/M +Bil/MY +Bilbao/M +Bilbo/M +Bili/M +Bill/JM +Billi/M +Billie/M +Billings/M +Billy/M +Billye/M +Bimini/M +Bing/M +Bingham/M +Binghamton/M +Bini/M +Bink/M +Binky/M +Binni/M +Binnie/M +Binny/M +Bioko/M +Birch/M +Bird/M +Birdie/M +Birdseye/M +Birgit/M +Birgitta/M +Birk/M +Birkenstock/M +Birmingham/M +Biro/M +Biron/M +Biscay/M +Biscayne/M +Bishkek +Bishop/M +Bismarck/M +Bismark/M +Bissau/M +Bizet/M +Bjorn/M +Bk/M +Black's +Blackburn/M +Blackfeet +Blackfoot/M +Blackman/M +Blackmer/M +Blackpool/M +Blackstone/M +Blackwell/MS +Blaine/M +Blair/M +Blaire/M +Blake/M +Blakelee/M +Blakeley/M +Blakey/M +Blanca/M +Blanch/M +Blancha/M +Blanchard/M +Blanche/M +Blane/M +Blankenship/M +Blanton/M +Blantyre/M +Blatz/M +Blavatsky/M +Blayne/M +Bleeker/M +Blenheim/M +Blevins/M +Bligh/M +Blinni/M +Blinnie/M +Blinny/M +Bliss/M +Blisse/M +Blithe/M +Bloch/M +Bloemfontein/M +Blomberg/M +Blomquist/M +Blondell/M +Blondelle/M +Blondie/M +Blondy/M +Bloom/MR +Bloomer/M +Bloomfield/M +Bloomington/M +Blucher/M +Bluebeard/M +Blum/M +Blumenthal/M +Blvd +Blythe/M +Bo/MRZ +Bob/M +Bobbe/M +Bobbee/M +Bobbette/M +Bobbi/M +Bobbie/M +Bobbitt/M +Bobbsey/M +Bobby/M +Bobbye/M +Bobette/M +Bobina/M +Bobine/M +Bobinette/M +Bobrow/M +Boca/M +Boccaccio/M +Bodenheim/M +Bodhidharma/M +Bodhisattva/M +Boeing/M +Boeotia/M +Boeotian +Boer/M +Bogart/M +Bogartian/M +Bogey/M +Bogotá/M +Boheme/M +Bohemia/SM +Bohemian/SM +Bohr/M +Boigie/M +Bois/M +Boise/M +Boleyn/M +Bolivar/M +Bolivia/M +Bolivian/S +Bologna/M +Bolshevik/MS +Bolshevism/MS +Bolshevist/MS +Bolshevistic/M +Bolshoi/M +Bolton/M +Boltzmann/M +Bombay/M +Bonaparte/M +Bonaventure/M +Bond/M +Bondie/M +Bondon/M +Bondy/M +Bone/M +Bonham/M +Boniface/M +Bonita/M +Bonn/RM +Bonnee/M +Bonner/M +Bonneville/M +Bonni/M +Bonnibelle/M +Bonnie/M +Bonny/M +Bontempo/M +Booker/M +Boole/M +Boolean +Boone/M +Boonie/M +Boony/M +Boot/M +Boote/M +Booth/M +Boothe/M +Bootle/M +Bord/MN +Bordeaux/M +Borden/M +Bordie/M +Bordon/M +Bordy/M +Borealis/M +Boreas/M +Borg/M +Borges +Borgia/M +Boris +Bork/M +Born/M +Borneo/M +Borodin/M +Borroughs/M +Boru/M +Bosch/M +Bose/M +Bosnia/M +Bosnian/S +Bosporus/M +Bostitch/M +Boston/MS +Bostonian/SM +Boswell/MS +Botswana/M +Botticelli/M +Boucher/M +Boulder/M +Bourbaki/M +Bourbon/SM +Bourke/M +Bourne/M +Bournemouth/M +Bouvier +Bovary/M +Bowditch/M +Bowell/M +Bowen/M +Bowers +Bowery/M +Bowes +Bowie/M +Bowman/M +Boy/MR +Boyce/M +Boycey/M +Boycie/M +Boyd/M +Boyer/M +Boyle/M +Boötes +Br/TMN +Brad/MYN +Bradan/M +Bradbury/M +Bradburys +Braddock/M +Brade/M +Braden/M +Bradford/M +Bradley/M +Bradly/M +Bradney/M +Bradshaw/M +Bradstreet/M +Brady/M +Bragg/M +Brahe/M +Brahma/MS +Brahman/SM +Brahmanism/MS +Brahmaputra/M +Brahmin's +Brahms +Braille/GDSM +Brain/M +Brainard/SM +Bram/M +Brampton/M +Bran/M +Brana/M +Branch/M +Branchville/M +Brand/MRN +Brandais/M +Brande/M +Brandea/M +Brandeis/M +Brandel/M +Branden/M +Brandenburg/M +Brander/M +Brandi/M +Brandice/M +Brandie/M +Brandise/M +Brando/M +Brandon/M +Brandt/M +Brandtr/M +Brandy/M +Brandyn/M +Braniff/M +Brannon/M +Brant/M +Brantley/M +Braque/M +Brasilia +Bratislava/M +Brattain/M +Braun/M +Bray/M +Brazil/M +Brazilian/MS +Brazos/M +Brazzaville/M +Breanne/M +Brear/M +Breathalyzer/SM +Brecht/M +Breckenridge/M +Bree/M +Breena/M +Bremen/M +Bren/M +Brena/M +Brenda/M +Brendan/M +Brenden/M +Brendin/M +Brendis/M +Brendon/M +Brenn/RNM +Brenna/M +Brennan/M +Brennen/M +Brenner/M +Brent/M +Brenton/M +Bresenham/M +Brest/M +Bret/M +Breton +Brett/M +Brew/RM +Brewer/M +Brewster/M +Brezhnev/M +Bria/M +Brian/M +Briana/M +Brianna/M +Brianne/M +Briano/M +Briant/M +Brice/M +Bridalveil/M +Bride/M +Bridewell/M +Bridgeport/M +Bridger/M +Bridges +Bridget/M +Bridgetown/M +Bridgett/M +Bridgette/M +Bridgewater/M +Bridgman/M +Bridie/M +Brie/RSM +Brien/M +Brier/M +Brietta/M +Brig/M +Brigadoon +Brigg/MS +Brigham/M +Bright/M +Brighton/M +Brigid/M +Brigida/M +Brigit/M +Brigitta/M +Brigitte/M +Brillo +Brillouin/M +Brina/M +Brindisi/M +Briney/M +Brinkley/M +Brinn/M +Brinna/M +Briny/M +Brion/M +Brisbane/M +Bristol/M +Brit/MS +Brita/M +Britain/M +Britannia/M +Britannic +Britannica/M +Briticism/MS +British/RYZ +Britisher/M +Britishly/M +Britney/M +Britni/M +Briton/MS +Britt/MN +Britta/M +Brittan/M +Brittaney/M +Brittani/M +Brittany/MS +Britte/M +Britten/M +Britteny/M +Brittne/M +Brittney/M +Brittni/M +Brnaba/M +Brnaby/M +Brno/M +Broadway/SM +Brobdingnag/M +Brobdingnagian +Brock/M +Brockie/M +Brocky/M +Brod/M +Broddie/M +Broddy/M +Broderic/M +Broderick/M +Brodie/M +Brody/M +Broglie/M +Brok/M +Bron/M +Bronnie/M +Bronny/M +Bronson/M +Bronte +Bronx/M +Brook/SM +Brookdale/M +Brooke/M +Brookfield/M +Brookhaven/M +Brooklyn/M +Brookmont/M +Bros +Brose/M +Brown/MG +Browne/M +Brownell/M +Brownian/M +Brownie/MS +Browning/M +Brownsville/M +Brubeck/M +Bruce/M +Brucie/M +Bruckner/M +Bruegel/M +Brueghel's +Bruis/M +Brumidi/M +Brummel/M +Brunei/M +Brunelleschi/M +Brunhilda/M +Brunhilde/M +Bruno/M +Brunswick/M +Brussels +Brutus/M +Bruxelles/M +Bryan/M +Bryana/M +Bryant/M +Bryanty/M +Bryce/M +Bryn/M +Bryna/M +Brynn/RM +Brynna/M +Brynne/M +Brynner/M +Bryon/M +Brzezinski/M +Btu +Buber/M +Buchanan/M +Bucharest/M +Buchenwald/M +Buchwald/M +Buck/M +Buckie/M +Buckingham/M +Buckley/M +Buckner/M +Bucky/M +Bud/M +Budapest/M +Budd/M +Buddha/MS +Buddhism/SM +Buddhist/SM +Buddie/M +Buddy/M +Budweiser/MS +Buehring/M +Buena/M +Buffalo/M +Buffy/M +Buford/M +Bugatti/M +Buick/M +Buiron/M +Bujumbura/M +Bukhara/M +Bukharin/M +Bulawayo/M +Bulba/M +Bulfinch/M +Bulganin/M +Bulgaria/M +Bulgarian/S +Bullock/M +Bullwinkle/M +Bultmann/M +Bumbry/M +Bumppo/M +Bunche/M +Bundestag/M +Bundy/M +Bunin/M +Bunker/M +Bunni/M +Bunnie/M +Bunny/M +Bunsen/SM +Bunyan/M +Burbank/M +Burch/M +Burg/RM +Burger/M +Burgess/M +Burgoyne/M +Burgundian/S +Burgundy/MS +Burk/SM +Burke/M +Burl/M +Burlie/M +Burlingame/M +Burlington/M +Burma/M +Burmese +Burnaby/M +Burnard/M +Burne/MS +Burnett/M +Burns +Burnside/MS +Burr/M +Burris/M +Burroughs/M +Bursa/M +Burt/M +Burtie/M +Burton/M +Burty/M +Burundi/M +Burundian/S +Busch/M +Bush/M +Bushido/M +Bushnell/M +Butch/M +Butler/M +Butterfield/M +Buxtehude/M +Buñuel/M +Byelorussia's +Byers/M +Byram/M +Byran/M +Byrann/M +Byrd/M +Byrle/M +Byrne/M +Byrom/M +Byron/M +Byronic +Byronism/M +Byzantine/S +Byzantium/M +C +C's +CA +CACM +CAD +CAI +CALCOMP/M +CAM +CAP +CARE +CATV +CB +CBC +CBS +CCTV +CCU +CD +CDC/M +CDT +CENTREX/M +CEO +CERN/M +CF +CFC +CFO +CIA +CID +CMOS +CNN +CNS +CO +COBOL +COD +COL +COLA +CPA +CPI +CPO +CPR +CPU/SM +CRT/S +CST +CT +CV +CZ +Ca/M +Cabernet/M +Cabot/M +Cabrera/M +Cabrini/M +Cacilia/M +Cacilie/M +Cad/M +Caddric/M +Cadette/S +Cadillac/MS +Cadiz/M +Caedmon/M +Caesar/MS +Cage/M +Cagney/M +Cahokia/M +Cahra/M +Caiaphas/M +Cain/MS +Caine/M +Cairistiona/M +Cairo/M +Caitlin/M +Caitrin/M +Cajun/MS +Cal/MY +CalComp/M +Calais/M +Calcomp/M +Calcutta/M +Calder/M +Calderon/M +Caldwell/M +Cale/M +Caleb/M +Caledonia/M +Calgary/M +Calhoun/M +Cali/M +Caliban/M +Calida/M +Calif/M +California/M +Californian/MS +Caligula/M +Calla/MS +Callaghan/M +Callahan/M +Callao/M +Callean/M +Calley/M +Calli/M +Callida/M +Callie/M +Calliope/M +Callisto/M +Cally/M +Caloocan/M +Caltech/M +Calumet/M +Calv/M +Calvary/M +Calvert/M +Calvin/M +Calvinism/MS +Calvinist/MS +Calvinistic +Calypso/M +Cam/M +Camacho/M +Camala/M +Cambodia/M +Cambodian/S +Cambrian/S +Cambridge/M +Camden/M +Camel/M +Camella/M +Camellia/M +Camelopardalis/M +Camelot/M +Camembert/MS +Cameron/M +Cameroon/SM +Cameroonian/S +Camey/M +Cami/M +Camila/M +Camile/M +Camilla/M +Camille/M +Camino/M +Cammi/M +Cammie/M +Cammy/M +Camoens/M +Campbell/M +Campbellsport/M +Campinas/M +Campos +Camry/M +Camus/M +Can/M +Canaan/M +Canaanite/SM +Canad/M +Canada/M +Canadian/S +Canadianism/SM +Canaletto/M +Canaries +Canaveral/M +Canberra/M +Cancer/MS +Cancun/M +Candace/M +Candi/SM +Candice/M +Candida/M +Candide/M +Candie/M +Candlewick/M +Candra/M +Candy/M +Canis/M +Cannes +Cannon/M +Canoga/M +Canonical +Canonical's +Canopus/M +Cantabrigian +Canterbury/M +Canton/M +Cantonese/M +Cantor/M +Cantrell/M +Cantu/M +Canute/M +Capek/M +Capella/M +Capet/M +Capetown/M +Caph/M +Capistrano/M +Capitan/M +Capitol/MS +Capitoline/M +Capone/M +Capote/M +Cappy/M +Capra/M +Capri/M +Caprice/M +Capricorn/MS +Capt/M +Capulet/M +Caputo/M +Car/MNY +Cara/M +Caracalla/M +Caracas/M +Caralie/M +Caravaggio/M +Carboloy/M +Carbondale/M +Carbone/MS +Carboniferous +Carborundum/MS +Carce/M +Cardenas/M +Cardiff/M +Cardin/M +Cardiod/M +Care/M +Caren/M +Carena/M +Caresa/M +Caressa/M +Caresse/M +Carey/M +Cari/M +Caria/M +Carib/M +Caribbean/S +Carie/M +Caril/M +Carilyn/M +Carin/M +Carina/M +Carine/M +Cariotta/M +Carissa/M +Carita/M +Caritta/M +Carl/MNG +Carla/M +Carlee/M +Carleen/M +Carlen/M +Carlene/M +Carleton/M +Carletonian/M +Carley/M +Carlie/M +Carlin/M +Carlina/M +Carline/M +Carling/M +Carlita/M +Carlo/SM +Carlota/M +Carlotta/M +Carlsbad/M +Carlson/M +Carlton/M +Carly/M +Carlye/M +Carlyle/M +Carlyn/M +Carlynn/M +Carlynne/M +Carma/M +Carmel/M +Carmela/M +Carmelia/M +Carmelina/M +Carmelita/M +Carmella/M +Carmelle/M +Carmelo/M +Carmen/M +Carmencita/M +Carmichael/M +Carmina/M +Carmine/M +Carmita/M +Carmon/M +Carnap/M +Carnegie/M +Carney/M +Carnot/M +Carny/M +Caro/M +Carol/M +Carola/M +Carolan/M +Carolann/M +Carole/M +Carolee/M +Carolin/M +Carolina/MS +Caroline/M +Carolingian +Carolinian/S +Caroljean/M +Carolus/M +Carolyn/M +Carolyne/M +Carolynn/M +Caron/M +Carpathian/MS +Carpenter/M +Carr/M +Carree/M +Carri/M +Carrie/M +Carrier/M +Carrillo/M +Carrissa/M +Carrol/M +Carroll/M +Carry/MR +Carson/M +Cart/MR +Carter/M +Cartesian +Carthage/M +Carthaginian/S +Cartier/M +Cartwright/M +Carty/RM +Caruso/M +Carver/M +Cary/M +Caryl/M +Caryn/M +Casablanca/M +Casals/M +Casandra/M +Casanova/SM +Casar/M +Cascades/M +Case/M +Casey/M +Cash/M +Casi/M +Casie/M +Caspar/M +Casper/M +Caspian +Cass +Cassandra/SM +Cassandre/M +Cassandry/M +Cassatt/M +Cassaundra/M +Cassey/M +Cassi/M +Cassie/M +Cassiopeia/M +Cassite/M +Cassius/M +Cassondra/M +Cassy/M +Castaneda/M +Castile's +Castillo/M +Castor/M +Castries/M +Castro/M +Catalan/MS +Catalina/M +Catalonia/M +Catarina/M +Catawba/M +Cate/M +Caterina/M +Caterpillar +Catha/M +Catharina/M +Catharine/M +Cathay/M +Cathe/RM +Cathee/M +Cather/M +Catherin/M +Catherina/M +Catherine/M +Cathi/M +Cathie/M +Cathleen/M +Cathlene/M +Catholic/S +Catholicism/SM +Cathrin/M +Cathrine/M +Cathryn/M +Cathy/M +Cathyleen/M +Cati/M +Catie/M +Catiline/M +Catina/M +Catlaina/M +Catlee/M +Catlin/M +Cato/M +Catrina/M +Catriona/M +Catskill/SM +Catt/M +Catullus/M +Caty/M +Caucasian/S +Caucasoid/S +Caucasus/M +Cauchy/M +Cavendish/M +Cavour/M +Caxton/M +Caye/M +Cayenne/M +Cayla/M +Cayman/M +Cayuga/M +Caz/M +Cazzie/M +Cb/M +Cchaddie/M +Cd/M +Ce +Ceausescu/M +Cebu/M +Cebuano/M +Cece/M +Cecelia/M +Cecil/M +Cecile/M +Ceciley/M +Cecilia/M +Cecilio/M +Cecilius/M +Cecilla/M +Cecily/M +Ced/M +Cedric/M +Ceil/M +Celanese/M +Cele/M +Celebes's +Celene/M +Celesta/M +Celeste/M +Celestia/M +Celestina/M +Celestine/M +Celestyn/M +Celestyna/M +Celia/M +Celie/M +Celina/M +Celinda/M +Celine/M +Celinka/M +Celisse/M +Celka/M +Celle/M +Cellini/M +Cello/M +Celsius/S +Celt/MS +Celtic/SM +Cenozoic +Centaurus/M +Centigrade +Centralia/M +Centrex +Cepheid +Cepheus/M +Cerberus/M +Cerenkov/M +Ceres/M +Cerf/M +Cervantes/M +Cesar/M +Cesare/M +Cesarean +Cesaro/M +Cessna/M +Cesya/M +Cetus/M +Ceylon/M +Ceylonese +Cezanne/S +Cf/M +Ch'in +Ch/MGNRS +Chablis/SM +Chad/M +Chadd/M +Chaddie/M +Chaddy/M +Chadian/S +Chadwick/M +Chaffey/M +Chagall/M +Chaim/M +Chaldea/M +Chaldean/M +Chalmers +Chamberlain/M +Chambers/M +Champlain/M +Chan/M +Chance/M +Chancellorsville/M +Chancey/M +Chanda/M +Chandal/M +Chandigarh/M +Chandler/M +Chandra/M +Chandragupta/M +Chandrasekhar/M +Chandy/M +Chane/M +Chanel/M +Chaney/M +Chang/M +Changchun/M +Changsha/M +Channa/M +Channing/M +Chantal/M +Chantalle/M +Chantilly/M +Chanukah's +Chao/M +Chaplin/M +Chapman/M +Chappaquiddick/M +Chara +Chardonnay +Charil/M +Charin/M +Chariot/M +Charis +Charissa/M +Charisse/M +Charita/M +Charity/M +Charla/M +Charlean/M +Charleen/M +Charlemagne/M +Charlena/M +Charlene/M +Charles/M +Charleston/SM +Charley/M +Charlie/M +Charline/M +Charlot/M +Charlotta/M +Charlotte/M +Charlottesville/M +Charlottetown/M +Charlton/M +Charmain/M +Charmaine/M +Charmane/M +Charmian/M +Charmin/M +Charmine/M +Charmion/M +Charo/M +Charolais +Charon/M +Chartres/M +Charybdis/M +Charyl/M +Chas +Chase/M +Chasity/M +Chastity/M +Chateaubriand +Chattahoochee/M +Chattanooga/M +Chatterley/M +Chatterton/M +Chaucer/M +Chaunce/M +Chauncey/M +Chautauqua/M +Chavez/M +Chayefsky/M +Che/M +Chechen/M +Chechnya/M +Cheddar/MS +Cheerios/M +Cheeto/M +Cheever/M +Chekhov/M +Chelsae/M +Chelsea/M +Chelsey/M +Chelsie/M +Chelsy/M +Chelyabinsk/M +Chen/M +Cheng/M +Chengdu +Cheops/M +Cher/M +Chere/M +Cherey/M +Cheri/M +Cherianne/M +Cherice/M +Cherida/M +Cherie/M +Cherilyn/M +Cherilynn/M +Cherin/M +Cherise/M +Cherish/M +Cheriton/M +Cherlyn/M +Chernenko/M +Chernobyl/M +Cherokee/MS +Cherri/M +Cherrita/M +Cherry/M +Chery/M +Cherye/M +Cheryl/M +Chesapeake/M +Cheshire/M +Cheslie/M +Chester/M +Chesterfield/M +Chesterton/M +Cheston/M +Chet/M +Chev/M +Chevalier/M +Cheviot/M +Chevrolet/M +Chevy/M +Cheyenne/SM +Chi/M +Chiang/M +Chianti/S +Chiarra/M +Chiba/M +Chic/M +Chicago/M +Chicagoan/SM +Chicana/MS +Chicano/MS +Chick/M +Chickasaw/SM +Chickie/M +Chicky/M +Chico/M +Chihuahua/MS +Chile/MS +Chilean/S +Chilton/M +Chimborazo/M +Chimera/S +Chimiques +Chimu/M +Chin/M +China/M +Chinaman/M +Chinamen +Chinatown/SM +Chinese/M +Ching/M +Chinook/MS +Chip/M +Chipewyan/M +Chippendale/M +Chippewa/MS +Chiquia/M +Chiquita/M +Chirico/M +Chisholm/M +Chisinau/M +Chittagong/M +Chlo/M +Chloe/M +Chloette/M +Chloris +Choctaw/MS +Chomsky/M +Chongqing +Chopin/M +Chou/M +Chretien/M +Chris/M +Chrisse/M +Chrissie/M +Chrissy/M +Christ/SMN +Christa/M +Christabel/M +Christabella/M +Christal/M +Christalle/M +Christan/M +Christchurch/M +Christean/M +Christel/M +Christen/M +Christendom/MS +Christensen/M +Christenson/M +Christi/M +Christian/MS +Christiana/M +Christiane/M +Christianity/SM +Christianize/GSD +Christiano/M +Christians/N +Christiansen/M +Christie/SM +Christin/M +Christina/M +Christine/M +Christlike +Christmas/SM +Christmastide/SM +Christmastime/S +Christoffel/M +Christoffer/M +Christoforo/M +Christoper/M +Christoph/MR +Christophe/M +Christopher/M +Christophorus/M +Christos/M +Christy's +Christye/M +Christyna/M +Chrisy/M +Chrotoem/M +Chrysa/M +Chrysler/M +Chrysostom/M +Chrystal/M +Chryste/M +Chrystel/M +Chucho/M +Chuck/M +Chukchi/M +Chumash/M +Chung/M +Chungking's +Church/MS +Churchill/M +Churchillian +Chuvash/M +Ci +Cicely/M +Cicero/M +Ciceronian +Cicily/M +Cid/M +Ciel/M +Cilka/M +Cincinnati/M +Cinda/M +Cindee/M +Cindelyn/M +Cinderella/MS +Cindi/M +Cindie/M +Cindra/M +Cindy/M +Cinerama/M +Cinnamon/M +Circe/M +Cirillo/M +Cirilo/M +Ciro/M +Cissiee/M +Cissy/M +Citibank/M +Citroen/M +Cl/VM +Claiborn/M +Claiborne/M +Clair/M +Claire/M +Clairol/M +Clancy/M +Clapeyron/M +Clapton/M +Clara/M +Clarabelle/M +Clarance/M +Clare/M +Claremont/M +Clarence/M +Clarendon/M +Claresta/M +Clareta/M +Claretta/M +Clarette/M +Clarey/M +Clari/M +Claribel/M +Clarice/M +Clarie/M +Clarinda/M +Clarine/M +Clarissa/M +Clarisse/M +Clarita/M +Clark/M +Clarke/M +Clarridge/M +Clary/M +Claude/M +Claudell/M +Claudelle/M +Claudetta/M +Claudette/M +Claudia/M +Claudian/M +Claudianus/M +Claudie/M +Claudina/M +Claudine/M +Claudio/M +Claudius/M +Claus/NM +Clausen/M +Clausewitz/M +Clausius/M +Clay/M +Clayborn/M +Clayborne/M +Claybourne/M +Clayson/M +Clayton/M +Clea/M +Clearwater/M +Cleavland/M +Clem/XM +Clemence/M +Clemenceau/M +Clement/MS +Clemente/M +Clementia/M +Clementina/M +Clementine/M +Clementius/M +Clemmie/M +Clemmy/M +Clemons +Clemson/M +Cleo/M +Cleon/M +Cleopatra/M +Clerc/M +Clerissa/M +Cletis +Cletus/M +Cleve/M +Cleveland/M +Clevey/M +Clevie/M +Cliburn/M +Cliff/M +Clifford/M +Clifton/M +Clim/M +Cline/M +Clint/M +Clinton/M +Clio/M +Clive/M +Clo/M +Cloe/M +Cloris/M +Clotho/M +Clotilda/M +Clovis/M +Cluj/M +Cly/M +Clyde/M +Clydesdale/M +Clytemnestra/M +Clyve/M +Clywd/M +Cm/M +Co/M +Coates/M +Cob/M +Cobain/M +Cobb/M +Cobbie/M +Cobby/M +Cobol/M +Cochabamba/M +Cochin/M +Cochise/M +Cochran/M +Cockney +Cocteau/M +Cod/M +Codee/M +Codi/M +Codie/M +Cody/M +Coffey/M +Coffman/M +Cognac/M +Cohan/M +Cohen/M +Cohn/M +Coimbatore/M +Cointon/M +Coke/MS +Col/MY +Colan/M +Colas +Colbert/M +Colby/M +Cole/M +Coleen/M +Coleman/M +Colene/M +Coleridge/M +Colet/M +Coletta/M +Colette/M +Colfax/M +Colgate/M +Colin/M +Colleen/M +Collen/M +Collete/M +Collette/M +Collie/M +Collier/M +Collin/MS +Colline/M +Colly/RM +Colman/M +Colo/M +Cologne/M +Colombia/M +Colombian/S +Colombo/M +Colon/M +Coloradan/S +Colorado/M +Coloradoan/S +Colosseum/M +Colt/M +Coltrane/M +Columbia/M +Columbian +Columbine/M +Columbus/M +Colver/M +Com/M +Comanche/MS +Combs/M +Comdex/M +Comdr/M +Cominform/M +Commie +Commons/M +Commonwealth/M +Commonwealths +Communion/SM +Communism/S +Communist/S +Comoros +Compaq/M +Compton/M +CompuServe/M +Compuserve/M +Comte/M +Con/M +Conakry/M +Conan/M +Conant/M +Concepción/M +Concetta/M +Concettina/M +Conchita/M +Concord/MS +Concorde/M +Concordia/M +Condorcet/M +Conestoga +Confederacy/M +Confederate/S +Confucian/S +Confucianism/SM +Confucius/M +Cong/M +Congo/M +Congolese +Congregational +Congregationalist/S +Congress/MS +Congreve/M +Conley/M +Conn/RM +Connecticut/M +Connelly/M +Conner/M +Connery/M +Conney/M +Conni/M +Connie/M +Connor/SM +Conny/M +Conrad/M +Conrade/M +Conrado/M +Conrail/M +Conroy/M +Consalve/M +Conservative/S +Consolata/M +Constable/M +Constance/M +Constancia/M +Constancy/M +Constanta/M +Constantia/M +Constantin/M +Constantina/M +Constantine/M +Constantino/M +Constantinople/M +Constitution +Consuela/M +Consuelo/M +Continent/M +Continental/S +Contreras/M +Conway/M +Cook/M +Cooke/M +Cookie/M +Cooley/M +Coolidge/M +Coop/MR +Cooper/M +Coors/M +Copeland/M +Copenhagen/M +Copernican +Copernicus/M +Copland/M +Copley/M +Copperfield/M +Coppola/M +Coptic/M +Cora/M +Corabel/M +Corabella/M +Corabelle/M +Coral/M +Coralie/M +Coraline/M +Coralyn/M +Corbet/M +Corbett/M +Corbie/M +Corbin/M +Corby/M +Cord/M +Cordelia/M +Cordelie/M +Cordell/M +Cordey/M +Cordi/M +Cordie/M +Cordilleras +Cordoba +Cordula/M +Cordy/M +Coreen/M +Corella/M +Corenda/M +Corene/M +Coretta/M +Corette/M +Corey/M +Corfu/M +Cori/M +Corie/M +Corilla/M +Corina/M +Corine/M +Corinna/M +Corinne/M +Corinth/M +Corinthian/S +Corinthians/M +Coriolanus/M +Coriolis/M +Coriss/M +Corissa/M +Cork/M +Corliss/M +Corly/M +Cormack/M +Cornall/M +Corneille/M +Cornela/M +Cornelia/M +Cornelius/M +Cornell/M +Cornelle/M +Corney/M +Cornie/M +Cornish/S +Cornwall/M +Cornwallis/M +Corny/M +Coronado/M +Corot/M +Corp +Correggio/M +Correna/M +Correy/M +Corri/M +Corrianne/M +Corrie/M +Corrina/M +Corrine/M +Corrinne/M +Corry/M +Corsica/M +Corsican/S +Cort/M +Cortes/S +Cortez's +Cortie/M +Cortland/M +Cortney/M +Corty/M +Corvallis/M +Corvus/M +Cory/M +Cos +Cosby/M +Cosetta/M +Cosette/M +Cosimo/M +Cosme/M +Cosmo/M +Cossack/SM +Costa/M +Costanza/M +Costello/M +Costner/M +Cote/M +Cotonou/M +Cotopaxi/M +Cotton/M +Coulomb/M +Couperin/M +Courbet/M +Court/M +Courtenay/M +Courtnay/M +Courtney/M +Cousteau/M +Covent/M +Coventry/MS +Coward/M +Cowley/M +Cowper/M +Cox/M +Coy/M +Cozmo/M +Cozumel/M +Cpl +Cr/M +Crabbe/M +Craft/M +Craggie/M +Craggy/M +Craig/M +Cramer/M +Cranach/M +Crandall/M +Crane/M +Cranford/M +Cranmer/M +Cranston/M +Crater/M +Crawford/M +Cray/SM +Crayola/M +Creation/M +Creator/M +Cree/MDS +Creek/SM +Creigh/M +Creight/M +Creighton/M +Creole/MS +Creon/M +Crestview/M +Cretaceous/Y +Cretaceously/M +Cretan/S +Crete/M +Crichton/M +Crick/M +Crimea/M +Crimean +Crin/M +Cris/M +Crisco/M +Crissie/M +Crissy/M +Crista/M +Cristabel/M +Cristal/M +Cristen/M +Cristi/M +Cristian/M +Cristiano/M +Cristie/M +Cristin/M +Cristina/M +Cristine/M +Cristionna/M +Cristobal/M +Cristy/M +Croat/SM +Croatia/M +Croatian/S +Croce/M +Crockett/M +Crockpot/M +Croesus/SM +Croix/M +Cromwell/M +Cromwellian +Cronin/M +Cronkite/M +Cronus/M +Crookes/M +Crosby/M +Cross/M +Crowley/M +Crucifixion/MS +Cruikshank/M +Crusoe/M +Crux/M +Cruz/M +Cryptozoic/M +Crysta/M +Crystal/M +Crystie/M +Cs +Ct/M +Cthrine/M +Cu/M +Cuba/M +Cuban/S +Cuchulain/M +Cuisinart/M +Culbertson/M +Cull/MN +Cullan/M +Cullen/M +Culley/M +Cullie/M +Cullin/M +Cully/M +Culver/MS +Cumberland/M +Cummings +Cunard/M +Cunningham/M +Cupertino/M +Cupid/M +Curacao/M +Curcio/M +Curie/M +Curitiba/M +Curr/M +Curran/M +Currey/M +Currie/M +Currier/M +Curry/MR +Curt/M +Curtice/M +Curtis/M +Cushman/M +Custer/M +Cuvier/M +Cuzco/M +Cy/M +Cyanamid/M +Cyb/M +Cybele/M +Cybil/M +Cybill/M +Cyclades +Cyclopes +Cyclops/M +Cygnus/M +Cymbre/M +Cynde/M +Cyndi/M +Cyndia/M +Cyndie/M +Cyndy/M +Cynthea/M +Cynthia/M +Cynthie/M +Cynthy/M +Cyprian +Cypriot/SM +Cyprus/M +Cyrano/M +Cyril/M +Cyrill/M +Cyrille/M +Cyrillic +Cyrillus/M +Cyrus/M +Czech +Czechoslovak/S +Czechoslovakia/M +Czechoslovakian/S +Czechs +Czerniak/M +Czerny/M +D +D'Arcy +D's +DA +DAG +DARPA/M +DAT +DB +DBMS +DC +DD +DDS +DDT +DE +DEC/M +DECNET +DECnet/M +DECstation/M +DECsystem/M +DECtape/M +DH +DI +DJ +DMD +DMZ +DNA +DOA +DOB +DOD +DOE +DOS +DOT +DP +DPT +DPs +DST +DTP +DUI +DWI +Dacca's +Dacey/M +Dachau/M +Dacia/M +Dacie/M +Dacron/MS +Dacy/M +Dada/M +Dadaism/M +Dadaist/M +Dade/M +Daedalus/M +Dael/M +Daffi/M +Daffie/M +Daffy/M +Dag/M +Dagmar/M +Dagny/M +Daguerre/M +Dagwood/M +Dahl/M +Dahlia/M +Dahomey/M +Daile/M +Daimler/M +Daisey/M +Daisi/M +Daisie/M +Daisy/M +Dakar/M +Dakota/SM +Dakotan +Dal/M +Dale/M +Dalenna/M +Daley/M +Dalhousie/M +Dali/SM +Dalia/M +Dalian/M +Dalila/M +Dall/M +Dallas/M +Dalli/MS +Dallon/M +Dalmatia/M +Dalmatian/SM +Daloris/M +Dalston/M +Dalt/M +Dalton/M +Daly/M +Damara/M +Damaris/M +Damascus/M +Dame/SMN +Damian/M +Damiano/M +Damien/M +Damion/M +Damita/M +Damocles/M +Damon/M +Dan/SM +Dana/M +Danaë +Danbury/M +Dane/SM +Danelaw/M +Danell/M +Danella/M +Danette/M +Dangerfield/M +Dani/M +Dania/M +Danial/M +Danica/M +Danice/M +Danie/M +Daniel/SM +Daniela/M +Daniele/M +Daniella/M +Danielle/M +Danielson/M +Danika/M +Danila/M +Danish +Danit/M +Danita/M +Danna/M +Dannel/M +Danni/M +Dannie/M +Danny/M +Dannye/M +Dante/M +Danton/M +Danube/M +Danubian +Danville/M +Danya/M +Danyelle/M +Danyette/M +Danzig/M +Daphene/M +Daphna/M +Daphne/M +Dar/MNH +Dara/M +Darb/M +Darbee/M +Darbie/M +Darby/M +Darcee/M +Darcey/M +Darci/M +Darcie/M +Darcy/M +Darda/M +Dardanelles +Dare/M +Dareen/M +Darell/M +Darelle/M +Daren/M +Dari/M +Daria/M +Darice/M +Darill/M +Darin/M +Dario/M +Darius/M +Darjeeling/M +Darla/M +Darleen/M +Darlene/M +Darline/M +Darling/M +Darlington/M +Darlleen/M +Darn/M +Darnall/M +Darnell/M +Daron/M +Darrel/M +Darrell/M +Darrelle/M +Darren/M +Darrick/M +Darrin/M +Darrow/M +Darryl/M +Darsey/M +Darsie/M +Darth/M +Dartmouth/M +Darvon/M +Darwin/M +Darwinian/S +Darwinism/MS +Darwinist/MS +Darya/M +Daryl/M +Daryle/M +Daryn/M +Dasha/M +Dasi/M +Dasie/M +Dasya/M +Datamation/M +Datamedia/M +Datha/M +Datsun/M +Daugherty/M +Daumier/M +Daune/M +Dav/MN +Davao/M +Dave/M +Daveen/M +Daven/M +Davenport/M +Daveta/M +Davey/M +David/SM +Davida/M +Davidde/M +Davide/M +Davidson/M +Davie/M +Davin/M +Davina/M +Davine/M +Davinich/M +Davis/M +Davita/M +Davon/M +Davy/SM +Dawes/M +Dawn/M +Dawna/M +Dawson/M +Day/M +Dayle/M +Dayna/M +Dayton/M +Ddene/M +De/NM +DeKalb/M +DeKastere/M +DeMorgan/M +Dean/M +Deana/M +Deandre/M +Deane/M +Deann/M +Deanna/M +Deanne/M +Dearborn/M +Deb/MS +Debbi/M +Debbie/M +Debby/M +Debee/M +Debera/M +Debi/M +Debian +Debian's +Debor/M +Debora/M +Deborah/M +Debra/M +Debussy/M +Dec/M +Decalogue/M +Decatur/M +Decca/M +Deccan/M +December/SM +Deck/RM +Decker/M +Dede/M +Dedekind/M +Dedie/M +Dedra/M +Dee/M +Deeann/M +Deeanne/M +Deedee/M +Deena/M +Deerdre/M +Deere/M +Deeyn/M +Defoe/M +Degas/M +Dehlia/M +Deidre/M +Deimos/M +Deina/M +Deirdre/MS +Deity/M +Dejesus/M +Del/MY +Dela/M +Delacroix/M +Delacruz/M +Delainey/M +Delaney/M +Delano/M +Delaware/MS +Delawarean/SM +Delbert/M +Delcina/M +Delcine/M +Deleon/M +Delft/M +Delgado/M +Delhi/M +Delia/M +Delibes/M +Delila/M +Delilah/M +Delilahs +Delinda/M +Delius/M +Dell/M +Della/M +Dellwood/M +Delly/M +Delmar/M +Delmarva/M +Delmer/M +Delmonico +Delmor/M +Delmore/M +Delora/M +Delores/M +Deloria/M +Deloris/M +Delphi/M +Delphic +Delphine/M +Delphinia/M +Delphinus/M +Delta/M +Dem/MG +Demavend/M +Demerol/M +Demeter/M +Demetra/M +Demetre/M +Demetri/MS +Demetria/M +Demetrius/M +Deming/M +Democrat/MS +Democratic +Democritus/M +Demosthenes/M +Demott/M +Dempsey/M +Den/M +Dena/M +Dene/M +Deneb/M +Denebola/M +Deneen/M +Deng/M +Deni/SM +Denice/M +Denise/M +Denmark/M +Denna/M +Dennet/M +Denney/M +Denni/MS +Dennie/M +Dennison/M +Denny/M +Denver/M +Deny/M +Denys +Denyse/M +Deon/M +Deonne/M +Dependant/MS +Dept/M +Der/M +Derby/SM +Derbyshire/M +Derek/M +Derick/M +Derk/M +Dermot/M +Derrek/M +Derrick/M +Derrida/M +Derrik/M +Derril/M +Derron/M +Derry/M +Derward/M +Derwin/M +Des +Descartes/M +Desdemona/M +Desi/M +Desirae/M +Desiree/M +Desiri/M +Desmond/M +Desmund/M +Detroit/M +Deuteronomy/M +Deutsch/M +Dev/M +Deva/M +Devan/M +Devanagari/M +Devi/M +Devin/M +Devina/M +Devinne/M +Devland/M +Devlen/M +Devlin/M +Devon/M +Devondra/M +Devonian +Devonna/M +Devonne/M +Devonshire/M +Devora/M +Devy/M +Dew/M +Dewain/M +Dewar/M +Dewayne/M +Dewey/M +Dewie/M +Dewitt/M +Dex/M +Dexedrine/M +Dexter/M +Dhaka +Dhaulagiri/M +Di/M +DiCaprio/M +DiMaggio/M +Diaghilev/M +Diahann/M +Dian/M +Diana/M +Diandra/M +Diane/M +Dianemarie/M +Diann/M +Dianna/M +Dianne/M +Diannne/M +Diarmid/M +Diaspora/SM +Diaz's +Dick/XM +Dickens/M +Dickensian/S +Dickerson/M +Dickie/M +Dickinson/M +Dickson/M +Dicky/M +Dictaphone/SM +Diderot/M +Didi/M +Dido/M +Diefenbaker/M +Diego/M +Diem/M +Diena/M +Dierdre/M +Diesel's +Dieter/M +Dietrich/M +Dietz/M +Dijkstra/M +Dijon/M +Dilan/M +Dilbert/M +Dill/M +Dillard/M +Dillie/M +Dillinger/M +Dillon/M +Dilly/M +Dimitri/M +Dimitry/M +Dina/M +Dinah/M +Dinnie/M +Dinny/M +Dino/M +Diocletian/M +Diogenes/M +Dion/M +Dione/M +Dionis/M +Dionisio/M +Dionne/M +Dionysian +Dionysus/M +Diophantine/M +Dior/M +Dipper/M +Dir +Dirac/M +Dirichlet/M +Dirk/M +Dis +Disney/M +Disneyland/M +Disraeli/M +Dita/M +Ditzel/M +Dix/M +Dixie/M +Dixiecrat/MS +Dixieland/MS +Dixon/M +Djakarta's +Djibouti/M +Dmitri/M +Dnepr's +Dnepropetrovsk/M +Dnieper's +Dniester/M +Dniren/M +Dobbin/M +Doberman +Dobro/M +Doctor +Doctorow/M +Dode/M +Dodge/M +Dodgson/M +Dodi/M +Dodie/M +Dodington/M +Dodoma/M +Dodson/M +Dody/M +Doe/M +Doge/M +Dogtown/M +Doha/M +Dolby/SM +Dole/M +Dolf/M +Doll/M +Dolley/M +Dolli/M +Dollie/M +Dolly/M +Dolores/M +Dolorita/SM +Dolph/M +Dom/M +Domenic/M +Domenico/M +Domeniga/M +Domesday/M +Dominga/M +Domingo/M +Dominguez/M +Domini/M +Dominic/M +Dominica/M +Dominican/MS +Dominick/M +Dominik/M +Dominique/M +Domitian/M +Don/SM +Dona/M +Donahue/M +Donal/M +Donald/M +Donaldson/M +Donall/M +Donalt/M +Donatello/M +Donaugh/M +Donavon/M +Donella/M +Donelle/M +Donetsk/M +Donetta/M +Donia/M +Donica/M +Donielle/M +Donizetti/M +Donn/RM +Donna/M +Donnamarie/M +Donne/M +Donnell/M +Donnelly/M +Donner/M +Donni/M +Donnie/M +Donny/M +Donovan/M +Dooley/M +Doolittle/M +Doonesbury/M +Doppler/M +Dora/M +Dorado/M +Doralia/M +Doralin/M +Doralyn/M +Doralynn/M +Doralynne/M +Dorcas +Dorchester/M +Doreen/M +Dorelia/M +Dorella/M +Dorelle/M +Dorena/M +Dorene/M +Doretta/M +Dorette/M +Dorey/M +Dori/MS +Doria/M +Dorian/M +Doric +Dorice/M +Dorie/M +Dorine/M +Dorisa/M +Dorise/M +Dorita/M +Doro/M +Dorolice/M +Dorolisa/M +Dorotea/M +Doroteya/M +Dorothea/M +Dorothee/M +Dorothy/M +Dorree/M +Dorri/SM +Dorrie/M +Dorry/M +Dorsey/M +Dorthea/M +Dorthy/M +Dortmund/M +Dory/M +Doré/M +Dosi/M +Dostoevsky/M +Dot/M +Doti/M +Dotson/M +Dotti/M +Dottie/M +Dotty/M +Douala/M +Douay/M +Doubleday/M +Doug/M +Dougherty/M +Dougie/M +Douglas/M +Douglass +Dougy/M +Douro/M +Dov/MR +Dover/M +Dow/M +Downey/M +Downs +Doy/M +Doyle/M +Dr/M +Draco/M +Draconian +Dracula/M +Drake/M +Dramamine/MS +Drambuie/M +Drano/M +Dravidian/M +Dre/M +Dreddy/M +Dredi/M +Dreiser/M +Dresden/M +Drew/M +Drexel/M +Dreyfus/M +Dreyfuss +Drona/M +Dru/M +Druci/M +Drucie/M +Drucill/M +Drucy/M +Drud/M +Drugi/M +Druid's +Drummond/M +Drury/M +Drusi/M +Drusie/M +Drusilla/M +Drusy/M +Dryden/M +Dshubba/M +Du/M +DuPont/MS +Duane/M +Dubai/M +Dubcek/M +Dubhe/M +Dublin/M +Dubrovnik/M +Dubuque/M +Duchamp/M +Dud/M +Dudley/M +Duff/M +Duffie/M +Duffy/M +Dugald/M +Duisburg/M +Duke/M +Dukey/M +Dukie/M +Duky/M +Dulce/M +Dulcea/M +Dulci/M +Dulcia/M +Dulciana/M +Dulcie/M +Dulcine/M +Dulcinea/M +Dulcy/M +Dulles/M +Dulsea/M +Duluth/M +Dumas +Dumbo/M +Dumont/M +Dumpster/S +Dumpty/M +Dun/M +Dunant/M +Dunbar/M +Dunc/M +Duncan/M +Dundee/M +Dunedin/M +Dunham/M +Dunkirk/M +Dunlap/M +Dunn/M +Dunne/M +Dunstan/M +Dupont/MS +Dur/M +Duracell/M +Duran/M +Durand/M +Durant/M +Durante/M +Durban/M +Durex/M +Durham/MS +Durkee/M +Durkheim/M +Durocher/M +Durward/M +Duse/M +Dusenberg/M +Dusenbury/M +Dushanbe/M +Dustin/M +Dusty/M +Dutch/M +Dutchman/M +Dutchmen +Dutchwoman +Dutchwomen +Duvalier/M +Dvina/M +Dvorák/M +Dwain/M +Dwayne/M +Dwight/M +Dy/M +Dyan/M +Dyana/M +Dyane/M +Dyann/M +Dyanna/M +Dyanne/M +Dyer/M +Dyke/M +Dylan/M +Dyna/M +Dynah/M +Dzerzhinsky/M +Dürer/M +Düsseldorf +E +E's +EBCDIC +EC +ECG +EDP +EDT +EEC +EEG +EEO +EEOC +EFL +EFT +EGA/M +EKG +EM +EMT +ENE +EOE +EPA +ER +ERA +ESE +ESL +ESP +EST +ET +ETA +ETD +EU +Eachelle/M +Eada/M +Eadie/M +Eadith/M +Eadmund/M +Eakins/M +Eal/M +Ealasaid/M +Eamon/M +Earhart/M +Earl/M +Earle/M +Earlene/M +Earlie/M +Earline/M +Early/M +Earnest/M +Earnestine/M +Earp/M +Eartha/M +Earvin/M +East/ZSMR +Easter/M +Eastern/RZ +Easterner/M +Easthampton/M +Eastland/M +Eastman/M +Eastwick/M +Eastwood/M +Eaton/M +Eb/MN +Eba/M +Ebba/M +Eben/M +Ebeneezer/M +Ebeneser/M +Ebenezer/M +Eberhard/M +Eberto/M +Ebola +Ebonee/M +Ebonics +Ebony/M +Ebro/M +Eccles +Ecclesiastes/M +Eco/M +Ecole/M +Econometrica/M +Ecstasy/S +Ecuador/M +Ecuadoran/S +Ecuadorean/S +Ecuadorian/S +Ed/XMN +Eda/M +Edam/SM +Edan/M +Edd/M +Edda/M +Eddi/M +Eddie/M +Eddy/M +Ede/M +Edee/M +Edeline/M +Eden/M +Edgar/M +Edgard/M +Edgardo/M +Edgerton/M +Edgewater/M +Edgewood/M +Edi/MH +Edie/M +Edik/M +Edin/M +Edinburgh/M +Edison/M +Edita/M +Edith/M +Editha/M +Edithe/M +Ediva/M +Edlin/M +Edmon/M +Edmond/M +Edmonton/M +Edmund/M +Edna/M +Edouard/M +Edsel/M +Edsger/M +Eduard/M +Eduardo/M +Edubuntu +Edubuntu's +Eduino/M +Edvard/M +Edward/SM +Edwardian +Edwardo/M +Edwin/M +Edwina/M +Edy/M +Edyth/M +Edythe/M +Eeyore/M +Effie/M +Efrain/M +Efrem/M +Efren/M +Egan/M +Egbert/M +Egerton/M +Egon/M +Egor/M +Egypt/M +Egyptian/S +Egyptology/M +Ehrlich/M +Eichmann/M +Eiffel/M +Eileen/M +Eilis/M +Eimile/M +Einstein/SM +Einsteinian +Eire/M +Eirena/M +Eisenhower/M +Eisenstein/M +Eisner/M +Ekaterina/M +Ekberg/M +Ekstrom/M +Ektachrome/M +El/MY +Elaina/M +Elaine/M +Elana/M +Elane/M +Elanor/M +Elayne/M +Elba/MS +Elbe/M +Elbert/M +Elberta/M +Elbertina/M +Elbertine/M +Elbrus/M +Elden/M +Eldin/M +Eldon/M +Eldorado's +Eldredge/M +Eldridge/M +Eleanor/M +Eleanora/M +Eleanore/M +Eleazar/M +Electra/M +Eleen/M +Elena/M +Elene/M +Eleni/M +Elenore/M +Eleonora/M +Eleonore/M +Elfie/M +Elfreda/M +Elfrida/M +Elfrieda/M +Elga/M +Elgar/M +Eli/M +Elia/SM +Elianora/M +Elianore/M +Elicia/M +Elie/M +Elihu/M +Elijah/M +Elinor/M +Elinore/M +Eliot/M +Elisa/M +Elisabet/M +Elisabeth/M +Elisabetta/M +Elise/M +Eliseo/M +Elisha/M +Elissa/M +Elita/M +Eliza/M +Elizabet/M +Elizabeth/M +Elizabethan/S +Elka/M +Elke/M +Elkhart/M +Ella/M +Elladine/M +Ellary/M +Elle/M +Ellen/M +Ellene/M +Ellerey/M +Ellery/M +Ellesmere/M +Ellette/M +Elli/SM +Ellie/M +Ellington/M +Elliot/M +Elliott/M +Ellison/M +Ellissa/M +Ellswerth/M +Ellsworth/M +Ellwood/M +Elly/M +Ellyn/M +Ellynn/M +Elma/M +Elmer/M +Elmhurst/M +Elmira/M +Elmo/M +Elmore/M +Elmsford/M +Elna/MH +Elnar/M +Elnath/M +Elnora/M +Elnore/M +Elohim/M +Eloisa/M +Eloise/M +Elonore/M +Elora/M +Eloy/M +Elroy/M +Elsa/M +Elsbeth/M +Else/M +Elset/M +Elsey/M +Elsi/M +Elsie/M +Elsinore/M +Elspeth/M +Elston/M +Elsworth/M +Elsy/M +Eltanin/M +Elton/M +Elva/M +Elvera/M +Elvia/M +Elvin/M +Elvina/M +Elvira/M +Elvis/M +Elvyn/M +Elwin/M +Elwira/M +Elwood/M +Elwyn/M +Ely/M +Elyn/M +Elyse/M +Elysees +Elysha/M +Elysia/M +Elysian +Elysium/SM +Elyssa/M +Elysée/M +Em/M +Ema/M +Emacs/M +Emalee/M +Emalia/M +Emanuel/M +Emanuele/M +Emelda/M +Emelen/M +Emelia/M +Emelina/M +Emeline/M +Emelita/M +Emelyne/M +Emera/M +Emerson/M +Emery/M +Emil/M +Emile/M +Emilee/M +Emili/M +Emilia/M +Emilie/M +Emiline/M +Emilio/M +Emily/M +Eminence/MS +Emlen/M +Emlyn/M +Emlynn/M +Emlynne/M +Emma/M +Emmalee/M +Emmaline/M +Emmalyn/M +Emmalynn/M +Emmalynne/M +Emmanuel/M +Emmeline/M +Emmerich/M +Emmery/M +Emmet/M +Emmett/M +Emmey/M +Emmi/M +Emmie/M +Emmit/M +Emmott/M +Emmy/SM +Emmye/M +Emogene/M +Emory/M +Emyle/M +Emylee/M +Endicott/M +Endymion/M +Eng/M +Engel/MS +Engelbert/M +England/M +Englebert/M +Englewood/M +English/GDRSM +Englishman/M +Englishmen +Englishwoman/M +Englishwomen +Engracia/M +Enid/M +Enif/M +Eniwetok/M +Enkidu/M +Ennis/M +Enoch/M +Enos +Enrica/M +Enrichetta/M +Enrico/M +Enrika/M +Enrique/M +Enriqueta/M +Ensolite/M +Enterprise/M +Eocene +Eolanda/M +Eolande/M +Ephesian/S +Ephesians/M +Ephesus/M +Ephraim/M +Ephrayim/M +Ephrem/M +Epictetus/M +Epicurean +Epicurus/M +Epimethius/M +Epiphany/SM +Episcopal/S +Episcopalian/S +Epistle/SM +Epsom/M +Epstein/M +Equuleus/M +Er/M +Eran/M +Erasmus/M +Erastus/M +Erato/M +Eratosthenes/M +Erda/M +Erebus/M +Erek/M +Erena/M +Erhard/M +Erhart/M +Eric/M +Erica/M +Erich/M +Ericha/M +Erick/M +Ericka/M +Erickson/M +Ericson's +Ericsson's +Eridanus/M +Erie/SM +Erik/M +Erika/M +Erikson/M +Erin/M +Erina/M +Erinn/M +Erinna/M +Eris +Eritrea/M +Erl/M +Erlang/M +Erlenmeyer/M +Erma/M +Ermanno/M +Ermengarde/M +Ermentrude/M +Ermin/M +Ermina/M +Erminia/M +Erminie/M +Erna/M +Ernaline/M +Ernest/M +Ernesta/M +Ernestine/M +Ernesto/M +Ernestus/M +Ernie/M +Ernst/M +Erny/M +Eros/SM +Errick/M +Errol/M +Erroll/M +Erse/M +Erskine/M +Ertha/M +Erv/M +ErvIn/M +Ervin/M +Erwin/M +Eryn/M +Es +Esau/M +Escher/M +Escherichia/M +Escondido/M +Esdras/M +Eskimo/SM +Esma/M +Esmaria/M +Esmark/M +Esme/M +Esmeralda/M +Esp/M +Espagnol/M +Esperanto/M +Esperanza/M +Espinoza/M +Esposito/M +Esq/M +Esquire/S +Esra/M +Essa/M +Essen/M +Essene/SM +Essequibo/M +Essex/M +Essie/M +Essy/M +Esta/M +Establishment/MS +Esteban/M +Estel/M +Estela/M +Estele/M +Estell/M +Estella/M +Estelle/M +Ester/M +Esterházy/M +Estes +Estevan/M +Esther/M +Estonia/M +Estonian/S +Estrada/M +Estrella/M +Estrellita/M +Etan/M +Ethan/M +Ethe/M +Ethel/M +Ethelbert/M +Ethelda/M +Ethelin/M +Ethelind/M +Etheline/M +Ethelred/M +Ethelyn/M +Ethernet/MS +Ethiopia/M +Ethiopian/S +Ethyl/M +Etienne/M +Etna/M +Etruria/M +Etruscan/MS +Etta/M +Etti/M +Ettie/M +Ettore/M +Etty/M +Eu/M +Eucharist/SM +Eucharistic +Euclid/M +Eudora/M +Euell/M +Eugen/M +Eugene/M +Eugenia/M +Eugenie/M +Eugenio/M +Eugenius/M +Eugine/M +Eula/M +Eulalie/M +Euler/M +Eulerian/M +Eumenides +Eunice/M +Euphemia/M +Euphrates/M +Eur/M +Eurasia/M +Eurasian/S +Euripides/M +Eurodollar/SM +Europa/M +Europe/M +European/MS +Europeanization/SM +Europeanized +Eurydice/M +Eustace/M +Eustachian/M +Eustacia/M +Euterpe/M +Ev/MN +Eva/M +Evaleen/M +Evan/MS +Evangelia/M +Evangelical/S +Evangelin/M +Evangelina/M +Evangeline/M +Evangelist/MS +Evania/M +Evanne/M +Evanston/M +Evansville/M +Eve/M +Eveleen/M +Evelin/M +Evelina/M +Eveline/M +Evelyn/M +Even/M +Evenki/M +EverReady/M +Everard/M +Eveready/M +Evered/M +Everest/M +Everett/M +Everette/M +Everglades +Everhart/M +Evey/M +Evie/M +Evin/M +Evita/M +Evonne/M +Evvie/M +Evvy/M +Evy/M +Evyn/M +Ewan/M +Eward/M +Ewart/M +Ewell/M +Ewen/M +Ewing/M +Excalibur/M +Excedrin/M +Excellency/MS +Exchequer/SM +Exeter/M +Exodus/M +Exxon/M +Eyck/M +Eyde/M +Eydie/M +Eyre/M +Eysenck/M +Ezechiel/M +Ezekiel/M +Ezequiel/M +Eziechiele/M +Ezmeralda/M +Ezra/M +Ezri/M +F +F's +FAA +FAQ/SM +FBI +FCC +FD +FDA +FDIC +FDR/M +FHA +FICA +FIFO +FL +FM +FNMA/M +FOFL +FORTH/M +FORTRAN +FPO +FSLIC +FTC +FTP +FUD +FWD +FY +FYI +Fabe/MR +Faber/M +Fabergé/M +Fabian/S +Fabiano/M +Fabien/M +Fabio/M +Fae/M +Faeroe/M +Fafnir/M +Fagin/M +Fahd/M +Fahrenheit/S +Faina/M +Fair/M +Fairbanks +Fairchild/M +Fairfax/M +Fairfield/M +Fairleigh/M +Fairlie/M +Fairmont/M +Fairport/M +Fairview/M +Faisal/M +Faisalabad +Faith/M +Falito/M +Falk/M +Falkland/MS +Falkner/M +Fallon/M +Fallopian/M +Falstaff/M +Falwell/M +Fan/M +Fanchette/M +Fanchon/M +Fancie/M +Fancy/M +Fanechka/M +Fania/M +Fanni/M +Fannie/M +Fanny/SM +Fanya/M +Far/MY +Fara/M +Faraday/M +Farah/M +Farand/M +Farber/M +Fargo/M +Farica/M +Farkas/M +Farlay/M +Farlee/M +Farleigh/M +Farley/M +Farlie/M +Farly/M +Farmer/M +Farmington/M +Farr/M +Farra/M +Farragut/M +Farrah/M +Farrakhan/M +Farrand/M +Farrel/M +Farrell/M +Farris/M +Fascism's +Fascist's +Fassbinder/M +Fates +Father/SM +Fatima/M +Faulkner/M +Faulknerian +Faun/M +Faunie/M +Fauntleroy/M +Faust/M +Faustian +Faustina/M +Faustine/M +Faustino/M +Faustus/M +Fawkes/M +Fawn/M +Fawne/M +Fawnia/M +Fax/M +Fay/M +Faydra/M +Faye/M +Fayette/M +Fayetteville/M +Fayina/M +Fayre/M +Fayth/M +Faythe/M +Fe/M +Featherman/M +Feb/M +February/MS +Fed/SM +FedEx/M +Federal/S +Federalist +Federica/M +Federico/M +Fedora/M +Fee/M +Felder/M +Feldman/M +Felecia/M +Felic/M +Felicdad/M +Felice/M +Felicia/M +Felicio/M +Felicity/M +Felicle/M +Felike/M +Feliks/M +Felipa/M +Felipe/M +Felisha/M +Felita/M +Felix/M +Feliza/M +Felizio/M +Fellini/M +Fenelia/M +Fenian/M +Fenwick/M +Feodor/M +Feodora/M +Ferber/M +Ferd/M +Ferdie/M +Ferdinand/M +Ferdinanda/M +Ferdinande/M +Ferdinando/M +Ferdy/M +Fergus/M +Ferguson/M +Ferlinghetti/M +Fermat/M +Fermi/M +Fern/M +Fernanda/M +Fernande/M +Fernandez/M +Fernandina/M +Fernando/M +Ferne/M +Ferrari/M +Ferraro/M +Ferreira/M +Ferrel/M +Ferrell/M +Ferrer/M +Ferris +Fess/M +Fey/M +Feynman/M +Fez/M +Fiann/M +Fianna/M +Fiat/M +Fiberglas/M +Fibonacci/M +Fichte/M +Fidel/M +Fidela/M +Fidelia/M +Fidelio/M +Fidelity/M +Fido/M +Fidole/M +Field/MGS +Fielding/M +Fifi/M +Fifine/M +Figaro/M +Figueroa/M +Fiji/M +Fijian/SM +Filbert/M +Filberte/M +Filberto/M +Filia/M +Filide/M +Filip/M +Filipino/SM +Filippa/M +Filippo/M +Fillmore/M +Filmer/M +Filmore/M +Filofax/S +Fin/M +Fina/M +Finch/M +Findlay/M +Findley/M +Finland/M +Finlay/M +Finley/M +Finn/MS +Finnbogadottir/M +Finnegan/M +Finnish +Fiona/M +Fionna/M +Fionnula/M +Fiorello/M +Fiorenze/M +Fiori/M +Firefox/M +Firestone/M +Fischbein/M +Fischer/M +Fisher/M +Fishkill/M +Fisk/M +Fiske/M +Fitch/M +Fitchburg/M +Fitz/M +Fitzgerald/M +Fitzpatrick/M +Fitzroy/M +Fizeau/M +Fla/M +Flanagan/M +Flanders/M +Flatt/M +Flaubert/M +Fledermaus/M +Fleischer/M +Fleischman/M +Fleisher/M +Flem/JGM +Fleming/M +Flemish/GDSM +Flemished/M +Flemishing/M +Flemming/M +Fletch/MR +Fletcher/M +Fleur/M +Fleurette/M +Flin/M +Flinn/M +Flint/M +Flintstones +Flo/M +Flor/M +Flora/M +Florance/M +Flore/SM +Florella/M +Florence/M +Florencia/M +Florentia/M +Florentine/S +Florenza/M +Florette/M +Flori/SM +Floria/M +Florian/M +Florida/M +Floridan/S +Floridian/S +Florie/M +Florina/M +Florinda/M +Florine/M +Florri/M +Florrie/M +Florry/M +Flory/M +Flossi/M +Flossie/M +Flossy/M +Flowers +Floyd/M +Flss/M +Flynn/M +Fm/M +Foch/M +Fokker/M +Foley/M +Folsom +Fomalhaut/M +Fonda/M +Fons +Fonsie/M +Fontaine/M +Fontainebleau/M +Fontana/M +Fonz/M +Fonzie/M +Foote/M +Forbes/M +Ford/M +Fordham/M +Foreman/M +Forest/MR +Forester/M +Formica/MS +Formosa/M +Formosan +Forrest/RM +Forrester/M +Forster/M +Fortaleza/M +Fortran/M +Foss/M +Foster/M +Foucault/M +Fourier/M +Fourth +Fourths +Fowler/M +Fox/MS +Foxhall/M +Fr/MD +Fragonard/M +Fran/MS +Francaise/M +France/MS +Francene/M +Francesca/M +Francesco/M +Franchot/M +Francie/M +Francine/M +Francis +Francisca/M +Franciscan/MS +Francisco/M +Franciska/M +Franciskus/M +Franck/M +Francklin/M +Francklyn/M +Franco/M +Francois/M +Francoise/M +Francyne/M +Frank/SM +Frankel/M +Frankenstein/MS +Frankford/M +Frankfort/M +Frankfurt/RM +Frankfurter/M +Frankie/M +Frankish/M +Franklin/M +Franklyn/M +Franky/M +Franni/M +Frannie/M +Franny/M +Fransisco/M +Frants/M +Franz/NM +Franzen/M +Frasco/M +Fraser/M +Frasier/M +Frasquito/M +Frau/MN +Fraulein/S +Frayda/M +Frayne/M +Fraze/MR +Frazer/M +Frazier/M +Fred/M +Freda/M +Freddi/M +Freddie/M +Freddy/M +Fredek/M +Fredelia/M +Frederic/M +Frederica/M +Frederich/M +Frederick/MS +Fredericka/M +Frederico/M +Fredericton/M +Frederigo/M +Frederik/M +Frederique/M +Fredholm/M +Fredi/M +Fredia/M +Fredra/M +Fredric/M +Fredrick/M +Fredrickson/M +Fredrika/M +Free/M +Freedman/M +Freeland/M +Freeman/M +Freemason/SM +Freemasonry/MS +Freemon/M +Freeport/M +Freetown/M +Freida/M +Fremont/M +French/MDSG +Frenchman/M +Frenchmen +Frenchwoman/M +Frenchwomen +Freon/SM +Fresnel/M +Fresno/M +Freud/M +Freudian/S +Frey/M +Freya/M +Fri/M +Frick/M +Friday/SM +Frieda/M +Friedan/M +Friederike/M +Friedman/M +Friedrich/M +Friedrick/M +Frigga/M +Frigidaire/M +Frisbee/MS +Frisco/M +Frisian/SM +Frito/M +Fritz/M +Frobisher/M +Froissart/M +Fromm/M +Frontenac/M +Frost/M +Frostbelt/M +Fruehauf/M +Frunze/M +Fry/M +Frye/M +Fuchs/M +Fuentes/M +Fugger/M +Fuji/M +Fujitsu/M +Fujiyama +Fukuoka/M +Fulani/M +Fulbright/M +Fuller/M +Fullerton/M +Fulton/M +Fulvia/M +Funafuti +Fundy/M +Fushun/M +Fuzhou/M +Fuzzbuster/M +G's +G/B +GA +GAO +GB +GDP +GE/M +GED +GHQ +GHz +GI +GIGO +GM +GMT +GNOME/M +GNP +GOP +GOTO/MS +GP +GPA +GPO +GPSS +GSA +GU +GUI +Ga/M +Gabbey/M +Gabbi/M +Gabbie/M +Gabby/M +Gabe/M +Gabey/M +Gabi/M +Gabie/M +Gable/M +Gabon/M +Gabonese +Gaborone/M +Gabriel/M +Gabriela/M +Gabriele/M +Gabriell/M +Gabriella/M +Gabrielle/M +Gabriellia/M +Gabriello/M +Gabrila/M +Gaby/M +Gacrux/M +Gadsden/M +Gae/M +Gaea/M +Gael/SM +Gaelan/M +Gaelic/M +Gagarin/M +Gage/M +Gail/M +Gaile/M +Gaines/M +Gainesville/M +Gainsborough/M +Gaithersburg/M +Gal/MN +Galahad/MS +Galapagos/M +Galatea/M +Galatia/M +Galatians/M +Galaxy/M +Galbraith/M +Galbreath/M +Gale/M +Galen/M +Galibi/M +Galilean/MS +Galilee/M +Galileo/M +Galina/M +Gall/M +Gallagher/M +Gallard/M +Gallegos/M +Gallic +Gallicism/SM +Galloway/M +Gallup/M +Galois/M +Galsworthy/M +Galvan/M +Galvani/M +Galven/M +Galveston/M +Galvin/M +Gama/M +Gamaliel/M +Gambia/M +Gambian/S +Gamble/M +Gamow/M +Gan/M +Gandhi/M +Gandhian +Ganges/M +Gangtok/M +Gannie/M +Gannon/M +Ganny/M +Gantry/M +Ganymede/M +Gar/MH +Garald/M +Garbo/M +Garcia/M +Gard/M +Gardener/M +Gardie/M +Gardiner/M +Gardner/M +Gardy/M +Gare/MH +Garek/M +Gareth/M +Garey/M +Garfield/M +Garfunkel/M +Gargantua/M +Garibaldi/M +Garik/M +Garland/M +Garner/M +Garnet/M +Garnett/M +Garnette/M +Garold/M +Garrard/M +Garrek/M +Garret/M +Garreth/M +Garrett/M +Garrick/M +Garrik/M +Garrison/M +Garrot/M +Garrott/M +Garry/M +Garth/M +Garv/M +Garvey/M +Garvin/M +Garvy/M +Garwin/M +Garwood/M +Gary/M +Garza/M +Gascony/M +Gaspar/M +Gaspard/M +Gasparo/M +Gasper/M +Gasser/M +Gasset/M +Gaston/M +Gates +Gatlinburg/M +Gatling/M +Gatorade/M +Gatsby/M +Gatun/M +Gauguin/M +Gaul/MS +Gaulish/M +Gaulle/M +Gaultiero/M +Gauntley/M +Gauss/M +Gaussian +Gautama/M +Gauthier/M +Gautier/M +Gav/MN +Gavan/M +Gaven/M +Gavin/M +Gavra/M +Gavrielle/M +Gawain/M +Gawen/M +Gay/M +Gaye/M +Gayel/M +Gayelord/M +Gayla/M +Gayle/RM +Gayleen/M +Gaylene/M +Gayler/M +Gaylor/M +Gaylord/M +Gaynor/M +Gaza/M +Gaziantep/M +Gd/M +Gdansk/M +Ge/M +Gearalt/M +Gearard/M +Geary/M +Gehenna/M +Gehrig/M +Geiger/M +Geigy/M +Gelya/M +Gemini/SM +Gemma/M +Gen/M +Gena/M +Genaro/M +Gene/M +Genesco/M +Genesis/M +Genet/M +Geneva/M +Genevieve/M +Genevra/M +Genghis/M +Genia/M +Genna/M +Genni/M +Gennie/M +Gennifer/M +Genny/M +Geno/M +Genoa/SM +Genovera/M +Gentile's +Gentry/M +Genvieve/M +Geo/M +Geoff/M +Geoffrey/M +Geoffry/M +Georas/M +Geordie/M +Georg/M +George/SM +Georgeanna/M +Georgeanne/M +Georgena/M +Georgeta/M +Georgetown/M +Georgetta/M +Georgette/M +Georgi/M +Georgia/M +Georgian/S +Georgiana/M +Georgianna/M +Georgianne/M +Georgie/M +Georgina/M +Georgine/M +Georgy/M +Ger/M +Gerald/M +Geralda/M +Geraldine/M +Gerard/M +Gerardo/M +Gerber/M +Gerda/M +Gerek/M +Gerhard/M +Gerhardine/M +Gerhardt/M +Geri/M +Gerianna/M +Gerianne/M +Gerick/M +Gerik/M +Geritol/M +Gerladina/M +Germain/M +Germaine/M +German/SM +Germana/M +Germania/M +Germanic/M +Germantown/M +Germany/M +Germayne/M +Gerome/M +Geronimo/M +Gerrard/M +Gerri/M +Gerrie/M +Gerrilee/M +Gerry/M +Gershwin/MS +Gert/M +Gerta/M +Gerti/M +Gertie/M +Gertrud/M +Gertruda/M +Gertrude/M +Gertrudis/M +Gerty/M +Gery/M +Gestapo/SM +Gethsemane/M +Getty/M +Gettysburg/M +Gewürztraminer +Ghana/M +Ghanaian/MS +Ghanian's +Ghats/M +Ghent/M +Gherardo/M +Ghibelline/M +Giacinta/M +Giacobo/M +Giacometti/M +Giacomo/M +Giacopo/M +Gian/M +Giana/M +Gianina/M +Gianna/M +Gianni/M +Giannini/M +Giauque/M +Giavani/M +Gib/M +Gibb/MS +Gibbie/M +Gibbon/M +Gibby/M +Gibraltar/MS +Gibson/M +Giddings/M +Gide/M +Gideon/MS +Gielgud/M +Gienah/M +Giff/RM +Giffard/M +Giffer/M +Giffie/M +Gifford/M +Giffy/M +Gigi/M +Gil/MY +Gila/M +Gilbert/M +Gilberta/M +Gilberte/M +Gilbertina/M +Gilbertine/M +Gilberto/M +Gilbertson/M +Gilburt/M +Gilchrist/M +Gilda/M +Gilead/M +Gilemette/M +Giles +Gilgamesh/M +Gilkson/M +Gill/M +Gillan/M +Gilles +Gillespie/M +Gillette/M +Gilli/M +Gilliam/M +Gillian/M +Gillie/M +Gilligan/M +Gilly/M +Gilmore/M +Gimbel/M +Gina/M +Ginelle/M +Ginevra/M +Ginger/M +Gingrich/M +Ginni/M +Ginnie/M +Ginnifer/M +Ginny/M +Gino/M +Ginsberg/M +Ginsburg/M +Gioconda/M +Giordano/M +Giorgi/M +Giorgia/M +Giorgio/M +Giorgione/M +Giotto/M +Giovanna/M +Giovanni/M +Gipsy's +Giralda/M +Giraldo/M +Giraud/M +Giraudoux/M +Gisela/M +Giselbert/M +Gisele/M +Gisella/M +Giselle/M +Gish/M +Giuditta/M +Giulia/M +Giuliano/M +Giulietta/M +Giulio/M +Giuseppe/M +Giustina/M +Giustino/M +Giusto/M +Giza/M +Gizela/M +Gk/M +Glad/M +Gladi/M +Gladstone/MS +Gladys +Glaser/M +Glasgow/M +Glass/M +Glastonbury/M +Glaswegian/S +Gleason/M +Gleda/M +Glen/M +Glenda/M +Glendale/M +Glenden/M +Glendon/M +Glenine/M +Glenn/M +Glenna/M +Glennie/M +Glennis/M +Glori/M +Gloria/M +Gloriana/M +Gloriane/M +Glory/M +Gloucester/M +Glover/M +Glyn/M +Glynda/M +Glynis/M +Glynn/M +Glynnis/M +Gnni/M +Gnostic/M +Gnosticism/M +Goa/M +Gobi/M +God/M +Godard/M +Godart/M +Goddard/M +Goddart/M +Godfree/M +Godfrey/M +Godfry/M +Godiva/M +Godot/M +Godspeed/S +Godthaab/M +Godunov/M +Godwin/M +Godzilla/M +Goebbels/M +Goering/M +Goethals/M +Goethe/M +Goff/M +Gog/M +Gogh/M +Gogol/M +Goiania/M +Golan/M +Golconda/M +Golda/M +Goldarina/M +Goldberg/M +Golden/M +Goldi/M +Goldia/M +Goldie/M +Goldilocks/M +Goldina/M +Golding/M +Goldman/M +Goldsmith/M +Goldstein/M +Goldwater/M +Goldwyn/M +Goldy/M +Goleta/M +Golgotha/M +Goliath/M +Goliaths +Gomez/M +Gomorrah/M +Gompers/M +Gondwanaland/M +Gonzales/M +Gonzalez/M +Gonzalo/M +Goober/M +Good/M +Goodman/M +Goodrich/M +Goodwin/M +Goodyear/M +Google/M +Gopher +Goran/M +Goraud/M +Gorbachev +Gordan/M +Gorden/M +Gordian/M +Gordie/M +Gordimer/M +Gordon/M +Gordy/M +Gore/M +Goren/M +Gorey/M +Gorgas +Gorgon/M +Gorgonzola/M +Gorham/M +Gorky/M +Gospel/SM +Goth/M +Gotham/M +Gothart/M +Gothic/S +Gothicism/M +Goths +Gottfried/M +Goucher/M +Gouda/SM +Gould/M +Gounod/M +Governor +Goya/M +Gr/M +Gracchus/M +Grace/M +Graceland/M +Gracia/M +Gracie/M +Graciela/M +Gradeigh/M +Gradey/M +Grady/M +Graehme/M +Graeme/M +Graff/M +Graffias/M +Grafton/M +Graham/M +Grahame/M +Graig/M +Grail/SM +Gram/M +Grammy/S +Grampians +Gran/M +Granada/M +Grange/MR +Granger/M +Grannie/M +Granny/M +Grant/M +Grantham/M +Granthem/M +Grantley/M +Granville/M +Grass/M +Grata/M +Gratia/M +Gratiana/M +Graves/M +Gray/M +Grayce/M +Grayson/M +Grazia/M +Grecian/S +Greece/M +Greek/SM +Greeley/M +Green/M +Greenberg/M +Greenblatt/M +Greenbriar/M +Greene/M +Greenfeld/M +Greenfield/M +Greenland/M +Greenpeace/M +Greensboro/M +Greensleeves/M +Greensville/M +Greentree/M +Greenville/M +Greenwich/M +Greer/M +Greg/M +Gregg/M +Greggory/M +Gregoire/M +Gregoor/M +Gregor/M +Gregorian +Gregorio/M +Gregorius/M +Gregory/M +Grenada/M +Grenadian/S +Grenadines +Grendel/M +Grenier/M +Grenoble/M +Grenville/M +Gresham/M +Greta/M +Gretal/M +Gretchen/M +Grete/M +Gretel/M +Grethel/M +Gretna/M +Gretta/M +Gretzky/M +Grey/M +Grieg/M +Grier/M +Griff/M +Griffie/M +Griffin/M +Griffith/M +Griffy/M +Grimaldi/M +Grimes +Grimm/M +Grinch/M +Gris/M +Griselda/M +Grissel/M +Griswold/M +Griz/M +Gromyko/M +Groot/M +Gropius/M +Gross +Grosset/M +Grossman/M +Grosvenor/M +Grosz/M +Grotius/M +Groton/M +Grove/RM +Grover/M +Grumman/M +Grundy/M +Grus/M +Grusky/M +Gruyeres +Gruyère +Grünewald/M +Guadalajara/M +Guadalcanal/M +Guadalquivir/M +Guadalupe/M +Guadeloupe/M +Guallatiri/M +Gualterio/M +Guam/M +Guamanian/SM +Guangzhou +Guantanamo/M +Guarani/M +Guardia/M +Guarnieri/M +Guatemala/M +Guatemalan/S +Guayaquil/M +Gucci/M +Guelph/M +Guendolen/M +Guenevere/M +Guenna/M +Guenther/M +Guernsey/SM +Guerra/M +Guerrero/M +Guevara/M +Guggenheim/M +Guglielma/M +Guglielmo/M +Guhleman/M +Gui/M +Guiana/M +Guido/M +Guilbert/M +Guillaume/M +Guillema/M +Guillemette/M +Guillermo/M +Guinea/M +Guinean/S +Guinevere/M +Guinna/M +Guinness/M +Guiyang +Guizot/M +Gujarat/M +Gujarati/M +Gujranwala/M +Gullah/M +Gulliver/M +Gun/M +Gunar/M +Gunderson/M +Gunilla/M +Gunnar/M +Gunner/M +Guntar/M +Gunter/M +Gunther/M +Guofeng/M +Gupta/M +Gurkha/M +Gus/M +Gusella/M +Guss +Gussi/M +Gussie/M +Gussy/M +Gusta/M +Gustaf/M +Gustafson/M +Gustav/M +Gustave/M +Gustavo/M +Gustavus/M +Gusti/M +Gustie/M +Gusty/M +Gutenberg/M +Guthrey/M +Guthrie/M +Guthry/M +Gutierrez/M +Guy/M +Guyana/M +Guyanese +Guzman/M +Gwalior/M +Gwen/M +Gwendolen/M +Gwendolin/M +Gwendoline/M +Gwendolyn/M +Gweneth/M +Gwenette/M +Gwenneth/M +Gwenni/M +Gwennie/M +Gwenny/M +Gwenora/M +Gwenore/M +Gwyn/M +Gwyneth/M +Gwynne/M +Gypsy/SM +Gödel/M +Göteborg/M +H +H's +HBO/M +HDTV +HF +HHS +HI +HIV +HM +HMO +HMS +HOV +HP +HQ +HR +HRH +HS +HST +HTML +HTTP +HUD +Ha/M +Haag/M +Haas/M +Habakkuk/M +Haber/M +Haberman/M +Habib/M +Hackett/M +Had/M +Hadamard/M +Hadar/M +Haddad/M +Hades +Hadlee/M +Hadleigh/M +Hadley/M +Hadria/M +Hadrian/M +Hafiz/M +Hagan/M +Hagar/M +Hagen/M +Hager/M +Haggai/M +Hagiographa/M +Hagstrom/M +Hague/M +Hahn/M +Haifa/M +Hailee/M +Hailey/M +Haily/M +Haiphong/M +Haiti/M +Haitian/S +Hakeem/M +Hakim/M +Hakka/M +Hakluyt/M +Hal/SMY +Haldane/M +Hale/M +Haleakala/M +Haleigh/M +Halette/M +Haley/M +Hali/M +Halie/M +Halifax/M +Halimeda/M +Hall/M +Halley/M +Halli/M +Hallie/M +Hallinan/M +Hallmark/M +Halloween/MS +Hallsy/M +Hally/M +Halpern/M +Halsey/M +Halsy/M +Ham/M +Hamal/M +Haman/M +Hamburg/MS +Hamel/M +Hamey/M +Hamhung/M +Hamid/M +Hamil/M +Hamilcar/M +Hamilton/M +Hamiltonian/MS +Hamish/M +Hamitic/M +Hamlen/M +Hamlet/M +Hamlin/M +Hammad/M +Hammarskjold/M +Hammerstein/M +Hammett/M +Hammond/M +Hammurabi/M +Hamnet/M +Hampshire/M +Hampton/M +Hamsun/M +Han/SM +Hana/M +Hanan/M +Hancock/M +Handel/M +Handy/M +Haney/M +Hangul/M +Hangzhou +Hank/M +Hankel/M +Hanna/M +Hannah/M +Hanni/MS +Hannibal/M +Hannie/M +Hanny/M +Hanoi/M +Hanover/M +Hanoverian +Hans/N +Hansel/M +Hansen/M +Hansiain/M +Hanson/M +Hanuka/S +Hanukkah/M +Hanukkahs +Hapgood/M +Happy/M +Hapsburg/M +Harald/M +Harare +Harbert/M +Harbin/M +Harcourt/M +Hardin/M +Harding/M +Hardy/M +Hargreaves/M +Harlan/M +Harland/M +Harlem/M +Harlen/M +Harlene/M +Harlequin +Harley/M +Harli/M +Harlie/M +Harlin/M +Harlow/M +Harman/M +Harmon/M +Harmonia/M +Harmonie/M +Harmony/M +Harold/M +Haroun/M +Harp/MR +Harper/M +Harpy/SM +Harrell/M +Harri/SM +Harrie/M +Harriet/M +Harriett/M +Harrietta/M +Harriette/M +Harrington/M +Harriot/M +Harriott/M +Harrisburg/M +Harrison/M +Harrisonburg/M +Harry/M +Hart/M +Harte/M +Hartford/M +Hartley/M +Hartline/M +Hartman/M +Hartwell/M +Harv/M +Harvard/M +Harvey/MS +Harwell/M +Harwilll/M +Hasbro/M +Hasheem/M +Hashim/M +Hasidim +Haskel/M +Haskell/M +Haskins/M +Haslett/M +Hastie/M +Hastings/M +Hasty/M +Hatchure/M +Hatfield/M +Hathaway/M +Hatteras/M +Hatti/M +Hattie/M +Hatty/M +Haugen/M +Hauptmann/M +Hausa/M +Hausdorff/M +Hauser/M +Havana/SM +Havarti +Havel/M +Haven/M +Haw +Hawaii/M +Hawaiian/S +Hawking +Hawkins/M +Hawley/M +Hawthorne/M +Hay/SM +Hayden/M +Haydn/M +Haydon/M +Hayes +Hayley/M +Haynes +Hayward/M +Haywood/M +Hayyim/M +Haze/M +Hazel/M +Hazlett/M +Hazlitt/M +He/M +Head/M +Heall/M +Hearst/M +Heartwood/M +Heath/MR +Heather/M +Heathkit/M +Heathman/M +Heaviside/M +Heb/M +Hebe/M +Hebert/M +Hebraic +Hebraism/MS +Hebrew/SM +Hebrides/M +Hecate/M +Hector/M +Hecuba/M +Heda/M +Hedda/M +Heddi/M +Heddie/M +Hedi/M +Hedvig/M +Hedvige/M +Hedwig/M +Hedwiga/M +Hedy/M +Heep/M +Hefner/M +Hegel/M +Hegelian +Hegira/M +Heida/M +Heidegger/M +Heidelberg/M +Heidi/M +Heidie/M +Heifetz/M +Heimlich/M +Heindrick/M +Heine/M +Heineken/M +Heinlein/M +Heinrich/M +Heinrick/M +Heinrik/M +Heinz/M +Heinze/M +Heisenberg/M +Heiser/M +Hejira's +Helaina/M +Helaine/M +Helen/M +Helena/M +Helene/M +Helenka/M +Helga/M +Helge/M +Helicon/M +Heliopolis/M +Helios/M +Hell's +Hellene/SM +Hellenic +Hellenism/MS +Hellenist/MS +Hellenistic +Hellenization/M +Hellenize +Heller/M +Hellespont/M +Helli/M +Hellman/M +Helmholtz/M +Helmut/M +Helsa/M +Helsinki/M +Helvetian/S +Helvetius/M +Helyn/M +Hemingway/M +Hench/M +Henderson/M +Hendrick/SM +Hendrickson/M +Hendrik/M +Hendrika/M +Hendrix/M +Henka/M +Henley/M +Hennessey/M +Henri/M +Henrie/M +Henrieta/M +Henrietta/M +Henriette/M +Henrik/M +Henry/M +Henryetta/M +Hensley/M +Henson/M +Hepburn/M +Hephaestus/M +Hephzibah/M +Hepplewhite +Hera/M +Heracles/M +Heraclitus/M +Herb/M +Herbart/M +Herbert/M +Herbie/M +Herby/M +Herc/M +Herculaneum/M +Hercule/MS +Herculean +Herculie/M +Herder/M +Hereford/SM +Heriberto/M +Herkimer/M +Herman/M +Hermann/M +Hermaphroditus/M +Hermes +Hermia/M +Hermie/M +Hermina/M +Hermine/M +Herminia/M +Hermione/M +Hermite/M +Hermon/M +Hermosa/M +Hermosillo/M +Hermy/M +Hernandez/M +Hernando/M +Herod/M +Herodotus/M +Herold/M +Herr/MG +Herrera/M +Herrick/M +Herring/M +Herrington/M +Hersch/M +Herschel/M +Hersey/M +Hersh/M +Hershel/M +Hershey/M +Herta/M +Hertha/M +Hertz/M +Hertzog/M +Hertzsprung/M +Herve/M +Hervey/M +Herzegovina/M +Herzl/M +Hesiod/M +Hesperus/M +Hess/M +Hesse/M +Hessian/MS +Hester/M +Hesther/M +Hestia/M +Heston/M +Hetti/M +Hettie/M +Hetty/M +Heublein/M +Heusen/M +Heuser/M +Hew/M +Hewe/M +Hewet/M +Hewett/M +Hewie/M +Hewitt/M +Hewlett/M +Heyerdahl/M +Heywood/M +Hezekiah/M +Hf/M +Hg/M +Hi/M +Hialeah/M +Hiawatha/M +Hibernia/M +Hibernian/S +Hickey/SM +Hickman/M +Hickok/M +Hicks/M +Hieronymus/M +Higashiosaka +Higgins/M +Highfield/M +Highlander/SM +Highlands +Highness/M +Hilario/M +Hilarius/M +Hilary/M +Hilbert/M +Hilda/M +Hildagard/M +Hildagarde/M +Hilde/M +Hildebrand/M +Hildegaard/M +Hildegarde/M +Hildy/M +Hill/M +Hillard/M +Hillary/M +Hillcrest/M +Hillel/M +Hillery/M +Hilliard/M +Hilliary/M +Hillie/M +Hillier/M +Hillsboro/M +Hillsdale/M +Hilly/RM +Hillyer/M +Hilton/M +Himalaya/MS +Himalayan/S +Himmler/M +Hinayana/M +Hinda/M +Hindemith/M +Hindenburg/M +Hindi/M +Hindu/MS +Hinduism/SM +Hindustan/M +Hindustani/MS +Hines/M +Hinkle/M +Hinsdale/M +Hinton/M +Hinze/M +Hipparchus/M +Hippocrates/M +Hippocratic +Hiram/M +Hirey/M +Hirohito/M +Hiroshi/M +Hiroshima/M +Hirsch/M +Hispanic/SM +Hispaniola/M +Hiss/M +Hitachi/M +Hitchcock/M +Hitler/SM +Hittite/SM +Hmong +Ho/M +Hobard/M +Hobart/M +Hobbes/M +Hobbs/M +Hobday/M +Hobey/M +Hobie/M +Hoboken/M +Hockney/M +Hodge/MS +Hodgkin/M +Hoebart/M +Hoff/M +Hoffa/M +Hoffman/M +Hofstadter/M +Hogan/M +Hogarth/M +Hohenlohe/M +Hohenstaufen/M +Hohenzollern/M +Hohhot/M +Hokkaido/M +Hokusai/M +Holbein/M +Holbrook/M +Holcomb/M +Holden/M +Holder/M +Holiday/M +Holiness/MS +Holland/RMSZ +Hollandaise/M +Hollander/M +Hollerith/M +Holley/M +Holli/SM +Hollie/M +Hollister/M +Holloway/M +Holly/M +Hollyanne/M +Hollywood/M +Holm/M +Holman/M +Holmes +Holocaust +Holocene +Holst/M +Holstein/MS +Holt/M +Holyoke/M +Holzman/M +Hom/MR +Homer/M +Homere/M +Homeric +Homerus/M +Hon/M +Honda/M +Hondo/M +Honduran/S +Honduras/M +Honecker/M +Honey/M +Honeywell/M +Honiara/M +Honolulu/M +Honor/M +Honoria/M +Honshu/M +Hood/M +Hooke/MR +Hooker/M +Hooper/M +Hoosier/SM +Hoover/MS +Hope/M +Hopewell/M +Hopi/SM +Hopkins/M +Hopkinsian/M +Hopper/M +Horace/M +Horacio/M +Horatia/M +Horatio/M +Horatius/M +Hormel/M +Hormuz/M +Horn/M +Hornblower/M +Horne/M +Horowitz/M +Horst/M +Hort/MN +Horten/M +Hortense/M +Hortensia/M +Horton/M +Horus/M +Hosea/M +Host/MS +Hottentot/SM +Houdaille/M +Houdini/M +House/M +Housman/M +Houston/M +Houyhnhnm/M +Howard/M +Howe/M +Howell/MS +Howey/M +Howie/M +Howrah/M +Hoyle/SM +Hoyt/M +Hrothgar/M +Hts/M +Huang/M +Hubbard/M +Hubble/M +Hube/RM +Huber/M +Hubert/M +Huberto/M +Hubey/M +Hubie/M +Huck/M +Huddersfield/M +Hudson/M +Huerta/M +Huey/M +Huff/M +Huffman/M +Huggins +Hugh/MS +Hughie/M +Hugibert/M +Hugo/M +Huguenot/SM +Hugues/M +Hui/M +Huitzilopitchli/M +Hulda/M +Hull/M +Humbert/M +Humberto/M +Humboldt/M +Hume/M +Humfrey/M +Humfrid/M +Humfried/M +Hummel/M +Humphrey/SM +Humpty/M +Humvee +Hun/MS +Hunfredo/M +Hung/M +Hungarian/MS +Hungary/M +Hunt/MR +Hunter/M +Huntington/M +Huntlee/M +Huntley/M +Huntsville/M +Hurlee/M +Hurleigh/M +Hurley/M +Huron/SM +Hurst/M +Hurwitz/M +Hus +Husain's +Husein/M +Hussein/M +Husserl/M +Huston/M +Hutchins/M +Hutchinson/M +Hutchison/M +Hutton/M +Hutu/M +Huxley/M +Huygens/M +Hy/M +Hyacinth/M +Hyacintha/M +Hyacinthe/M +Hyacinthia/M +Hyacinthie/M +Hyades +Hyannis/M +Hyatt/M +Hyde/M +Hyderabad/M +Hydra/M +Hyman/M +Hymen/M +Hymie/M +Hynda/M +Hyperion/M +Hyundai/M +Hz +Héloise/M +I +I'd +I'll +I'm +I've +IA +IBM/M +ICBM/S +ICC +ICU +ID +IDs +IE +IEEE +IL +IMF +IMHO +IMNSHO +IMO +IN +INRI +INS +INTERNET/M +IOU +IPA +IQ +IRA/S +IRS +ISBN +ISO +IT +ITCorp/M +ITT +ITcorp/M +IUD/S +IV +IVs +Ia/M +Iaccoca/M +Iago/M +Iain/M +Ian/M +Ianthe/M +Ibadan/M +Ibbie/M +Ibby/M +Iberia/M +Iberian/MS +Ibero/M +Ibo/M +Ibrahim/M +Ibsen/M +Icarus/M +Ice/M +Iceland/MRZ +Icelander/M +Icelandic +Ichabod/M +Ida/M +Idaho/MS +Idahoan/S +Idahoes +Idalia/M +Idalina/M +Idaline/M +Idell/M +Idelle/M +Idette/M +Ieyasu/M +Ifni/M +Iggie/M +Iggy/M +Ignace/M +Ignacio/M +Ignacius/M +Ignatius/M +Ignaz/M +Ignazio/M +Igor/M +Iguassu/M +Ijsselmeer/M +Ike/M +Ikey/M +Ikhnaton/M +Ila/M +Ilaire/M +Ilario/M +Ileana/M +Ileane/M +Ilene/M +Iliad/MS +Ilise/M +Ilka/M +Ill/M +Illa/M +Illinois/M +Illinoisan/MS +Illuminati +Ilona/M +Ilsa/M +Ilse/M +Ilysa/M +Ilyse/M +Ilyssa/M +Ilyushin/M +Imagen/M +Imbrium/M +Imelda/M +Immanuel/M +Imogen/M +Imogene/M +Imojean/M +Imus/M +In/PM +Ina/M +Inc/M +Inca/SM +Inchon/M +Ind/M +Independence/M +India/M +Indian/SM +Indiana/M +Indianan/S +Indianapolis/M +Indianian/S +Indira/M +Indochina/M +Indochinese +Indonesia/M +Indonesian/S +Indore/M +Indra/M +Indus/M +Indy/SM +Ines +Inesita/M +Inessa/M +Inez/M +Informatica/M +Inga/M +Ingaberg/M +Ingaborg/M +Ingamar/M +Ingar/M +Inge/RM +Ingeberg/M +Ingeborg/M +Ingelbert/M +Ingemar/M +Inger/M +Ingersoll/M +Inglebert/M +Inglewood/M +Inglis/M +Ingmar/M +Ingra/M +Ingram/M +Ingres/M +Ingrid/M +Ingrim/M +Ingunna/M +Inigo/M +Inna/M +Innis/M +Innocent/M +Innsbruck/M +Inonu/M +Inquisition/MS +Inst +Intel/M +Intelsat/M +Interdata/M +Internationale/M +Internet/M +Interpol/M +Inuit/MS +Inverness/M +Io/M +Iolande/M +Iolanthe/M +Iona/M +Ionesco/M +Ionian/M +Ionic/S +Iorgo/MS +Iormina/M +Iosep/M +Iowa/SM +Iowan/S +Iphigenia/M +Ipswich/M +Iqbal/M +Iquitos/M +Ir/M +Ira/M +Iran/M +Iranian/MS +Iraq/M +Iraqi/SM +Ireland/M +Irena/M +Irene/M +Irina/M +Iris +Irish/R +Irishman/M +Irishmen +Irishwoman/M +Irishwomen +Irita/M +Irkutsk/M +Irma/M +Iroquoian/MS +Iroquois/M +Irrawaddy/M +Irtish/M +Irv/MG +Irvin/M +Irvine/M +Irving/M +Irwin/M +Irwinn/M +Isa/M +Isaac/SM +Isaak/M +Isabel/M +Isabelita/M +Isabella/M +Isabelle/M +Isac/M +Isacco/M +Isador/M +Isadora/M +Isadore/M +Isahella/M +Isaiah/M +Isak/M +Iscariot/M +Iseabal/M +Isfahan/M +Isherwood/M +Ishim/M +Ishmael/M +Ishtar/M +Isiah/M +Isiahi/M +Isidor/M +Isidora/M +Isidore/M +Isidoro/M +Isidro/M +Isis/M +Islam/SM +Islamabad/M +Islamic/S +Islandia/M +Ismael/M +Isobel/M +Isolde/M +Ispahan's +Ispell/M +Israel/MS +Israeli/MS +Israelite/SM +Issac/M +Issi/M +Issiah/M +Issie/M +Issy/M +Istanbul/M +Istvan/M +Isuzu/M +It +Itaipu/M +Ital/M +Italian/MS +Italianate/GSD +Italy/M +Itasca/M +Itch/M +Itel/M +Ithaca/M +Ithacan +Ito/M +Iva/M +Ivan/M +Ivanhoe/M +Ivar/M +Ive/MRS +Iver/M +Ivett/M +Ivette/M +Ivie/M +Ivonne/M +Ivor/M +Ivory/M +Ivy/M +Izaak/M +Izabel/M +Izak/M +Izanagi/M +Izanami/M +Izhevsk/M +Izmir/M +Izvestia/M +Izzy/M +J's +J/X +JCS +JD +JFK/M +JP +JV +Jabez/M +Jablonsky/M +Jacenta/M +Jacinda/M +Jacinta/M +Jacintha/M +Jacinthe/M +Jack/M +Jackelyn/M +Jacki/M +Jackie/M +Jacklin/M +Jacklyn/M +Jackman/M +Jackquelin/M +Jackqueline/M +Jackson/SM +Jacksonian +Jacksonville/M +Jacky/M +Jaclin/M +Jaclyn/M +Jacob/SM +Jacobean +Jacobi/M +Jacobian/M +Jacobin/M +Jacobite/M +Jacobo/M +Jacobs/N +Jacobsen/M +Jacobson/M +Jacobus +Jacoby/M +Jacquard/SM +Jacquelin/M +Jacqueline/M +Jacquelyn/M +Jacquelynn/M +Jacquenetta/M +Jacquenette/M +Jacques/M +Jacquetta/M +Jacquette/M +Jacqui/M +Jacquie/M +Jacuzzi/S +Jacynth/M +Jada/M +Jade/M +Jae/M +Jaeger/M +Jagger/M +Jaime/M +Jaimie/M +Jain/M +Jaine/M +Jainism/M +Jaipur/M +Jakarta/M +Jake/MS +Jakie/M +Jakob/M +Jamaal/M +Jamaica/M +Jamaican/S +Jamal/M +Jamar/M +Jame/MS +Jamel/M +Jameson/M +Jamestown/M +Jamesy/M +Jamey/M +Jami/M +Jamie/M +Jamil/M +Jamill/M +Jamima/M +Jamison/M +Jammal/M +Jammie/M +Jan/M +Jana/M +Janacek/M +Janaya/M +Janaye/M +Jandy/M +Jane/M +Janean/M +Janeczka/M +Janeen/M +Janeiro/M +Janek/M +Janel/M +Janela/M +Janell/M +Janella/M +Janelle/M +Janene/M +Janenna/M +Janessa/M +Janesville/M +Janet/M +Janeta/M +Janetta/M +Janette/M +Janeva/M +Janey/M +Jania/M +Janice/M +Janie/M +Janifer/M +Janina/M +Janine/M +Janis/M +Janith/M +Janka/M +Janna/M +Jannel/M +Jannelle/M +Jannie/M +Janos/M +Janot/M +Jansen/M +Jansenist/M +January/MS +Janus/M +Jany/M +Japan/M +Japanese/SM +Japura/M +Jaquelin/M +Jaquelyn/M +Jaquenetta/M +Jaquenette/M +Jaquith/M +Jarad/M +Jard/M +Jareb/M +Jared/M +Jarib/M +Jarid/M +Jarlsberg +Jarrad/M +Jarred/M +Jarret/M +Jarrett/M +Jarrid/M +Jarrod/M +Jarvis/M +Jase/M +Jasen/M +Jasmin/M +Jasmina/M +Jasmine/M +Jason/M +Jasper/M +Jastrow/M +Jasun/M +Java/SM +JavaScript/M +Javanese +Javier/M +Jaxartes/M +Jay/M +Jayapura/M +Jaycee/SM +Jaye/M +Jayme/M +Jaymee/M +Jaymie/M +Jayne/M +Jaynell/M +Jayson/M +Jazmin/M +Jdavie/M +Jean/M +Jeana/M +Jeane/M +Jeanelle/M +Jeanette/M +Jeanie/M +Jeanine/M +Jeanna/M +Jeanne/M +Jeannette/M +Jeannie/M +Jeannine/M +Jecho/M +Jed/M +Jedd/M +Jeddy/M +Jedediah/M +Jedi/M +Jedidiah/M +Jeep/S +Jeeves/M +Jeff/M +Jefferey/M +Jefferson/M +Jeffersonian/S +Jeffery/M +Jeffie/M +Jeffrey/SM +Jeffry/M +Jeffy/M +Jehanna/M +Jehoshaphat/M +Jehovah/M +Jehu/M +Jekyll/M +Jelene/M +Jello/M +Jemie/M +Jemima/M +Jemimah/M +Jemmie/M +Jemmy/M +Jen/M +Jena/M +Jenda/M +Jenelle/M +Jeni/M +Jenica/M +Jeniece/M +Jenifer/M +Jeniffer/M +Jenilee/M +Jenine/M +Jenkins/M +Jenn/RMJ +Jenna/M +Jennee/M +Jenner/M +Jennette/M +Jenni/M +Jennica/M +Jennie/M +Jennifer/M +Jennilee/M +Jennine/M +Jennings/M +Jenny/M +Jeno/M +Jens/N +Jensen/M +Jephthah/M +Jerad/M +Jerald/M +Jeralee/M +Jeramey/M +Jeramie/M +Jere/M +Jereme/M +Jeremiah/M +Jeremiahs +Jeremias/M +Jeremie/M +Jeremy/M +Jeri/M +Jericho/M +Jermain/M +Jermaine/M +Jermayne/M +Jeroboam/M +Jerold/M +Jerome/M +Jeromy/M +Jerri/M +Jerrie/M +Jerrilee/M +Jerrilyn/M +Jerrine/M +Jerrod/M +Jerrold/M +Jerrome/M +Jerry/M +Jerrylee/M +Jersey/MS +Jerusalem/M +Jervis/M +Jes +Jess/M +Jessa/M +Jessalin/M +Jessalyn/M +Jessamine/M +Jessamyn/M +Jesse/M +Jessee/M +Jesselyn/M +Jessey/M +Jessi/M +Jessica/M +Jessie/M +Jessika/M +Jessy/M +Jesuit/SM +Jesus +Jeth/M +Jethro/M +Jew/MS +Jewel/M +Jewell/MD +Jewelle/M +Jewelled/M +Jewess/SM +Jewish/P +Jewishness/MS +Jewry/MS +Jezebel/MS +Jidda/M +Jilin +Jill/M +Jillana/M +Jillane/M +Jillayne/M +Jilleen/M +Jillene/M +Jilli/M +Jillian/M +Jillie/M +Jilly/M +Jim/M +Jimenez/M +Jimmie/M +Jimmy/M +Jinan +Jinnah/M +Jinny/M +Jivaro/M +Jo/MY +Joachim/M +Joan/M +Joana/M +Joane/M +Joanie/M +Joann/M +Joanna/M +Joanne/SM +Joaquin/M +Job/SM +Jobey/M +Jobi/M +Jobie/M +Jobina/M +Jobrel/M +Joby/M +Jobye/M +Jobyna/M +Jocasta/M +Jocelin/M +Joceline/M +Jocelyn/M +Jocelyne/M +Jock/M +Jocko/M +Jodee/M +Jodi/M +Jodie/M +Jody/M +Joe/M +Joeann/M +Joel/MY +Joela/M +Joelie/M +Joell/MN +Joella/M +Joelle/M +Joellen/M +Joelly/M +Joellyn/M +Joelynn/M +Joesph/M +Joete/M +Joey/M +Jogjakarta/M +Johan/M +Johann/M +Johanna/M +Johannah/M +Johannes +Johannesburg/M +Johansen/M +Johanson/M +John/SM +Johna/MH +Johnath/M +Johnathan/M +Johnathon/M +Johnette/M +Johnie/M +Johnna/M +Johnnie/M +Johnny/M +Johns/N +Johnsen/M +Johnson/M +Johnston/M +Johnstown/M +Johny/M +Joice/M +Jojo/M +Jolee/M +Joleen/M +Jolene/M +Joletta/M +Joli/M +Jolie/M +Joliet's +Joline/M +Jolla/M +Jolson/M +Joly/M +Jolyn/M +Jolynn/M +Jon/M +Jonah/M +Jonahs +Jonas +Jonathan/M +Jonathon/M +Jone/MS +Jonell/M +Jones/S +Joni/MS +Jonie/M +Jonson/M +Joplin/M +Jordain/M +Jordan/M +Jordana/M +Jordanian/S +Jordanna/M +Jordon/M +Jorey/M +Jorgan/M +Jorge/M +Jorgensen/M +Jorgenson/M +Jori/M +Jorie/M +Jorrie/M +Jorry/M +Jory/M +Joscelin/M +Jose/M +Josee/M +Josef/M +Josefa/M +Josefina/M +Joseito/M +Joseph/M +Josepha/M +Josephina/M +Josephine/M +Josephs +Josephson/M +Josephus/M +Josey/M +Josh/M +Joshia/M +Joshua/M +Joshuah/M +Josi/M +Josiah/M +Josias/M +Josie/M +Josselyn/M +Josue/M +Josy/M +Joule/M +Jourdain/M +Jourdan/M +Jovanovich/M +Jove/M +Jovian +Joy/M +Joya/M +Joyan/M +Joyann/M +Joyce/M +Joycean +Joycelin/M +Joye/M +Joyner/M +Jozef/M +Jpn +Jr/M +Jsandye/M +Juan/M +Juana/M +Juanita/M +Juarez +Jubal/M +Jud/M +Judah/M +Judaic +Judaical +Judaism/SM +Judas/S +Judd/M +Jude/M +Judea/M +Judi/MH +Judie/M +Judith/M +Juditha/M +Judon/M +Judson/M +Judy/M +Judye/M +Juggernaut/M +Juieta/M +Jul/M +Jule/MS +Julee/M +Juli/M +Julia/M +Julian/M +Juliana/M +Juliane/M +Juliann/M +Julianna/M +Julianne/M +Julie/M +Julienne/M +Juliet/M +Julieta/M +Julietta/M +Juliette/M +Julina/M +Juline/M +Julio/M +Julissa/M +Julita/M +Julius/M +July/SM +Julys +Jun/M +June/MS +Juneau/M +Junette/M +Jung/M +Jungfrau/M +Jungian +Junia/M +Junie/M +Junina/M +Junior/S +Junker/SM +Juno/M +Jupiter/M +Jurassic +Jurua/M +Justen/M +Justice/M +Justin/M +Justina/M +Justine/M +Justinian/M +Justinn/M +Justino/M +Justis/M +Justus/M +Jutish +Jutland/M +Juvenal/M +Jyoti/M +K's +K/G +KB +KC +KDE/M +KGB +KIA +KKK +KO +KP +KS +KY +Kaaba/M +Kabuki +Kabul/M +Kacey/M +Kacie/M +Kacy/M +Kaddish/M +Kaela/M +Kafka/M +Kafkaesque +Kagoshima/M +Kahaleel/M +Kahlil/M +Kahlua/M +Kahn/M +Kai/M +Kaia/M +Kaifeng/M +Kaila/M +Kaile/M +Kailey/M +Kain/M +Kaine/M +Kaiser/SM +Kaitlin/M +Kaitlyn/M +Kaitlynn/M +Kaja/M +Kajar/M +Kakalina/M +Kala/M +Kalahari/M +Kalamazoo/M +Kalashnikov/M +Kalb/M +Kale/M +Kaleb/M +Kaleena/M +Kalgoorlie/M +Kali/M +Kalie/M +Kalil/M +Kalila/M +Kalina/M +Kalinda/M +Kalindi/M +Kalle/M +Kalli/M +Kally/M +Kalmyk +Kalvin/M +Kama/M +Kamchatka/M +Kamehameha/M +Kameko/M +Kamikaze/MS +Kamila/M +Kamilah/M +Kamillah/M +Kampala/M +Kampuchea/M +Kan/MS +Kanchenjunga/M +Kandace/M +Kandahar/M +Kandinsky/M +Kandy/M +Kane/M +Kania/M +Kankakee/M +Kannada/M +Kano/M +Kanpur/M +Kansan/S +Kansas +Kant/M +Kantian +Kanya/M +Kaohsiung/M +Kaplan/M +Kaposi/M +Kara/M +Karachi/M +Karaganda/M +Karakorum/M +Karalee/M +Karalynn/M +Karamazov/M +Kare/M +Karee/M +Kareem/M +Karel/M +Karen/M +Karena/M +Karenina/M +Kari/M +Karia/M +Karie/M +Karil/M +Karilynn/M +Karim/M +Karin/M +Karina/M +Karine/M +Kariotta/M +Karisa/M +Karissa/M +Karita/M +Karl/MNX +Karla/M +Karlan/M +Karlee/M +Karleen/M +Karlen/M +Karlene/M +Karlie/M +Karlik/M +Karlis +Karloff/M +Karlotta/M +Karlotte/M +Karly/M +Karlyn/M +Karmen/M +Karna/M +Karney/M +Karo/YM +Karol/M +Karola/M +Karole/M +Karolina/M +Karoline/M +Karoly/M +Karon/M +Karp/M +Karrah/M +Karrie/M +Karroo/M +Karry/M +Kary/M +Karyl/M +Karylin/M +Karyn/M +Kasai/M +Kasey/M +Kashmir/SM +Kaspar/M +Kasparov/M +Kasper/M +Kass +Kassandra/M +Kassey/M +Kassi/M +Kassia/M +Kassie/M +Kat/M +Kata/M +Katalin/M +Kate/M +Katee/M +Katelyn/M +Katerina/M +Katerine/M +Katey/M +Kath/M +Katha/M +Katharina/M +Katharine/M +Katharyn/M +Kathe/M +Katherina/M +Katherine/M +Katheryn/M +Kathi/M +Kathiawar/M +Kathie/M +Kathleen/M +Kathlin/M +Kathmandu +Kathrine/M +Kathryn/M +Kathryne/M +Kathy/M +Kathye/M +Kati/M +Katie/M +Katina/M +Katine/M +Katinka/M +Katleen/M +Katlin/M +Katmai/M +Katmandu's +Katowice/M +Katrina/M +Katrine/M +Katrinka/M +Katti/M +Kattie/M +Katuscha/M +Katusha/M +Katy/M +Katya/M +Katz/M +Kauai/M +Kauffman/M +Kaufman/M +Kaunas/M +Kaunda/M +Kawabata/M +Kawasaki/M +Kay/M +Kaycee/M +Kaye/M +Kayla/M +Kayle/M +Kaylee/M +Kayley/M +Kaylil/M +Kaylyn/M +Kayne/M +Kazakh/M +Kazakhstan +Kazan/M +Kazantzakis/M +Kb +Kean/M +Keane/M +Kearney/M +Keary/M +Keaton/M +Keats/M +Keck/M +Keefe/MR +Keefer/M +Keegan/M +Keelby/M +Keeley/M +Keelia/M +Keely/M +Keen/M +Keenan/M +Keene/M +Keewatin/M +Keillor/M +Keir/M +Keisha/M +Keith/M +Kelbee/M +Kelby/M +Kelcey/M +Kelci/M +Kelcie/M +Kelcy/M +Kele/M +Kelila/M +Kellby/M +Kellen/M +Keller/M +Kelley/M +Kelli/M +Kellia/M +Kellie/M +Kellina/M +Kellogg/M +Kellsie/M +Kelly/M +Kellyann/M +Kelsey/M +Kelsi/M +Kelsy/M +Kelt's +Kelvin/M +Kelwin/M +Kemerovo/M +Kemp/M +Kempis/M +Ken/M +Kendal/M +Kendall/M +Kendell/M +Kendra/M +Kendre/M +Kendrick/MS +Kenilworth/M +Kenmore/M +Kenn/M +Kenna/M +Kennan/M +Kennecott/M +Kennedy/M +Kenneth/M +Kennett/M +Kennie/M +Kennith/M +Kenny/M +Kenon/M +Kenosha/M +Kensington/M +Kent/M +Kenton/M +Kentuckian/S +Kentucky/M +Kenya/M +Kenyan/S +Kenyatta/M +Kenyon/M +Keogh/M +Keokuk/M +Kepler/M +Ker/M +Kerby/M +Kerensky/M +Keri/M +Keriann/M +Kerianne/M +Kerk/M +Kermie/M +Kermit/M +Kermy/M +Kern/M +Kerouac/M +Kerr/M +Kerri/M +Kerrie/M +Kerrill/M +Kerrin/M +Kerry/M +Kerstin/M +Kerwin/M +Kerwinn/M +Kesley/M +Keslie/M +Kessia/M +Kessiah/M +Kessler/M +Kettering/M +Ketti/M +Kettie/M +Ketty/M +Kev/MN +Kevan/M +Keven/M +Kevin/M +Kevina/M +Kevlar +Kevon/M +Kevorkian/M +Kevyn/M +Kewaskum/M +Kewaunee/M +Kewpie/M +Key/M +Keynes/M +Keynesian/M +Khabarovsk/M +Khachaturian/M +Khalid/M +Khalil/M +Khan/M +Kharkov/M +Khartoum/M +Khayyam/M +Khmer/M +Khoisan/M +Khomeini/M +Khorana/M +Khrushchev/SM +Khufu/M +Khulna/M +Khwarizmi/M +Khyber/M +Ki/M +Kiah/M +Kial/M +Kickapoo/M +Kidd/M +Kieffer/M +Kiel/M +Kiele/M +Kienan/M +Kierkegaard/M +Kiersten/M +Kieth/M +Kiev/M +Kigali/M +Kikelia/M +Kikuyu/M +Kilauea/M +Kile/M +Kiley/M +Kilian/M +Kilimanjaro/M +Killebrew/M +Killian/M +Killie/M +Killy/M +Kim/M +Kimball/M +Kimbell/M +Kimberlee/M +Kimberley/M +Kimberli/M +Kimberly/M +Kimberlyn/M +Kimble/M +Kimbra/M +Kimmi/M +Kimmie/M +Kimmy/M +Kin/M +Kincaid/M +King/M +Kingsbury/M +Kingsley/M +Kingsly/M +Kingston/M +Kingstown/M +Kingwood/M +Kinna/M +Kinney/M +Kinnickinnic/M +Kinnie/M +Kinny/M +Kinsey/M +Kinshasa/M +Kinshasha/M +Kinsley/M +Kiowa/SM +Kip/M +Kipling/M +Kipp/MR +Kippar/M +Kipper/M +Kippie/M +Kippy/M +Kira/M +Kirbee/M +Kirbie/M +Kirby/M +Kirchhoff/M +Kirchner/M +Kirchoff/M +Kirghistan/M +Kirghiz/M +Kirghizia/M +Kiri/M +Kiribati +Kirinyaga/M +Kirk/M +Kirkland/M +Kirkpatrick/M +Kirkwood/M +Kirov/M +Kirsten/M +Kirsteni/M +Kirsti/M +Kirstin/M +Kirstyn/M +Kisangani/M +Kishinev/M +Kissee/M +Kissiah/M +Kissie/M +Kissinger/M +Kit/M +Kitakyushu/M +Kitchener/M +Kitti/M +Kittie/M +Kitty/M +Kiwanis/M +Kizzee/M +Kizzie/M +Klan/M +Klansman/M +Klara/M +Klarika/M +Klarrisa/M +Klaus/M +Klee/M +Kleenex/SM +Klein/M +Kleinrock/M +Klemens/M +Klement/M +Kleon/M +Kliment/M +Kline/M +Klingon/M +Klondike/SDMG +Klux/M +Knapp/M +Knauer/M +Knesset/M +Kngwarreye/M +Knickerbocker/MS +Knievel/M +Knight/M +Knobeloch/M +Knopf/M +Knossos/M +Knowles +Knox/M +Knoxville/M +Knudsen/M +Knudson/M +Knuth/M +Knutsen/M +Knutson/M +Kobayashi/M +Kobe/M +Koch/M +Kochab/M +Kodachrome/M +Kodak/SM +Kodaly/M +Kodiak/M +Koenig/M +Koenigsberg/M +Koenraad/M +Koestler/M +Kohinoor/M +Kohl/MR +Kohler/M +Kolyma/M +Kommunizma/M +Kong/M +Kongo/M +Konrad/M +Konstance/M +Konstantin/M +Konstantine/M +Konstanze/M +Koo/M +Koontz/M +Koppers/M +Kora/M +Koral/M +Koralle/M +Koran/SM +Koranic +Kordula/M +Kore/M +Korea/M +Korean/S +Korella/M +Koren/M +Koressa/M +Korey/M +Kori/M +Korie/M +Kornberg/M +Korney/M +Korrie/M +Korry/M +Kort/M +Kory/M +Korzybski/M +Kosciusko/M +Kossuth/M +Kosygin/M +Kovacs/M +Kowalewski/M +Kowalski/M +Kowloon/M +Kr/M +Kraemer/M +Kraft/M +Krakatau's +Krakatoa/M +Krakow/M +Kramer/M +Krasnodar/M +Krasnoyarsk/M +Krause/M +Krebs/M +Kremlin/M +Kremlinologist/MS +Kremlinology/MS +Kresge/M +Krieger/M +Kringle/M +Kris/M +Krisha/M +Krishna/M +Krishnah/M +Krispin/M +Krissie/M +Krissy/M +Krista/M +Kristal/M +Kristan/M +Kriste/M +Kristel/M +Kristen/M +Kristi/MN +Kristian/M +Kristie/M +Kristien/M +Kristin/M +Kristina/M +Kristine/M +Kristo/MS +Kristofer/M +Kristoffer/M +Kristofor/M +Kristoforo/M +Kristopher/M +Kristy/M +Kristyn/M +Kroc/M +Kroger/M +Kronecker/M +Kropotkin/M +Krueger/M +Kruger/M +Krugerrand/S +Krupp/M +Kruse/M +Krysta/M +Krystal/M +Krystalle/M +Krystle/M +Krystyna/M +Ku/M +Kublai/M +Kubrick/M +Kubuntu +Kubuntu's +Kuenning/M +Kuhn/M +Kuibyshev/M +Kumar/M +Kunming/M +Kuomintang/M +Kurd/SM +Kurdish/M +Kurdistan/SM +Kurosawa/M +Kurt/M +Kurtis/M +Kusch/M +Kuwait/M +Kuwaiti/SM +Kuznets/M +Kuznetsk/M +Kwakiutl/M +Kwangchow's +Kwangju/M +Kwanzaa/S +Ky/MH +Kyla/M +Kyle/M +Kylen/M +Kylie/M +Kylila/M +Kylynn/M +Kym/M +Kynthia/M +Kyoto/M +Kyrgyzstan +Kyrstin/M +Kyushu/M +L +L'Amour +L'Ouverture +L's +L'vov +LA +LAN +LBJ/M +LC +LCD +LCM +LDC +LED/SM +LIFO +LL +LLB +LLD +LNG +LOGO +LP +LPG +LPN/S +LSD +La/M +LaTeX/M +Lab/SM +Laban/M +Labrador/SM +Labradorean/S +Lac/M +Lacee/M +Lacey/M +Lachesis/M +Lacie/M +Lackawanna/M +Lacy/M +Ladoga/M +Ladonna/M +Lady/SM +Ladyship/MS +Laetitia/M +Lafayette/M +Lafitte/M +Lagos/M +Lagrange/M +Lagrangian/M +Laguerre/M +Laguna/M +Lahore/M +Laidlaw/M +Laina/M +Lainey/M +Laird/M +Laius/M +Lakehurst/M +Lakeisha/M +Lakewood/M +Lakisha/M +Lakshmi/M +Lalo/M +Lamaism/SM +Lamar/M +Lamarck/M +Lamaze +Lamb/M +Lambert/M +Lamborghini/M +Lammond/M +Lamond/M +Lamont/M +Lamport/M +Lana/M +Lanae/M +Lanai/M +Lancashire/M +Lancaster/M +Lance/M +Lancelot/M +Land/M +Landis/M +Landon/M +Landry/M +Landsat +Landsteiner/M +Landwehr/M +Lane/M +Lanette/M +Laney/M +Lang/M +Lange/M +Langeland/M +Langerhans/M +Langford/M +Langland/M +Langley/M +Langmuir/M +Langsdon/M +Langston/M +Lani/M +Lanie/M +Lanita/M +Lanna/M +Lanni/M +Lannie/M +Lanny/M +Lansing/M +Lanzhou +Lao/SM +Laocoon/M +Laotian/MS +Laplace/M +Lapland/ZMR +Lapp/SM +Lara/M +Laraine/M +Laramie/M +Lardner/M +Laredo/M +Lari/M +Larina/M +Larine/M +Larisa/M +Larissa/M +Lark/M +Larousse/M +Larry/M +Lars/NM +Larsen/M +Larson/M +Laryssa/M +Lascaux/M +Lassa/M +Lassen/M +Lassie/M +Laszlo/M +Lat/M +Latasha/M +Latashia/M +Lateran/M +Lathrop/M +Latia/M +Latin/RMS +Latina/SM +Latinate +Latino/S +Latisha/M +Latonya/M +Latoya/M +Latrena/M +Latrina/M +Latrobe/M +Lattimer/M +Latvia/M +Latvian/S +Laud/MR +Lauder/M +Lauderdale/M +Laue/M +Laughton/M +Launce/M +Laundromat/SM +Laura/M +Lauraine/M +Laural/M +Lauralee/M +Laurasia/M +Laure/M +Lauree/M +Laureen/M +Laurel/M +Laurella/M +Lauren/SM +Laurena/M +Laurence/M +Laurene/M +Laurent/M +Laurentian +Lauretta/M +Laurette/M +Lauri/M +Laurianne/M +Laurice/M +Laurie/M +Lauritz/M +Lauryn/M +Lausanne/M +Laval/M +Lavena/M +Lavern/M +Laverna/M +Laverne/M +Lavina/M +Lavinia/M +Lavinie/M +Lavoisier/M +Lavonne/M +Law/M +Lawanda/M +Lawford/M +Lawrence/M +Lawrenceville/M +Lawry/M +Lawson/M +Lawton/M +Lay/M +Layamon/M +Layla/M +Layne/M +Layney/M +Layton/M +Lazar/M +Lazare/M +Lazaro/M +Lazarus/M +Le/NM +Lea/M +Leach/M +Leadbelly/M +Leah/M +Leakey/M +Lean/M +Leander/M +Leandra/M +Leann/M +Leanna/M +Leanne/M +Leanor/M +Leanora/M +Lear/M +Leary/M +Leavenworth/M +Lebanese +Lebanon/M +Lebbie/M +Lebesgue/M +Leblanc/M +Leda/M +Lederberg/M +Lee/M +Leeann/M +Leeanne/M +Leeds/M +Leela/M +Leelah/M +Leeland/M +Leena/M +Leesa/M +Leese/M +Leeuwenhoek/M +Leeward/M +Left/S +Lefty/M +Legendre/M +Leger/SM +Leghorn/SM +Lego/M +Legra/M +Legree/M +Lehigh/M +Lehman/M +Leia/M +Leibniz/M +Leicester/SM +Leiden/M +Leif/M +Leigh/M +Leigha/M +Leighton/M +Leila/M +Leilah/M +Leipzig/M +Leisha/M +Lek/M +Lela/M +Lelah/M +Leland/M +Lelia/M +Lem/M +Lemaitre/M +Lemar/M +Lemke/M +Lemmie/M +Lemmy/M +Lemuel/M +Lemuria/M +Len/M +Lena/M +Lenard/M +Lenci/M +Lenee/M +Lenette/M +Lenin/M +Leningrad/M +Leninism/M +Leninist +Lenka/M +Lenna/M +Lennard/M +Lennie/M +Lennon/M +Lenny/M +Leno/M +Lenoir/M +Lenora/M +Lenore/M +Lent/SMN +Leo/MS +Leodora/M +Leoine/M +Leola/M +Leoline/M +Leon/M +Leona/M +Leonanie/M +Leonard/M +Leonardo/M +Leoncavallo/M +Leone/M +Leonel/M +Leonelle/M +Leonerd/M +Leonhard/M +Leonid/M +Leonidas/M +Leonie/M +Leonor/M +Leonora/M +Leonore/M +Leontine/M +Leontyne/M +Leopold/M +Leopoldo/M +Leopoldville/M +Leora/M +Lepidus/M +Lepke/M +Lepus/M +Lerner/M +Leroi/M +Leroy/M +Les/Y +Lesa/M +Leshia/M +Lesley/M +Lesli/M +Leslie/M +Lesly/M +Lesotho/M +Lesseps/M +Lessie/M +Lester/M +Lesya/M +Leta/M +Letha/M +Lethe/M +Lethia/M +Leticia/M +Letisha/M +Letitia/M +Letizia/M +Letta/M +Letterman/M +Letti/M +Lettie/M +Letty/M +Leupold/M +Lev/M +Levant/M +Levesque/M +Levey/M +Levi/MS +Leviathan +Levin/M +Levine/M +Leviticus/M +Levitt/M +Levon/M +Levy/M +Lew/M +Lewellyn/M +Lewes +Lewie/M +Lewinsky/M +Lewis/M +Lewiss +Lexi/MS +Lexie/M +Lexine/M +Lexington/M +Lexus/M +Lexy/M +Leyden/M +Leyla/M +Lezley/M +Lezlie/M +Lhasa/SM +Lhotse/M +Li/MY +Lia/M +Liam/M +Lian/M +Liana/M +Liane/M +Lianna/M +Lianne/M +Lib/M +Libbey/M +Libbi/M +Libbie/M +Libby/M +Liberace/M +Liberia/M +Liberian/S +Libra/SM +Libreville/M +Librium/M +Libya/M +Libyan/S +Licha/M +Lichtenstein/M +Lichter/M +Lida/M +Lidia/M +Lie/M +Lieberman/M +Liebfraumilch/M +Liechtenstein/RMZ +Lief/M +Liege/M +Liesa/M +Lieut/M +Lil/MY +Lila/SM +Lilah/M +Lilia/MS +Lilian/M +Liliana/M +Liliane/M +Lilith/M +Liliuokalani/M +Lilla/M +Lille/M +Lilli/MS +Lillian/M +Lillie/M +Lilliput/M +Lilliputian/SM +Lilllie/M +Lilly/M +Lilongwe/M +Lily/M +Lilyan/M +Lima/M +Limbaugh/M +Limbo +Limburger/SM +Limoges/M +Limpopo/M +Lin/M +Lina/M +Linc/M +Lincoln/SM +Lind/M +Linda/M +Lindberg/M +Lindbergh/M +Lindholm/M +Lindi/M +Lindie/M +Lindon/M +Lindquist/M +Lindsay/M +Lindsey/M +Lindstrom/M +Lindsy/M +Lindy/M +Linea/M +Linell/M +Linet/M +Linette/M +Link/M +Linn/M +Linnaeus/M +Linnea/M +Linnell/M +Linnet/M +Linnie/M +Linoel/M +Linotype/M +Linton/M +Linus/M +Linux/M +Linwood/M +Linzy/M +Lion/M +Lionel/M +Lionello/M +Lippi/M +Lippmann/M +Lipschitz/M +Lipscomb/M +Lipton/M +Lira/M +Lisa/M +Lisabeth/M +Lisbeth/M +Lisbon/M +Lise/M +Lisetta/M +Lisette/M +Lisha/M +Lishe/M +Lisle/M +Liss/M +Lissa/M +Lissajous/M +Lissi/M +Lissie/M +Lissy/M +Lister/M +Listerine/M +Liston/M +Liszt/M +Lita/M +Lithuania/M +Lithuanian/S +Little/M +Littleton/M +Litton/M +Liuka/M +Liv/M +Liva/M +Livermore/M +Liverpool/M +Liverpudlian/MS +Livia/M +Livingston/M +Livingstone/M +Livonia/M +Livvie/M +Livvy/M +Livvyy/M +Livy/M +Liz/M +Liza/M +Lizabeth/M +Lizbeth/M +Lizette/M +Lizzie/M +Lizzy/M +Ljubljana/M +Llewellyn/M +Lloyd/M +Llywellyn/M +Loafer/S +Lobachevsky/M +Lochinvar/M +Lock/M +Locke/M +Lockean/M +Lockhart/M +Lockheed/M +Lockian/M +Lockwood/M +Lodge/M +Lodovico/M +Lodowick/M +Lodz +Loeb/M +Loella/M +Loewe/M +Loewi/M +Logan/M +Lohengrin/M +Loire/M +Lois/M +Loise/M +Loki/M +Lola/M +Loleta/M +Lolita/M +Lolly/M +Lomb/M +Lombard/M +Lombardi/M +Lombardy/M +Lome +Lon/M +Lona/M +London/RMZ +Londonderry/M +Londoner/M +Lonee/M +Long/M +Longfellow/M +Longstreet/M +Longueuil/M +Loni/M +Lonna/M +Lonnard/M +Lonni/M +Lonnie/M +Lonny/M +Loomis/M +Lopez/M +Lora/M +Lorain/M +Loraine/M +Loralee/M +Loralie/M +Loralyn/M +Lorant/M +Lord/MS +Lordship/SM +Loree/M +Loreen/M +Lorelei/M +Lorelle/M +Loren/SM +Lorena/M +Lorene/M +Lorentz/M +Lorentzian/M +Lorenz/M +Lorenza/M +Lorenzo/M +Loretta/M +Lorette/M +Lori/M +Loria/M +Lorianna/M +Lorianne/M +Lorie/M +Lorilee/M +Lorilyn/M +Lorin/M +Lorinda/M +Lorine/M +Lorita/M +Lorna/M +Lorne/M +Lorraine/M +Lorrayne/M +Lorre/M +Lorri/M +Lorrie/M +Lorrin/M +Lorry/M +Lory/M +Los +Lot/M +Lothaire/M +Lothario/MS +Lott/M +Lotta/M +Lotte/M +Lotti/M +Lottie/M +Lotty/M +Lou/M +Louella/M +Louie/M +Louis/M +Louisa/M +Louise/M +Louisette/M +Louisiana/M +Louisianan/S +Louisianian/S +Louisville/M +Lourdes/M +Loutitia/M +Louvre/M +Love/M +Lovecraft/M +Lovejoy/M +Lovelace/M +Loveland/M +Lovell/M +Lowe/M +Lowell/M +Lowery/M +Lowlands/M +Lowrance/M +Loy/M +Loyang/M +Loyd/M +Loydie/M +Loyola/M +Lr +Lt/M +Ltd/M +Lu/M +Luanda/M +Luann/M +Lubbock/M +Lubumbashi/M +Luca/MS +Lucais/M +Luce/M +Lucerne/M +Lucho/M +Luci/MN +Lucia/MS +Lucian/M +Luciana/M +Luciano/M +Lucie/M +Lucien/M +Lucienne/M +Lucifer/M +Lucila/M +Lucile/M +Lucilia/M +Lucille/M +Lucina/M +Lucinda/M +Lucine/M +Lucio/M +Lucita/M +Lucite/MS +Lucius/M +Lucknow/M +Lucky/M +Lucretia/M +Lucretius/M +Lucy/M +Luddite/SM +Ludhiana/M +Ludlow/M +Ludmilla/M +Ludovico/M +Ludovika/M +Ludvig/M +Ludwig/M +Luella/M +Luelle/M +Lufthansa/M +Luftwaffe/M +Luger/M +Lugosi/M +Luigi/M +Luis/M +Luisa/M +Luise/M +Lukas/M +Luke/M +Lula/M +Lulita/M +Lulu/M +Lumière/M +Luna/M +Lund/M +Lundberg/M +Lundquist/M +Lupe/M +Lupus/M +Lura/M +Lurette/M +Luria/M +Lurleen/M +Lurlene/M +Lurline/M +Lusa/M +Lusaka/M +Lusitania/M +Lutero/M +Luther/M +Lutheran/SM +Lutheranism/MS +Lutz +Luxembourg/RMZ +Luxembourgian +Luxemburg's +Luz/M +Luzon/M +Ly/MY +LyX/M +Lyallpur/M +Lycra/S +Lycurgus/M +Lyda/M +Lydia/M +Lydian/S +Lydie/M +Lydon/M +Lyell/M +Lyle/M +Lyly/M +Lyman/M +Lyme/M +Lyn/M +Lynch/M +Lynchburg/M +Lynda/M +Lynde/M +Lyndel/M +Lyndell/M +Lyndon/M +Lyndsay/M +Lyndsey/M +Lyndsie/M +Lyndy/M +Lynea/M +Lynelle/M +Lynett/M +Lynette/M +Lynn/M +Lynna/M +Lynne/M +Lynnea/M +Lynnell/M +Lynnelle/M +Lynnet/M +Lynnett/M +Lynnette/M +Lynsey/M +Lyon/SM +Lyra/M +Lysenko/M +Lysistrata/M +Lysol/M +Lyssa/M +M's +M/GB +MA +MAG +MASH +MB +MBA +MC +MCI/M +MD +MDT +ME +MFA +MGM/M +MHz +MI +MIA +MIG/S +MIMD +MIPS +MIRV/DSG +MIT/M +MITRE/SM +MM +MMe +MN +MO +MP +MPH +MRI +MS +MSG +MST +MSW +MT +MTS +MTV +MULTICS/M +MVP +MW +Maalox/M +Mab/M +Mabel/M +Mabelle/M +Mable/M +Mac/SGMD +MacArthur/M +MacDonald/M +MacDraw/M +MacGregor/M +MacIntosh/M +MacKenzie/M +MacLeish/M +MacMillan/M +MacPaint/M +Macao/M +Macarthur/M +Macaulay/M +Macbeth/M +Maccabees/M +Maccabeus/M +Macdonald/M +Mace/MS +Macedon/M +Macedonia/M +Macedonian/S +Macgregor/M +Mach/M +Machiavelli/M +Machiavellian/S +Machs +Macias/M +Macintosh/M +Mack/M +Mackenzie/M +Mackinac/M +Mackinaw +Macmillan/M +Macon/SM +Macy/M +Mada/M +Madagascan/SM +Madagascar/M +Madalena/M +Madalyn/M +Madame/MS +Maddalena/M +Madden/M +Maddi/M +Maddie/M +Maddox/M +Maddy/M +Madeira/SM +Madel/M +Madelaine/M +Madeleine/M +Madelena/M +Madelene/M +Madelin/M +Madelina/M +Madeline/M +Madella/M +Madelle/M +Madelon/M +Madelyn/M +Madge/M +Madhya/M +Madison/M +Madlen/M +Madlin/M +Madonna/MS +Madras +Madrid/M +Madsen/M +Madurai/M +Mady/M +Mae/M +Maegan/M +Maelstrom/M +Maeterlinck/M +Mafia/MS +Mafioso/S +Mag/M +Magda/M +Magdaia/M +Magdalen/M +Magdalena/M +Magdalene/M +Magellan/M +Magellanic +Maggee/M +Maggi/M +Maggie/M +Maggy/M +Magi/M +Magill/M +Maginot/M +Magnitogorsk/M +Magnum +Magnuson/M +Magog/M +Magoo/M +Magritte/M +Magruder/M +Magsaysay/M +Maguire/SM +Magus/M +Magyar/MS +Mahabharata +Mahala/M +Mahalia/M +Maharashtra/M +Mahavira/M +Mahayana/M +Mahayanist +Mahdi/M +Mahfouz/M +Mahican/SM +Mahler/M +Mahmoud/M +Mahmud/M +Mahomet's +Mai/MR +Maia/M +Maible/M +Maier/M +Maiga/M +Maighdiln/M +Maigret/M +Mailer/M +Maillol/M +Maiman/M +Maimonides/M +Maine/MZR +Mainer/M +Mair/M +Maire/M +Maisey/M +Maisie/M +Maison/M +Maitilde/M +Maj +Maje/M +Majesty/MS +Major/M +Majorca/M +Majuro/M +Makarios/M +Maker/M +Mal/M +Mala/M +Malabar/M +Malabo/M +Malacca/M +Malachi/M +Malagasy/M +Malamud/M +Malanie/M +Malaprop/M +Malawi/M +Malawian/S +Malay/SM +Malaya/M +Malayalam/M +Malayan/MS +Malaysia/M +Malaysian/S +Malchy/M +Malcolm/M +Maldive/SM +Maldivian/S +Maldonado/M +Male/M +Malena/M +Mali/M +Malia/M +Malian/S +Malibu/M +Malina/M +Malinda/M +Malinde/M +Malinowski/M +Malissa/M +Malissia/M +Mallarmé/M +Mallissa/M +Mallorie/M +Mallory/M +Malone/M +Malorie/M +Malory/M +Malraux/M +Malta/M +Maltese +Malthus/M +Malthusian/S +Malva/M +Malvin/M +Malvina/M +Malynda/M +Mame/M +Mamet/M +Mamie/M +Mammon's +Mamore/M +Man/SM +Managua/M +Manama/M +Manasseh/M +Manaus's +Manchester/M +Manchu/MS +Manchuria/M +Manchurian/S +Mancini/M +Mancunian/MS +Manda/M +Mandalay/M +Mandarin +Mandel/M +Mandela +Mandelbrot/M +Mandi/M +Mandie/M +Mandingo/M +Mandy/M +Manet/M +Manfred/M +Manhattan/SM +Mani/M +Manichean/M +Manila/MS +Manitoba/M +Manitoulin/M +Manitowoc/M +Mankowski/M +Manley/M +Mann/GM +Mannheim/M +Mannie/M +Manning/M +Manny/M +Mano/M +Manolo/M +Manon/M +Mansfield/M +Manson/M +Mantegna/M +Mantle/M +Manuel/M +Manuela/M +Manville/M +Manx +Manya/M +Mao/M +Maoism/MS +Maoist/S +Maori/SM +Maplecrest/M +Mapplethorpe/M +Maputo/M +Mar/SMN +Mara/M +Marabel/M +Maracaibo/M +Marat/M +Marathi +Marathon/M +Marc/M +Marceau/M +Marcel/M +Marcela/M +Marcelia/M +Marcelino/M +Marcella/M +Marcelle/M +Marcellina/M +Marcelline/M +Marcello/M +Marcellus/M +Marcelo/M +March/MS +Marchall/M +Marchelle/M +Marci/M +Marcia/M +Marciano/M +Marcie/M +Marcile/M +Marcille/M +Marco/SM +Marconi/M +Marcotte/M +Marcus/M +Marcy/M +Mardi/SM +Marduk/M +Mareah/M +Maren/M +Marena/M +Maressa/M +Marga/M +Margalit/M +Margalo/M +Margaret/M +Margareta/M +Margarete/M +Margaretha/M +Margarethe/M +Margaretta/M +Margarette/M +Margarita/M +Margarito/M +Margaux/M +Marge/M +Margeaux/M +Margery/M +Marget/M +Margette/M +Margi/M +Margie/M +Margit/M +Margo/M +Margot/M +Margret/M +Margrethe/M +Marguerite/M +Margy/M +Mari/MS +Maria/M +Mariam/M +Marian/MS +Mariana/SM +Mariann/M +Marianna/M +Marianne/M +Mariano/M +Maribel/M +Maribelle/M +Maribeth/M +Marice/M +Maricela/M +Maridel/M +Marie/M +Marieann/M +Mariejeanne/M +Mariel/M +Mariele/M +Marielle/M +Mariellen/M +Marietta/M +Mariette/M +Marigold/M +Marijn/M +Marijo/M +Marika/M +Marilee/M +Marilin/M +Marillin/M +Marilyn/M +Marin/M +Marina/M +Marine/S +Marinna/M +Marino/M +Mario/M +Marion/M +Mariquilla/M +Marisa/M +Mariska/M +Marisol/M +Marissa/M +Marita/M +Maritain/M +Maritsa/M +Maritza/M +Mariupol/M +Marius/M +Mariya/M +Marj/M +Marja/M +Marje/M +Marji/M +Marjie/M +Marjorie/M +Marjory/M +Marjy/M +Mark/MS +Markab/M +Marketa/M +Markham/M +Markism/M +Markos +Markov +Markovian +Markovitz/M +Markus/M +Marla/M +Marlane/M +Marlboro/M +Marlborough/M +Marleah/M +Marlee/M +Marleen/M +Marlena/M +Marlene/M +Marley/M +Marlie/M +Marlin/M +Marline/M +Marlo/M +Marlon/M +Marlow/M +Marlowe/M +Marlyn/M +Marmaduke/M +Marmara/M +Marna/M +Marne/M +Marney/M +Marni/M +Marnia/M +Marnie/M +Marquesas/M +Marquette/M +Marquez/M +Marquis/M +Marquita/M +Marrakesh/M +Marrilee/M +Marriott/M +Marris/M +Marrissa/M +Marseillaise/SM +Marseille's +Marseilles +Marsh/M +Marsha/M +Marshal/M +Marshall/GDM +Marshalled/M +Marshalling/M +Marsiella/M +Mart/MN +Marta/M +Martainn/M +Martel/M +Martelle/M +Marten/M +Martguerita/M +Martha/M +Marthe/M +Marthena/M +Marti/M +Martial +Martian/S +Martica/M +Martie/M +Martin/M +Martina/M +Martinez/M +Martinique/M +Martino/M +Martinson/M +Martita/M +Marty/M +Martyn/M +Martynne/M +Marv/NM +Marva/M +Marve/M +Marvell/M +Marven/M +Marvin/M +Marwin/M +Marx/M +Marxian/S +Marxism/SM +Marxist/SM +Mary/M +Marya/M +Maryann/M +Maryanna/M +Maryanne/M +Marybelle/M +Marybeth/M +Maryellen/M +Maryjane/M +Maryjo/M +Maryl/M +Maryland/MZR +Marylee/M +Marylin/M +Marylinda/M +Marylou/M +Marylynne/M +Maryrose/M +Marys +Marysa/M +Masada/M +Masai/M +Masaryk/M +Mascagni/M +Masefield/M +Maseru/M +Masha/M +Mashhad/M +Mason/SM +Masonic +Masonite/M +Mass/S +Massachusetts/M +Massasoit/M +Massenet/M +Massey/M +Massimiliano/M +Massimo/M +Master/SM +Mata/M +Matelda/M +Mateo/M +Mathe/RM +Mathematica/M +Mathematik/M +Mather/M +Mathew/MS +Mathewson/M +Mathian/M +Mathias +Mathieu/M +Mathilda/M +Mathilde/M +Mathis +Matias/M +Matilda/M +Matilde/M +Matisse/SM +Matsumoto/M +Matt/M +Mattel/M +Matteo/M +Matterhorn/M +Matthaeus/M +Mattheus/M +Matthew/MS +Matthias +Matthieu/M +Matthiew/M +Matthus/M +Matti/M +Mattias/M +Mattie/M +Matty/M +Maud/M +Maude/M +Maudie/M +Maugham/M +Maui/M +Maupassant/M +Maura/M +Maure/M +Maureen/M +Maureene/M +Maurene/M +Mauriac/M +Maurice/M +Mauricio/M +Maurie/M +Maurine/M +Maurise/M +Maurita/M +Mauritania/M +Mauritanian/S +Mauritian/S +Mauritius/M +Maurits/M +Maurizia/M +Maurizio/M +Mauro/M +Maurois/M +Maury/M +Mauser/M +Mavis/M +Mavra/M +Mawr/M +Max/M +Maxi/M +Maxie/M +Maxim/M +Maximilian/M +Maximilianus/M +Maximilien/M +Maximo/M +Maxine/M +Maxtor/M +Maxwell/M +Maxwellian +Maxy/M +May/SMR +Maya/MS +Mayan/S +Maybelle/M +Maye/M +Mayer/M +Mayfair/M +Mayflower/M +Maynard/M +Mayne/M +Maynord/M +Mayo/M +Mayor/M +Maypole/SM +Mayra/M +Mazama/M +Mazarin/M +Mazatlan/M +Mazda/M +Mazzini/M +Mb +Mbabane/M +Mbini/M +McAdam/MS +McAllister/M +McBride/M +McCabe/M +McCain/M +McCall/M +McCarthy/M +McCarthyism/M +McCartney/M +McCarty/M +McCauley/M +McClain/M +McClellan/M +McClure/M +McCluskey/M +McConnell/M +McCormick/M +McCoy/SM +McCracken/M +McCray/M +McCullough/M +McDaniel/M +McDermott/M +McDonald/M +McDonnell/M +McDougall/M +McDowell/M +McElhaney/M +McEnroe/M +McFadden/M +McFarland/M +McGee/M +McGill/M +McGovern/M +McGowan/M +McGrath/M +McGraw/M +McGregor/M +McGuffey/M +McGuire/M +McIntosh/M +McIntyre/M +McKay/M +McKee/M +McKenzie/M +McKesson/M +McKinley/M +McKinney/M +McKnight/M +McLanahan/M +McLaughlin/M +McLean/M +McLeod/M +McLuhan/M +McMahon/M +McMartin/M +McMillan/M +McNamara/M +McNaughton/M +McNeil/M +McPherson/M +Md/M +Me/M +Mead/M +Meade/M +Meadows +Meagan/M +Meaghan/M +Meany/M +Meara/M +Mecca/MS +Mechelle/M +Medan/M +Medea/M +Medellin +Medfield/M +Medicaid/SM +Medicare/MS +Medici/MS +Medina/M +Mediterranean/MS +Medusa/M +Meg/MN +Megan/M +Megen/M +Meggi/M +Meggie/M +Meggy/M +Meghan/M +Meghann/M +Mehetabel/M +Mei/MR +Meier/M +Meighen/M +Meiji/M +Meir/M +Meister/M +Meistersinger/M +Mejia/M +Mekong/M +Mel/MY +Mela/M +Melamie/M +Melanesia/M +Melanesian/S +Melania/M +Melanie/M +Melantha/M +Melany/M +Melba/M +Melbourne/M +Melcher/M +Melchior/M +Melendez/M +Melesa/M +Melessa/M +Melicent/M +Melina/M +Melinda/M +Melinde/M +Melisa/M +Melisande/M +Melisandra/M +Melisenda/M +Melisent/M +Melissa/M +Melisse/M +Melita/M +Melitta/M +Mella/M +Melli/M +Mellicent/M +Mellie/M +Mellisa/M +Mellisent/M +Mellon/M +Melloney/M +Melly/M +Melodee/M +Melodie/M +Melody/M +Melonie/M +Melony/M +Melosa/M +Melpomene/M +Melton/M +Melva/M +Melville/M +Melvin/M +Melvyn/M +Memling/M +Memphis/M +Menander/M +Menard/M +Mencius/M +Mencken/M +Mendel/M +Mendeleev/M +Mendelian +Mendelssohn/M +Mendez/M +Mendie/M +Mendocino/M +Mendoza/M +Mendy/M +Menelaus/M +Menes/M +Menkalinan/M +Menkar/M +Menkent/M +Menlo/M +Mennonite/SM +Menominee +Menotti/M +Mensa/M +Mensch/M +Menuhin/M +Menzies/M +Mephistopheles/M +Merak/M +Mercado/M +Mercator/M +Mercedes +Mercer/M +Merci/M +Mercie/M +Merck/M +Mercurochrome/M +Mercury/MS +Mercy/M +Meredeth/M +Meredith/M +Meredithe/M +Merell/M +Meridel/M +Meridith/M +Meriel/M +Merilee/M +Merill/M +Merilyn/M +Meris +Merissa/M +Meriwether/M +Merl/M +Merla/M +Merle/M +Merlin/M +Merlina/M +Merline/M +Merna/M +Merola/M +Merralee/M +Merrel/M +Merriam/M +Merrick/M +Merridie/M +Merrie/M +Merrielle/M +Merrile/M +Merrilee/M +Merrili/M +Merrill/M +Merrily/M +Merrimac/M +Merrimack/M +Merritt/M +Merry/M +Mersey/M +Merton/M +Merv/M +Mervin/M +Merwin/M +Merwyn/M +Meryl/M +Mesa +Mesabi/M +Meshed's +Mesolithic/M +Mesopotamia/M +Mesopotamian/S +Mesozoic +Messerschmidt/M +Messiaen/M +Messiah/M +Messiahs +Messianic +Messrs/M +Meta/M +Methodism/SM +Methodist/MS +Methuen/M +Methuselah/M +Methuselahs +Metrecal/M +Metternich/M +Metzler/M +Meuse/M +Mex +Mexicali/M +Mexican/S +Mexico/M +Meyer/SM +Meyerbeer/M +Mg/M +Mgr +Mia/M +Miami/SM +Miaplacidus/M +Mic/M +Micaela/M +Micah/M +Mich/M +Michael/SM +Michaela/M +Michaelangelo/M +Michaelina/M +Michaeline/M +Michaella/M +Michaelmas/MS +Michaelson/M +Michail/M +Michal/M +Michale/M +Micheal/M +Micheil/M +Michel/M +Michelangelo/M +Michele/M +Michelin/M +Michelina/M +Micheline/M +Michell/M +Michelle/M +Michelson/M +Michigan/M +Michigander/S +Michiganite/S +Mick/M +Mickelson/M +Mickey/M +Micki/M +Mickie/M +Micky/M +Micmac/M +MicroVAX/M +MicroVAXes +Micronesia/M +Micronesian/S +Microport/M +Microsoft/M +Microsystems +Midas/M +Middlebury/M +Middlesex/M +Middleton/M +Middletown/M +Mideast/M +Mideastern +Midge/M +Midland/MS +Midway/M +Midwest/M +Midwestern/ZR +Midwesterner/M +Mignon/M +Mignonne/M +Miguel/M +Miguela/M +Miguelita/M +Mikael/M +Mikaela/M +Mike/M +Mikel/M +Mikey/M +Mikhail/M +Mikkel/M +Mikol/M +Mikoyan/M +Mil/MY +Milagros/M +Milan/M +Milanese +Mildred/M +Mildrid/M +Mile/SM +Milena/M +Milford/M +Milicent/M +Milissent/M +Milka/M +Milken/M +Mill/SMR +Millard/M +Millay/M +Miller/M +Millet/M +Milli/M +Millicent/M +Millie/M +Millikan/M +Millisent/M +Milly/M +Milne/M +Milo/M +Milquetoast/S +Milt/M +Miltiades/M +Miltie/M +Milton/M +Miltonic +Miltown/M +Milty/M +Milwaukee/M +Milzie/M +Mimi/M +Mimosa/M +Min/MR +Mina/M +Minda/M +Mindanao/M +Mindoro/M +Mindy/M +Miner/M +Minerva/M +Minetta/M +Minette/M +Ming/M +Mingus/M +Minn/M +Minna/M +Minnaminnie/M +Minne/M +Minneapolis/M +Minnesota/M +Minnesotan/S +Minni/M +Minnie/M +Minnnie/M +Minny/M +Minoan/S +Minolta/M +Minor/M +Minos +Minot/M +Minotaur/M +Minsk/M +Minsky/M +Minta/M +Mintaka/M +Minuit/M +Minuteman/M +Miocene +Miquela/M +Mir/M +Mira/M +Mirabeau/M +Mirabel/M +Mirabella/M +Mirabelle/M +Mirach/M +Miran/M +Miranda/M +Mireielle/M +Mireille/M +Mirella/M +Mirelle/M +Mirfak/M +Miriam/M +Mirilla/M +Mirna/M +Miro +Mirzam/M +Mischa/M +Misha/M +Miskito +Miss/SM +Missie/M +Mississauga/M +Mississippi/M +Mississippian/S +Missoula/M +Missouri/M +Missourian/S +Missy/M +Mistassini/M +Mister/SM +Misti/M +Mistress/MS +Misty/M +Mitch/M +Mitchael/M +Mitchel/M +Mitchell/M +Mitford/M +Mithra/M +Mithridates/M +Mitsubishi/M +Mitterrand/M +Mitty/M +Mitzi/M +Mizar/M +Mk +Mlle/M +Mme/SM +Mn/M +Mnemosyne/M +Mo/MN +Mobil/M +Mobile/M +Mobutu/M +Modesta/M +Modestia/M +Modestine/M +Modesto/M +Modesty/M +Modigliani/M +Modula/M +Moe/M +Moen/M +Mogadiscio's +Mogadishu +Mogul/MS +Mohamed/M +Mohammad/M +Mohammed's +Mohammedan/SM +Mohammedanism/MS +Mohandas/M +Mohandis/M +Mohawk/MS +Mohegan/S +Mohican's +Moho/M +Mohorovicic/M +Mohr/M +Moina/M +Moines/M +Moira/M +Moise/MS +Moiseyev/M +Moishe/M +Mojave/M +Moldavia/M +Moldavian/S +Moldova +Moliere +Molina/M +Moline/M +Moll/M +Mollee/M +Molli/M +Mollie/M +Molly/M +Molnar/M +Moloch/M +Molokai/M +Molotov/M +Moluccas +Mombasa/M +Mommy/M +Mon/SM +Mona/M +Monaco/M +Monah/M +Monash/M +Mondale/M +Monday/MS +Mondrian/M +Monegasque/SM +Monera/M +Monet/M +Monfort/M +Mongol/SM +Mongolia/M +Mongolian/S +Mongolic/M +Mongoloid/S +Monica/M +Monika/M +Monique/M +Monk/M +Monmouth/M +Monongahela/M +Monro/M +Monroe/M +Monrovia/M +Monsanto/M +Monsignor/MS +Monsignori +Mont/M +Montague/M +Montaigne/M +Montana/M +Montanan/MS +Montcalm/M +Montclair/M +Monte/M +Montenegrin +Montenegro/M +Monterey/M +Monterrey/M +Montesquieu/M +Montessori/M +Monteverdi/M +Montevideo/M +Montezuma +Montgomery/M +Monti/M +Monticello/M +Montmartre/M +Montoya/M +Montpelier/M +Montrachet/M +Montreal/M +Montserrat/M +Monty/M +Moody/M +Moog +Moon/M +Mooney/M +Moor/MS +Moore/M +Moorish +Mora/M +Morales/M +Moran/M +Moravia/M +Moravian +Mord/M +Mordecai/M +Mordred/M +Mordy/M +More/M +Moreen/M +Morehouse/M +Moreland/M +Morena/M +Moreno/M +Morey/M +Morgan/MS +Morgana/M +Morganica/M +Morganne/M +Morgen/M +Morgun/M +Moria/M +Moriarty/M +Morie/M +Morin/M +Morison/M +Morissa/M +Morita/M +Moritz/M +Morlee/M +Morley/M +Morly/M +Mormon/SM +Mormonism/MS +Morna/M +Moro/M +Moroccan/S +Morocco/M +Moroni/M +Morpheus/M +Morrie/M +Morris/M +Morrison/M +Morristown/M +Morrow/M +Morry/M +Morse/M +Mort/MN +Morten/M +Mortie/M +Mortimer/M +Morton/M +Morty/M +Mosaic +Moscone/M +Moscow/M +Mose/MSR +Moseley/M +Moselle/M +Moser/M +Moshe/M +Moslem's +Mosley/M +Moss/M +Mossberg/M +Mosul/M +Motorola/M +Motown/M +Mott/M +Mount/M +Mountbatten/M +Mountie/SM +Moussorgsky/M +Mouthe/M +Mouton/M +Mowgli/M +Moyer/M +Moyna/M +Moyra/M +Mozambican/S +Mozambique/M +Mozart/M +Mozelle/M +Mozes/M +Mozilla/M +Mr/M +Mrs +Ms/S +Msgr/M +Mt/M +Muawiya/M +Mubarak/M +Mueller/M +Muenster +Muffin/M +Mufi/M +Mufinella/M +Mugabe/M +Muhammad/M +Muhammadan/SM +Muhammadanism/S +Muir/M +Muire/M +Mukden/M +Mulder/M +Mullen/M +Muller/M +Mulligan/M +Mullikan/M +Mullins +Multan/M +Multibus/M +Multics/M +Mumford/M +Munch/M +Muncie/M +Mundt/M +Munich/M +Munmro/M +Munoz/M +Munro/M +Munroe/M +Munsey/M +Munson/M +Munster/MS +Muong/M +Muppet/M +Murasaki/M +Murat/M +Murchison/M +Murcia/M +Murdoch/M +Murdock/M +Mureil/M +Murial/M +Muriel/M +Murielle/M +Murillo/M +Murmansk/M +Murphy/M +Murray/M +Murrow/M +Murrumbidgee/M +Murry/M +Murvyn/M +Muscat/M +Muscovite/M +Muscovy/M +Muse/M +Musial/M +Muskegon/M +Muslim/MS +Mussolini/MS +Mussorgsky/M +Mutsuhito/M +Muzak/SM +Muzo/M +My/M +Myanmar +Myca/M +Mycah/M +Mycenae/M +Mycenaean +Mychal/M +Myer/MS +Mylar/S +Myles/M +Mylo/M +Mynheer/M +Myra/M +Myrah/M +Myranda/M +Myrdal/M +Myriam/M +Myrilla/M +Myrle/M +Myrlene/M +Myrna/M +Myron/M +Myrta/M +Myrtia/M +Myrtice/M +Myrtie/M +Myrtle/M +Myrvyn/M +Myrwyn/M +Mysore/M +Myst/M +Münchhausen/M +N +N'Djamena +N's +NAACP +NASA/MS +NASDAQ +NATO/SM +NB +NBA +NBC +NBS +NC +NCAA +NCC +NCO +NCR +ND +NE +NF +NFC +NFL +NFS +NH +NHL +NIH +NIMBY +NJ +NLRB +NM +NOAA +NORAD/M +NOW +NP +NRA +NS +NSF +NT +NV +NW +NWT +NY +NYC +NYSE +NZ +Na/M +NaCl/M +Nabisco/M +Nabokov/M +Nada/M +Nadean/M +Nadeen/M +Nader/M +Nadia/M +Nadine/M +Nadiya/M +Nady/M +Nadya/M +Nagasaki/M +Nagoya/M +Nagpur/M +Nagy/M +Nahuatl/SM +Nahum/M +Naipaul/M +Nair/M +Nairobi/M +Naismith/M +Nakamura/M +Nakayama/M +Nakoma/M +Nalani/M +Nam/M +Namath/M +Namibia/M +Namibian/S +Nan/M +Nana/M +Nanak/M +Nananne/M +Nance/M +Nancee/M +Nancey/M +Nanchang/M +Nanci/M +Nancie/M +Nancy/M +Nanete/M +Nanette/M +Nani/M +Nanice/M +Nanine/M +Nanjing +Nanking's +Nannette/M +Nanni/M +Nannie/M +Nanny/M +Nanon/M +Nanook/M +Nansen/M +Nantes/M +Nantucket/M +Naoma/M +Naomi/M +Nap/M +Naphtali/M +Napier/M +Naples/M +Napoleon/MS +Napoleonic +Nappie/M +Nappy/M +Nara/M +Narbonne/M +Narcissus/M +Nari/M +Nariko/M +Narmada/M +Narragansett/M +Nash/M +Nashua/M +Nashville/M +Nassau/M +Nasser/M +Nat/M +Nata/M +Natal/M +Natala/M +Natale/M +Natalee/M +Natalia/M +Natalie/M +Natalina/M +Nataline/M +Natalya/M +Nataniel/M +Natasha/M +Natassia/M +Natchez +Nate/XMN +Nathalia/M +Nathalie/M +Nathan/MS +Nathanael/M +Nathanial/M +Nathaniel/M +Nathanil/M +Natividad/M +Nativity/M +Natka/M +Natty/M +Naugahyde/S +Naur/M +Nauru/M +Navaho's +Navajo/S +Navajoes +Navarro/M +Navona/M +Navratilova/M +Navy/S +Nazarene/MS +Nazareth/M +Nazi/SM +Nazism/S +Nb/M +Nd/M +Ndjamena/M +Ne +NeWS/M +Neal/M +Neala/M +Neale/M +Neall/M +Nealon/M +Nealson/M +Nealy/M +Neanderthal/S +Neapolitan/SM +Neb/M +Nebr/M +Nebraska/M +Nebraskan/MS +Nebuchadnezzar/MS +Ned/M +Neda/M +Nedda/M +Neddie/M +Neddy/M +Nedi/M +Needham/M +Neel/M +Neely/M +Nefen/M +Nefertiti/M +Negev/M +Negress/MS +Negritude/S +Negro/M +Negroes +Negroid/S +Nehemiah/M +Nehru/M +Neil/SM +Neila/M +Neile/M +Neill/M +Neilla/M +Neille/M +Nelda/M +Nelia/M +Nelie/M +Nell/M +Nelle/M +Nelli/M +Nellie/M +Nelly/M +Nels/N +Nelsen/M +Nelson/M +Nembutal/M +Nemesis/M +Neogene +Neolithic/M +Nepal/M +Nepalese +Nepali/MS +Neptune/M +Nereid/M +Nerf/M +Nerissa/M +Nerita/M +Nero/M +Neron/M +Nert/M +Nerta/M +Nerte/M +Nerti/M +Nertie/M +Nerty/M +Neruda/M +Nessa/M +Nessi/M +Nessie/M +Nessy/M +Nesta/M +Nester/M +Nestle/M +Nestor/M +Nestorius/M +Netherlander/SM +Netherlands/M +Netscape/M +Netta/M +Netti/M +Nettie/M +Nettle/M +Netty/M +Netzahualcoyotl/M +Neumann/M +Nev/M +Neva/M +Nevada/M +Nevadan/S +Nevadian/S +Nevil/M +Nevile/M +Neville/M +Nevin/SM +Nevis/M +Nevsa/M +Nevsky/M +Newark/M +Newbury/M +Newburyport/M +Newcastle/M +Newell/M +Newfoundland/SRMZ +Newfoundlander/M +Newman/M +Newport/M +Newsweek/MY +Newsweekly/M +Newton/M +Newtonian +Nexis/M +Neysa/M +Ngaliema/M +Nguyen/M +Ni/M +Niagara/M +Nial/M +Niall/M +Niamey/M +Nibelung/M +Nicaean +Nicaragua/M +Nicaraguan/S +Niccolo/M +Nice/M +Nicene +Nichol/MS +Nicholas +Nichole/M +Nicholle/M +Nicholson/M +Nick/M +Nickey/M +Nicki/M +Nickie/M +Nicklaus/M +Nicko/M +Nickola/MS +Nickolai/M +Nickolaus/M +Nicky/M +Nico/M +Nicobar/M +Nicodemus/M +Nicol/M +Nicola/MS +Nicolai/MS +Nicole/M +Nicolea/M +Nicolette/M +Nicoli/MS +Nicolina/M +Nicoline/M +Nicolle/M +Nicosia/M +Niebuhr/M +Niel/MS +Niels/N +Nielsen/M +Nielson/M +Nietzsche/M +Nieves/M +Nigel/M +Niger/M +Nigeria/M +Nigerian/S +Nigerien +Nightingale/M +Nijinsky/M +Nikaniki/M +Nike/M +Niki/M +Nikita/M +Nikki/M +Nikkie/M +Nikko/M +Niko/MS +Nikola/MS +Nikolai/M +Nikolaos/M +Nikolaus/M +Nikolayev's +Nikoletta/M +Nikolia/M +Nikolos/M +Nikon/M +Nil/MS +Nile/SM +Nils/N +Nilsen/M +Nilson/M +Nilsson/M +Nimitz/M +Nimrod/MS +Nina/M +Ninetta/M +Ninette/M +Nineveh/M +Ninnetta/M +Ninnette/M +Ninon/M +Nintendo/M +Niobe/M +Nippon/M +Nipponese +Nirenberg/M +Nirvana/S +Nisei/MS +Nissa/M +Nissan/M +Nisse/M +Nissie/M +Nissy/M +Nita/M +Niven/M +Nixie/M +Nixon/M +Nkrumah/M +No/M +NoDoz/M +Noach/M +Noah/M +Noak/M +Noam/M +Noami/M +Nobe/M +Nobel/M +Nobelist/SM +Nobie/M +Noble/M +Noby/M +Noe/M +Noel/MS +Noelani/M +Noell/M +Noella/M +Noelle/M +Noellyn/M +Noelyn/M +Noemi/M +Nola/M +Nolan/M +Nolana/M +Noland/M +Nolie/M +Noll/M +Nollie/M +Nolly/M +Nome/M +Nomi/M +Nona/M +Nonah/M +Noni/M +Nonie/M +Nonna/M +Nonnah/M +Nora/M +Norah/M +Norbert/M +Norberto/M +Norbie/M +Norby/M +Nordhoff/M +Nordic/S +Nordstrom/M +Norean/M +Noreen/M +Norene/M +Norfolk/M +Norina/M +Norine/M +Norma/M +Norman/SM +Normand/M +Normandy/M +Normie/M +Normy/M +Norplant +Norri/SM +Norrie/M +Norristown/M +Norry/M +Norse +Norseman/M +Norsemen +North/M +Northampton/M +Northeast/SM +Northerner/M +Northfield/M +Northrop/M +Northrup/M +Norths +Northumberland/M +Northwest/MS +Norton/M +Norw/M +Norwalk/M +Norway/M +Norwegian/S +Norwich/M +Nosferatu/M +Nostradamus/M +Nostrand/M +Notre/M +Nottingham/M +Nouakchott/M +Noumea/M +Nov/M +Nova/M +Novak/M +Novelia/M +Novell/SM +November/SM +Novgorod/M +Novocain/S +Novocaine/M +Novokuznetsk/M +Novosibirsk/M +Nowell/M +Noyce/M +Noyes/M +Np +Nubia/M +Nubian/M +Nugent/M +Nukualofa +Numbers/M +Nunavut/M +Nunez/M +Nunki/M +Nuremberg/M +Nureyev/M +Nutrasweet/M +Nyasa/M +Nydia/M +Nye/M +Nyerere/M +Nyquist/M +Nyssa/M +O +O'Brien/M +O'Casey +O'Clock +O'Connell/M +O'Connor/M +O'Dell/M +O'Donnell/M +O'Dwyer/M +O'Er +O'Hara +O'Hare/M +O'Higgins +O'Keeffe +O'Leary/M +O'Neil +O'Neill +O'Shea/M +O'Sullivan/M +OAS +OB +OCR +OD +ODs +OE +OED +OEM/M +OEMS +OH +OHSA/M +OJ +OK/MDG +OKs +ON +OOo/M +OPEC +OR +OS/M +OSHA +OT +OTB +OTC +OTOH +Oahu/M +Oakland/M +Oakley/M +Oakmont/M +Oates/M +Oaxaca/M +Ob/MD +Obadiah/M +Obadias/M +Obed/M +Obediah/M +Oberlin/M +Oberon/M +Obidiah/M +Obie/M +Oby/M +Occam/M +Occident/SM +Occidental/S +Oceania/M +Oceanside/M +Oceanus/M +Ochoa/M +Oconomowoc/M +Oct/M +Octavia/M +Octavian/M +Octavio/M +Octavius/M +October/MS +Ode/MR +Odele/M +Odelia/M +Odelinda/M +Odell/M +Odella/M +Odelle/M +Oder/M +Oderberg/MS +Odessa/M +Odets/M +Odetta/M +Odette/M +Odey/M +Odie/M +Odilia/M +Odille/M +Odin/M +Odis/M +Odo/M +Odom/M +Ody/M +Odysseus/M +Odyssey/M +Oedipal/Y +Oedipus/M +Oersted/M +Ofelia/M +Ofella/M +Offenbach/M +Ofilia/M +Ogbomosho/M +Ogdan/M +Ogden/M +Ogdon/M +Ogilvy/M +Oglethorpe/M +Ohio/M +Ohioan/S +Oise/M +Ojibwa/SM +Okamoto/M +Okayama/M +Okeechobee/M +Okefenokee +Okhotsk/M +Okinawa/M +Okinawan/S +Okla/M +Oklahoma/M +Oklahoman/SM +Oktoberfest +Ola/M +Olaf/M +Olag/M +Olav/M +Oldenburg/M +Oldfield/M +Oldsmobile/M +Olduvai/M +Ole/MV +Oleg/M +Olen/M +Olenek/M +Olenka/M +Olenolin/M +Olga/M +Olia/M +Oligocene +Olimpia/M +Olin/M +Olive/MZR +Oliver/M +Olivero/M +Olivette/M +Olivetti/M +Olivia/M +Olivie/RM +Olivier/M +Oliviero/M +Oliy/M +Ollie/M +Olly/M +Olmec +Olmsted/M +Olsen/M +Olson/M +Olva/M +Olvan/M +Olwen/M +Olympe/M +Olympia/SM +Olympiad/MS +Olympian/S +Olympic/S +Olympie/M +Olympus/M +Omaha/SM +Oman/M +Omar/M +Omdurman/M +Omero/M +Omnipotent +Omsk/M +Onassis/M +Ondrea/M +Oneal/M +Onega/M +Onegin/M +Oneida/SM +Onfre/M +Onfroi/M +Onida/M +Ono/M +Onofredo/M +Onondaga/MS +Onsager/M +Ont/M +Ontarian/S +Ontario/M +Oona/M +Oort/M +Opal/M +Opalina/M +Opaline/M +Opel/M +OpenOffice.org/M +Ophelia/M +Ophelie/M +Ophiuchus/M +Oppenheimer/M +Oprah/M +Ora/M +Oralee/M +Oralia/M +Oralie/M +Oralla/M +Oralle/M +Oran/M +Orange/M +Oranjestad/M +Orazio/M +Orbadiah/M +Ordovician +Ore/NM +Oreg/M +Oregon/M +Oregonian/S +Orel/M +Orelee/M +Orelia/M +Orelie/M +Orella/M +Orelle/M +Oren/M +Oreo +Orestes +Oriana/M +Orient/SM +Oriental/S +Orin/M +Orinoco/M +Orion/M +Oriya/M +Orizaba/M +Orkney/M +Orlan/M +Orland/M +Orlando/M +Orleans +Orlick/M +Orlon/SM +Orly/M +Orono/M +Orpheus/M +Orphic +Orr/MN +Orran/M +Orren/M +Orrin/M +Orsa/M +Orsola/M +Orson/M +Ortega/M +Ortensia/M +Orthodox/S +Ortiz/M +Orton/M +Orv/M +Orval/M +Orville/M +Orwell/M +Orwellian +Os/M +Osage/SM +Osaka/M +Osbert/M +Osborn/M +Osborne/M +Osbourn/M +Osbourne/M +Oscar/SM +Osceola/M +Osgood/M +Oshawa/M +Oshkosh/M +Osiris/M +Oslo/M +Osman/M +Osmond/M +Osmund/M +Ossie/M +Ostrander/M +Ostrogoth/M +Ostwald/M +Osvaldo/M +Oswald/M +Oswell/M +Otes +Otha/M +Othelia/M +Othella/M +Othello/M +Othilia/M +Othilie/M +Otho/M +Otis/M +Ottawa/MS +Ottilie/M +Otto/M +Ottoman +Ouagadougou/M +Ouija/MS +Ovid/M +Owen/MS +Oxford/MS +Oxnard +Oxonian +Oxus/M +Oz/M +Ozark/SM +Ozymandias/M +Ozzie/M +Ozzy/M +P +P's +PA +PAC +PARC/M +PASCAL +PBS +PBX +PC/M +PCB +PCP +PCs +PD +PDP +PDQ +PDT +PE +PET +PFC +PG +PIN +PJ's +PLO +PM +PMS +PO +POW +PP +PPS +PR +PRC +PRO +PS +PST +PT +PTA +PTO +PVC +PW +PX +Pa/M +Pablo/M +Pablum/M +Pabst/M +Pace/M +Pacheco/M +Pacific/M +Packard/SM +Packston/M +Packwood/M +Paco/M +Pacorro/M +Padang/M +Paddie/M +Paddy/M +Padget/M +Padgett/M +Padilla/M +Padraic/M +Padraig/M +Padrewski/M +Padriac/M +Paganini/M +Page/M +Paglia/M +Pahlavi/M +Paige/M +Pail/M +Paine/M +Pakistan/M +Pakistani/S +Palatine +Palembang/M +Paleocene +Paleogene +Paleolithic +Paleozoic +Palermo/M +Palestine/M +Palestinian/S +Palestrina/M +Paley/M +Palisades/M +Pall/M +Palladio/M +Palm/MR +Palmer/M +Palmerston/M +Palmolive/M +Palmyra/M +Palo/M +Paloma/M +Palomar/M +Pam/M +Pamela/M +Pamelina/M +Pamella/M +Pamirs +Pammi/M +Pammie/M +Pammy/M +Pampers +Pan/M +Panama/MS +Panamanian/S +Panchito/M +Pancho/M +Pandora/M +Pangaea/M +Pankhurst/M +Panmunjom/M +Pansie/M +Pansy/M +Pantagruel/M +Pantaloon/M +Panza/M +Paola/M +Paoli/M +Paolina/M +Paolo/M +Papagena/M +Papageno/M +Pappas/M +Paquito/M +Paracelsus/M +Paraclete/M +Paradise/M +Paraguay/M +Paraguayan/S +Paramaribo/M +Paramus/M +Paraná +Parcheesi/M +Pareto/M +Paris/M +Parisian/SM +Park/RMS +Parke/M +Parker/M +Parkersburg/M +Parkhouse/M +Parkinson/M +Parkman +Parliament/MS +Parmesan/S +Parnassus/SM +Parnell/M +Parr/M +Parrish/M +Parrnell/M +Parry/M +Parsee's +Parsifal/M +Parsons/M +Parthenon/M +Parthia/M +Pasadena/M +Pascal/M +Pascale/M +Paso/M +Pasquale/M +Passaic/M +Passion/SM +Passover/MS +Pasternak/M +Pasteur/M +Pat/MN +Patagonia/M +Patagonian/S +Pate/M +Patel/M +Paten/M +Paterson/M +Patience/M +Patin/M +Patna/M +Paton/M +Patric/M +Patrica/M +Patrice/M +Patricia/M +Patricio/M +Patrick/M +Patrizia/M +Patrizio/M +Patrizius/M +Patsy/SM +Patten/M +Patterson/M +Patti/M +Pattie/M +Pattin/M +Patton/M +Patty/M +Paul/MG +Paula/M +Paule/M +Pauletta/M +Paulette/M +Pauli/M +Paulie/M +Paulina/M +Pauline +Pauling/M +Paulita/M +Paulo/M +Paulsen/M +Paulson/M +Paulus/M +Pauly/M +Pavarotti +Pavel/M +Pavia/M +Pavla/M +Pavlov/M +Pavlova/MS +Pavlovian +Pawnee/SM +Pawtucket/M +Paxon/M +Paxton/M +Payne/SM +Payson/M +Payton/M +Paz/M +Pb/M +Pd/M +Peabody/M +Peace/M +Peachtree/M +Peadar/M +Peale/M +Pearce/M +Pearl/M +Pearla/M +Pearle/M +Pearlie/M +Pearline/M +Pearson/M +Peary/M +Pebrook/M +Pechora/M +Peck/M +Peckinpah/M +Pecos/M +Peder/M +Pedro/M +Peel/M +Peg/M +Pegasus/MS +Pegeen/M +Peggi/M +Peggie/M +Peggy/M +Pei/M +Peiping/M +Peirce/M +Pekinese's +Peking/SM +Pekingese/SM +Pele/M +Pelee/M +Pelham/M +Peloponnese/M +Pembroke/M +Pen/M +Pena/M +Penderecki/M +Pendleton/M +Penelopa/M +Penelope/M +Penn/M +Penna +Penney/M +Penni/M +Pennie/M +Pennington/M +Pennsylvania/M +Pennsylvanian/S +Penny/M +Penrod/M +Pensacola/M +Pentagon/M +Pentateuch/M +Pentecost/SM +Pentecostal/S +Pentecostalism/S +Pentium/M +Peoria/M +Pepe/M +Pepi/M +Pepillo/M +Pepin/M +Pepita/M +Pepito/M +Pepsi/M +PepsiCo/M +Pepsico/M +Pepys/M +Pequot/M +Perceval/M +Percival/M +Percy/M +Perelman/M +Perez/M +Pergamon/M +Peri/M +Peria/M +Perice/M +Periclean +Pericles/M +Perilla/M +Perkin/SM +Perl/M +Perla/M +Perle/M +Perm/M +Permalloy/M +Permian +Pernell/M +Pernod/M +Peron/M +Perot/M +Perren/M +Perri/M +Perrine/M +Perry/MR +Perseid/M +Persephone/M +Perseus/M +Pershing/M +Persia/M +Persian/S +Persis/M +Perth/M +Peru/M +Peruvian/S +Peshawar/M +Pet/MRZ +Peta/M +Pete/M +Peter/M +Peters/N +Petersburg/M +Petersen/M +Peterson/M +Peterus/M +Petey/M +Petkiewicz/M +Petr/M +Petra/M +Petrarch/M +Petrina/M +Petronella/M +Petronia/M +Petronilla/M +Petronille/M +Pettibone/M +Petty/M +Petunia/M +Peugeot/M +Pewaukee/M +Peyter/M +Peyton/M +Pfc +Pfizer/M +Ph/M +PhD +Phaedra/M +Phaethon/M +Phaidra/M +Phanerozoic +Pharaoh/M +Pharaohs +Pharisaic +Pharisaical +Pharisee/SM +Phebe/M +Phedra/M +Phekda/M +Phelia/M +Phelps/M +Phidias/M +Phil/MY +Philadelphia/M +Philbert/M +Philco/M +Philip/M +Philipa/M +Philippa/M +Philippe/M +Philippians/M +Philippine/SM +Philis/M +Philistine/SM +Phillida/M +Phillie/M +Phillip/MS +Phillipa/M +Phillipe/M +Phillipp/M +Phillis/M +Philly/SM +Philomena/M +Phineas/M +Phip/M +Phipps/M +Phobos/M +Phoebe/M +Phoenicia/M +Phoenician/SM +Phoenix/M +Photostat/MS +Photostatted +Photostatting +Phylis/M +Phyllida/M +Phyllis/M +Phyllys/M +Phylys/M +Pia/M +Piaf/M +Piaget/M +Pianola/M +Picasso/M +Piccadilly/M +Pickering/M +Pickett/M +Pickford/M +Pickman/M +Pickwick/M +Pict/M +Piedmont/M +Pier/M +Pierce/M +Pierette/M +Pierre/M +Pierrette/M +Pierrot/M +Pierson/M +Pieter/M +Pietra/M +Pietrek/M +Pietro/M +Piggy/M +Pigmy's +Pike/M +Pilate/M +Pilcomayo/M +Pilgrim +Pillsbury/M +Pinatubo/M +Pincas/M +Pinchas/M +Pincus/M +Pindar/M +Pinehurst/M +Pinkerton/M +Pinocchio/M +Pinochet/M +Pinsky/M +Pinter/M +Pinyin +Piotr/M +Pip/MR +Piper/M +Pipestone/M +Pippa/M +Pippo/M +Pippy/M +Piraeus/M +Pirandello/M +Pisa/M +Pisces/M +Pisistratus/M +Pissaro/M +Pitcairn/M +Pitney/M +Pitt/SM +Pittman/M +Pittsburgh/ZM +Pittsfield/M +Pittston/M +Pius/M +Pizarro/M +Pkwy +Plainfield/M +Plainview/M +Planck/M +Plano +Plantagenet/M +Plasticine/M +Plath/M +Plato/M +Platonic +Platonism/M +Platonist +Platte/M +Platteville/M +Plautus/M +Playboy/M +Playtex/M +Pleiads +Pleistocene +Plexiglas/MS +Pliny/M +Pliocene/S +Plutarch/M +Pluto/M +Plymouth/M +Pm/M +Po/M +Pocahontas/M +Pocono/MS +Podgorica/M +Podunk/M +Poe/M +Pogo/M +Poincaré/M +Poindexter/M +Poisson/M +Pokemon/M +Pol/MY +Poland/M +Polanski/M +Polaris/M +Polaroid/SM +Pole/MS +Polish +Politburo/M +Polk/M +Pollard/M +Pollock/SM +Pollux/M +Polly/M +Pollyanna/M +Polo/M +Polyhymnia/M +Polynesia/M +Polynesian/S +Polyphemus/M +Pomerania/M +Pomeranian +Pomona/M +Pompadour/M +Pompeian/S +Pompeii/M +Pompey/M +Ponce/M +Ponchartrain/M +Pontchartrain/M +Pontiac/M +Pontianak/M +Pooh/M +Poole/M +Poona/M +Pope/SM +Popek/MS +Popeye/M +Popocatepetl/M +Popper/M +Poppins/M +Poppy/M +Popsicle/MS +Porfirio/M +Porrima/M +Porsche/M +Port/MR +Porte/M +Porter/M +Portia/M +Portie/M +Portland/M +Portsmouth/M +Portugal/M +Portuguese/M +Porty/M +Poseidon/M +Posner/M +Post/M +Potemkin/M +Potomac/M +Potsdam/M +Pottawatomie/M +Potter/M +Potts/M +Poughkeepsie/M +Poul/M +Pound/M +Poussin/MS +Powell/M +Powers +Powhatan/M +Poznan/M +Pr/MN +Pradesh/M +Prado/M +Praetorian +Prague/M +Praia +Prakrit/M +Pratchett/M +Pratt/M +Prattville/M +Pravda/M +Praxiteles/M +Preakness/M +Precambrian +Preminger/M +Pren/M +Prent/M +Prentice/MGD +Prenticed/M +Prenticing/M +Prentiss/M +Pres +Presbyterian/S +Presbyterianism/S +Prescott/M +Presley/M +Preston/M +Pretoria/M +Priam/M +Pribilof/M +Price/M +Priestley/M +Prime's +Prince/M +Princeton/M +Principe/M +Principia/M +Prinz/M +Pris +Prisca/M +Priscella/M +Priscilla/M +Prissie/M +Procrustean +Procrustes/M +Procyon/M +Prof +Prohibition/MS +Prokofieff/M +Prokofiev/M +Promethean +Prometheus/M +Proserpine/M +Protagoras/M +Proterozoic/M +Protestant/SM +Protestantism/MS +Proteus/M +Proudhon/M +Proust/M +Provencals +Provence/M +Provençal +Proverbs/M +Providence/SM +Provo/M +Proxmire/M +Prozac +Pru/M +Prudence/M +Prudential/M +Prudi/M +Prudy/M +Prue/M +Pruitt/M +Prussia/M +Prussian/S +Prut/M +Pryce/M +Psalms/M +Psalter/SM +Psyche/M +Pt/M +Ptah/M +Ptolemaic +Ptolemaists +Ptolemy/MS +Pu +Puccini/M +Puck/M +Puckett/M +Puebla/M +Pueblo/MS +Puerto/M +Puff/M +Puget/M +Pugh/M +Pulaski/SM +Pulitzer/SM +Pullman/MS +Punch/M +Punic +Punjab/M +Punjabi/M +Purcell/M +Purdue/M +Purim/SM +Purina/M +Puritan/SM +Puritanism/MS +Purus +Pusan/M +Pusey/M +Pushkin/M +Pushtu/M +Putin/M +Putnam/M +Putnem/M +Pvt/M +Pygmalion/M +Pygmy/SM +Pyhrric/M +Pyle/M +Pym/M +Pynchon/M +Pyongyang/M +Pyotr/M +Pyrenees +Pyrex/SM +Pyrrhic +Pythagoras/M +Pythagorean/S +Pythias +Python/M +Pétain/M +Pôrto/M +Q +Q's +QA +QB +QC +QED +QM +Qaddafi/M +Qantas/M +Qatar/M +Qingdao +Qiqihar/M +Qom/M +Quaalude/M +Quaker/SM +Quakeress/M +Quakerism/S +Quantico/M +Quasimodo/M +Quaternary +Quayle/M +Que/M +Quebec/M +Quechua/M +Queen/SM +Queenie/M +Queensland/M +Quent/M +Quentin/M +Querida/M +Quetzalcoatl/M +Quezon/M +Quill/M +Quillan/M +Quincey/M +Quincy/M +Quinlan/M +Quinn/M +Quint/M +Quinta/M +Quintana/M +Quintilian/M +Quintilla/M +Quintin/M +Quintina/M +Quinton/M +Quintus/M +Quirinal/M +Quisling/M +Quito/M +Quixote/M +Quixotism/M +Quonset +R's +R/G +RAF +RAM/S +RBI/S +RC +RCA +RCS +RD +RDA +REIT +REM/S +RF +RFC +RFD +RI +RIP +RISC +RMS +RN +RNA +ROFL +ROM +ROTC +RP +RPM +RR +RSFSR +RSI +RSV +RSVP +RSX +RTFM +RV +RVs +Ra/M +Rab/M +Rabat/M +Rabbi/M +Rabelais/M +Rabelaisian +Rabi/M +Rabin/M +Rachael/M +Rachel/M +Rachele/M +Rachelle/M +Rachmaninoff/M +Racine/M +Rad/M +Radcliffe/M +Raddie/M +Raddy/M +Rae/M +Raeann/M +Raf/M +Rafa/M +Rafael/M +Rafaela/M +Rafaelia/M +Rafaelita/M +Rafaellle/M +Rafaello/M +Rafe/M +Raff/M +Raffaello/M +Raffarty/M +Rafferty/M +Rafi/M +Ragnar/M +Ragnarök +Rahal/M +Rahel/M +Raimondo/M +Raimund/M +Raimundo/M +Raina/M +Raine/MR +Rainer/M +Rainier/M +Rajive/M +Rakel/M +Raleigh/M +Ralf/M +Ralina/M +Ralph/M +Ralston/M +Ram/M +Rama/M +Ramada/M +Ramadan/SM +Ramakrishna/M +Raman/M +Ramayana/M +Rambo/M +Ramirez/M +Ramiro/M +Ramo/MS +Ramon/M +Ramona/M +Ramonda/M +Ramsay/M +Ramses/M +Ramsey/M +Rana/M +Rance/M +Rancell/M +Rand/M +Randa/M +Randal/M +Randall/M +Randee/M +Randell/M +Randene/M +Randi/M +Randie/M +Randolf/M +Randolph/M +Randy/M +Ranee/M +Rangoon/M +Rani/MR +Rania/M +Ranice/M +Ranier/M +Ranique/M +Rankin/M +Rankine/M +Ranna/M +Ransell/M +Ransom/M +Raoul/M +Raphael/M +Raphaela/M +Rapunzel/M +Raquel/M +Raquela/M +Rasalgethi/M +Rasalhague/M +Rasia/M +Rasla/M +Rasmussen/M +Rasputin/M +Rastaban/M +Rastafarian/M +Rastus/M +Ratfor/M +Rather/M +Ratliff/M +Raul/M +Ravel/M +Raven/M +Ravi/M +Ravid/M +Raviv/M +Rawalpindi/M +Rawley/M +Rawlings/M +Rawlins/M +Rawlinson/M +Rawson/M +Ray/M +Rayburn/M +Raychel/M +Raye/M +Rayleigh/M +Raymond/M +Raymondville/M +Raymund/M +Raymundo/M +Rayna/M +Raynard/M +Raynell/M +Rayner/M +Raynor/M +Rayshell/M +Raytheon/M +Rb +Rd/M +Re/M +Rea/M +Read/GM +Reade/M +Reading/M +Reagan/M +Reagen/M +Realtor/S +Reamonn/M +Reasoner/M +Reba/M +Rebbecca/M +Rebe/M +Rebeca/M +Rebecca's +Rebecka/M +Rebeka/M +Rebekah/M +Rebekkah/M +Recife/M +Reconstruction/M +Redd/M +Redeemer/M +Redford/M +Redgrave/M +Redhook/M +Redmond/M +Redondo/M +Redstone/M +Ree/MDS +Reeba/M +Reebok/M +Reece/M +Reed/M +Reedville/M +Reena/M +Reese/M +Reeta/M +Reeva/M +Reeves +Refugio/M +Reg/MN +Regan/M +Regen/M +Reggi/MS +Reggie/M +Reggy/M +Regina/M +Reginae +Reginald/M +Reginauld/M +Regine/M +Regis/M +Regor/M +Regulus/M +Rehnquist +Reich/M +Reichenberg/M +Reichstag's +Reichstags +Reid/MR +Reidar/M +Reider/M +Reiko/M +Reilly/M +Reina/M +Reinald/M +Reinaldo/MS +Reine/M +Reinhard/M +Reinhardt/M +Reinhold/M +Reinold/M +Reinwald/M +Rem/M +Remarque/M +Rembrandt/M +Remington/M +Remus/M +Remy/M +Rena/M +Renado/M +Renae/M +Renaissance/SM +Renaldo/M +Renard/M +Renascence/SM +Renata/M +Renate/M +Renato/M +Renaud/M +Renault/MS +Rene/M +Renee/M +Renell/M +Renelle/M +Renie/M +Rennie/M +Reno/M +Renoir/M +Rensselaer/M +Renville/M +Rep/M +Representative/S +Republican/S +Republicanism/S +Requiem/MS +Resistance/SM +Restoration/M +Reta/M +Retha/M +Reub/NM +Reube/M +Reuben/M +Reunion/M +Reuters +Reuther/M +Reuven/M +Rev/M +Reva/M +Revelation/MS +Revere/M +Reverend +Revkah/M +Revlon/M +Rex/M +Rey/M +Reyes +Reykjavik/M +Reyna/M +Reynaldo/M +Reynard/M +Reynold/SM +Rf +Rh/M +Rhea/M +Rheba/M +Rhee/M +Rheims/M +Rheinholdt/M +Rhenish +Rheta/M +Rhett/M +Rhetta/M +Rhiamon/M +Rhianna/M +Rhiannon/M +Rhianon/M +Rhine/M +Rhineland/RM +Rhinelander/M +Rhoda/M +Rhodes +Rhodesia/M +Rhodesian/S +Rhodia/M +Rhodie/M +Rhody/M +Rhona/M +Rhonda/M +Rhone +Rhys/M +Riane/M +Riannon/M +Rianon/M +Ribbentrop/M +Ric/M +Rica/M +Rican/SM +Ricard/M +Ricardo/M +Ricca/M +Riccardo/M +Rice/M +Rich/M +Richard/MS +Richardo/M +Richardson/M +Richart/M +Richelieu/M +Richey/M +Richfield/M +Richie/M +Richland/M +Richmond/M +Richmound/M +Richter/M +Richthofen/M +Richy/M +Rici/M +Rick/M +Rickard/M +Rickenbacker/M +Rickenbaugh/M +Rickert/M +Rickey/M +Ricki/M +Rickie/M +Rickover/M +Ricky/M +Rico/M +Ricoriki/M +Riddle/M +Ride/M +Ridgefield/M +Ridgway/M +Riemann/M +Riesling/SM +Riga/M +Rigel/M +Riggs/M +Right/S +Rigoberto/M +Rigoletto/M +Rik/M +Riki/M +Rikki/M +Riley/M +Rilke/M +Rimbaud/M +Rina/M +Rinaldo/M +Rinehart/M +Ring/M +Ringling/M +Ringo/M +Rio/MS +Riobard/M +Riordan/M +Rip/M +Ripley/M +Risa/M +Rita/M +Ritalin +Ritchie/M +Ritter/M +Ritz/M +Riva/MS +Rivalee/M +Rivera/M +Rivers +Riverside/M +Riverview/M +Rivi/M +Riviera/MS +Rivkah/M +Rivy/M +Riyadh/M +Rn/M +Roach/M +Roana/M +Roanna/M +Roanne/M +Roanoke/M +Roarke/M +Rob/MZ +Robb/M +Robbert/M +Robbi/M +Robbie/M +Robbin/MS +Robby/M +Robbyn/M +Robena/M +Robenia/M +Robers/M +Roberson/M +Robert/MS +Roberta/M +Roberto/M +Robertson/SM +Robeson/M +Robespierre/M +Robin/M +Robina/M +Robinet/M +Robinett/M +Robinetta/M +Robinette/M +Robinia/M +Robinson/M +Robinsonville/M +Robles/M +Robson/M +Robt/M +Roby/M +Robyn/M +Rocco/M +Roch/M +Rocha/M +Rochambeau/M +Roche/M +Rochell/M +Rochella/M +Rochelle/M +Rochester/M +Rochette/M +Rock/M +Rockaway/MS +Rockefeller/M +Rockey/M +Rockford/M +Rockie/M +Rockland/M +Rockne/M +Rockville/M +Rockwell/M +Rocky/SM +Rod/M +Roda/M +Rodd/M +Roddenberry/M +Roddie/M +Roddy/M +Roderic/M +Roderich/M +Roderick/M +Roderigo/M +Rodge/ZMR +Rodger/M +Rodi/M +Rodie/M +Rodin/M +Rodina/M +Rodney/M +Rodolfo/M +Rodolph/M +Rodolphe/M +Rodrick/M +Rodrigo/M +Rodriguez/M +Rodrique/M +Rodriquez/M +Roentgen's +Rog/MRZ +Rogelio/M +Roger/M +Rogerio/M +Roget/M +Roi/SM +Rojas/M +Roland/M +Rolando/M +Roldan/M +Roley/M +Rolf/M +Rolfe/M +Rolland/M +Rollerblade/S +Rollie/M +Rollin/SM +Rollo/M +Rolodex +Rolph/M +Rolvaag/M +Rom/SM +Roma/M +Romain/M +Roman/SM +Romanesque/S +Romania/M +Romanian/SM +Romano/MS +Romanov/M +Romans/M +Romansh/M +Romanticism/S +Romany/SM +Rome/SM +Romeo/MS +Romero/M +Rommel/M +Romney/M +Romola/M +Romona/M +Romonda/M +Romulus/M +Romy/M +Ron/M +Rona/M +Ronald/M +Ronalda/M +Ronda/M +Ronica/M +Ronna/M +Ronni/M +Ronnica/M +Ronnie/M +Ronny/M +Ronstadt/M +Rontgen +Roobbie/M +Rooney/M +Roosevelt/M +Rooseveltian +Root/M +Roquefort/MS +Roquemore/M +Rora/M +Rori/M +Rorie/M +Rorke/M +Rorschach +Rory/M +Ros/N +Rosa/M +Rosabel/M +Rosabella/M +Rosabelle/M +Rosaleen/M +Rosales/M +Rosalia/M +Rosalie/M +Rosalind/M +Rosalinda/M +Rosalinde/M +Rosaline/M +Rosalyn/M +Rosalynd/M +Rosamond/M +Rosamund/M +Rosana/M +Rosanna/M +Rosanne/M +Rosario/M +Rosco/M +Roscoe/M +Rose/M +Roseann/M +Roseanna/M +Roseanne/M +Roseau +Rosecrans/M +Roseland/M +Roselia/M +Roselin/M +Roseline/M +Rosella/M +Roselle/M +Rosemaria/M +Rosemarie/M +Rosemary/M +Rosemonde/M +Rosen/M +Rosenberg/M +Rosenblum/M +Rosendo/M +Rosene/M +Rosenthal/M +Rosenzweig/M +Rosetta/M +Rosette/M +Roshelle/M +Rosicrucian/M +Rosie/M +Rosina/M +Rosita/M +Roslyn/M +Rosmunda/M +Ross +Rossetti/M +Rossi/M +Rossie/M +Rossini/M +Rossy/M +Rostand/M +Rostov/M +Roswell/M +Rosy/M +Rotarian/SM +Roth/M +Rothschild/M +Rotterdam/M +Rouault/M +Rourke/M +Rousseau/M +Rouvin/M +Rover/M +Row/MN +Rowan/M +Rowe/M +Rowen/M +Rowena/M +Rowland/M +Rowley/M +Rowney/M +Roxana/M +Roxane/M +Roxanna/M +Roxanne/M +Roxi/M +Roxie/M +Roxine/M +Roxy/M +Roy/M +Royal/M +Royall/M +Royce/M +Roz/M +Rozalie/M +Rozalin/M +Rozamond/M +Rozanna/M +Rozanne/M +Roze/M +Rozele/M +Rozella/M +Rozelle/M +Rozina/M +Rriocard/M +Rte +Ru/MH +Rubaiyat/M +Rube/M +Ruben/MS +Rubetta/M +Rubi/M +Rubia/M +Rubicon/SM +Rubie/M +Rubik/M +Rubin/M +Rubina/M +Rubinstein/M +Ruby/M +Ruchbah/M +Rudd/M +Ruddie/M +Ruddy/M +Rudie/M +Rudiger/M +Rudolf/M +Rudolfo/M +Rudolph/M +Rudy/M +Rudyard/M +Rufe/M +Rufus/M +Rugby's +Ruggiero/M +Ruhr/M +Ruiz/M +Rumania's +Rumanian's +Rumford/M +Rummel/M +Rumpelstiltskin/M +Runge/M +Runnymede/M +Runyon/M +Rupert/M +Ruperta/M +Ruperto/M +Ruppert/M +Ruprecht/M +Rurik/M +Rush/M +Rushdie/M +Rushmore/M +Ruskin/M +Russ/S +Russel/M +Russell/M +Russia/M +Russian/SM +Russo/M +Rustbelt/M +Rustie/M +Rustin/M +Rusty/M +Rutger/SM +Ruth/M +Ruthann/M +Ruthanne/M +Ruthe/M +Rutherford/M +Ruthi/M +Ruthie/M +Ruthy/M +Rutland/M +Rutledge/M +Rutter/M +Ruttger/M +Ruy/M +Rwanda/SM +Rwandan/S +Rwy/M +Rx/M +Ry/M +Ryan/M +Ryann/M +Rycca/M +Rydberg/M +Ryder/M +Ryley/M +Ryon/M +Ryukyu/M +Ryun/M +S +S's +SA +SAC +SALT +SAM +SASE +SAT +SBA +SC +SCCS +SCSI +SD +SDI +SE +SEATO +SEC +SF +SIDS +SIGGRAPH/M +SIMD +SIMULA/M +SJ +SK +SLR +SMSA/MS +SMTP +SO +SOP +SOS +SPARC/M +SPARCstation/M +SPCA +SPF +SPSS +SRO +SS +SSA +SSE +SSS +SST +SSW +ST +STD +STOL +SUV +SW +SWAK +SWAT +Saab/M +Saar/M +Saba/M +Sabbath/M +Sabbaths +Sabik/M +Sabin/M +Sabina/M +Sabine/M +Sabra/M +Sabrina/M +Sacajawea/M +Sacco/M +Sacha/M +Sachs/M +Sacramento/M +Sada/M +Sadat/M +Saddam/M +Sadducee/M +Sade/M +Sadella/M +Sadie/M +Sadr/M +Sadye/M +Sagan/M +Saginaw/M +Sagittarius/MS +Sahara/M +Saharan/M +Sahel +Saidee/M +Saigon/M +Saiph/M +Sakai/M +Sakhalin/M +Sakharov/M +Saki/M +Sal/MY +Saladin/M +Salado/M +Salaidh/M +Salas/M +Salazar/M +Saleem/M +Salem/M +Salerno/M +Salim/M +Salina/MS +Salinger/M +Salisbury/M +Salish/M +Salk/M +Salle/M +Sallee/M +Salli/M +Sallie/M +Sallust/M +Sally/M +Sallyann/M +Sallyanne/M +Salmon/M +Saloma/M +Salome/M +Salomi/M +Salomo/M +Salomon/M +Salomone/M +Salonika/M +Salton/M +Salvador/M +Salvadoran/S +Salvadorian/S +Salvatore/M +Salvidor/M +Salween/M +Salyut/M +Salz/M +Sam/M +Samantha/M +Samara/M +Samaria/M +Samaritan/MS +Samarkand/M +Sammie/M +Sammy/M +Samoa +Samoan/S +Samoset/M +Samoyed/M +Sampson/M +Samson/M +Samsonite/M +Samuel/SM +Samuele/M +Samuelson/M +San'a +San/M +Sana/M +Sanborn/M +Sanchez/M +Sancho/M +Sand/MRZ +Sandburg/M +Sande/M +Sander/M +Sanderling/M +Sanderson/M +Sandi/M +Sandia/M +Sandie/M +Sandinista +Sandor/M +Sandoval/M +Sandra/M +Sandro/M +Sandusky/M +Sandy/M +Sandye/M +Sanford/M +Sanforized +Sang/RM +Sanger/M +Sanhedrin/M +Sankara/M +Sanskrit/M +Sanskritic +Sanskritize/M +Sanson/M +Sansone/M +Santa/M +Santana/M +Santayana/M +Santeria +Santiago/M +Santo/MS +Sapphira/M +Sapphire/M +Sappho/M +Sapporo/M +Sara/M +Saraann/M +Saracen/MS +Saragossa/M +Sarah/M +Sarajane/M +Sarajevo/M +Saran/M +Sarasota/M +Saratoga/M +Saratov/M +Sarawak/M +Sardinia/M +Saree/M +Sarena/M +Sarene/M +Sarette/M +Sargasso/M +Sarge/M +Sargent/M +Sargon/M +Sari/M +Sarina/M +Sarine/M +Sarita/M +Sarnoff/M +Saroyan/M +Sarto/M +Sartre/M +Sascha/M +Sasha/M +Sashenka/M +Sask/M +Saskatchewan/M +Saskatoon/M +Sassoon/M +Sat/M +Satan/M +Satanism/M +Satanist/M +Saturday/MS +Saturn/M +Saturnalia/M +Satyanarayanan/M +Saud/M +Saudi/S +Saudra/M +Saukville/M +Saul/M +Sault/M +Sauncho/M +Saunder/SM +Saunderson/M +Saundra/M +Saussure/M +Sauternes/M +Sauveur/M +Savage/M +Savannah/M +Savina/M +Savior/M +Saviour/M +Savonarola/M +Savoy/M +Savoyard/M +Saw/M +Sawyer/M +Sawyere/M +Sax/M +Saxe/M +Saxon/SM +Saxony/M +Saxton/M +Say/ZMR +Sayer/M +Sayre/MS +Sb/M +Sc/M +Scala/M +Scan +Scandinavia/M +Scandinavian/S +Scaramouch/M +Scarborough/M +Scarface/M +Scarlatti/M +Scarlet/M +Scarlett/M +Schaefer/M +Schaeffer/M +Schafer/M +Schaffner/M +Schantz/M +Schapiro/M +Scheat/M +Schedar/M +Scheherazade/M +Scheherezade/M +Schelling/M +Schenectady/M +Schick/M +Schiller/M +Schlesinger/M +Schliemann/M +Schlitz/M +Schloss/M +Schmidt/M +Schmitt/M +Schnabel/M +Schneider/M +Schoenberg/M +Schofield/M +Schopenhauer/M +Schottky/M +Schrieffer/M +Schroeder/M +Schroedinger/M +Schrödinger/M +Schubert/M +Schultz/M +Schulz/M +Schumacher/M +Schuman/M +Schumann/M +Schuster/M +Schuyler/M +Schuylkill/M +Schwab/M +Schwartz/M +Schwartzkopf/M +Schwarzenegger/M +Schweitzer/M +Schweppes/M +Schwinger/M +Schwinn/M +Scientology/M +Scipio/M +Scopes/M +Scorpio/SM +Scorpius/M +Scorsese/M +Scot/MS +Scotch/S +Scotchgard/M +Scotchman/M +Scotchmen +Scotchwoman +Scotchwomen +Scotia/M +Scotian/M +Scotland/M +Scotsman/M +Scotsmen +Scotswoman +Scotswomen +Scott/M +Scotti/M +Scottie/SM +Scottish +Scottsdale/M +Scotty's +Scout's +Scrabble/SM +Scranton/M +Scriabin/M +Scribner/MS +Scripps/M +Scripture/MS +Scrooge/MS +Scruggs/M +Scud/M +Sculley/M +Scylla/M +Scythia/M +Se/H +Seaborg/M +Seabrook/M +Seagate/M +Seagram/M +Seamus/M +Sean/M +Seana/M +Seaquarium/M +Sears/M +Seattle/M +Sebastian/M +Sebastiano/M +Sebastien/M +Seconal +Seder/SM +Sedgwick/M +See/M +Seebeck/M +Seeley/M +Segovia/M +Segre/M +Segundo/M +Seidel/M +Seiko/M +Seine/M +Seinfeld/M +Seka/M +Sela/M +Selassie/M +Selby/M +Selectric/M +Selena/M +Selene/M +Selestina/M +Seleucid/M +Seleucus/M +Selfridge/M +Selia/M +Selie/M +Selig/M +Selim/M +Selina/M +Selinda/M +Seline/M +Seljuk/M +Selkirk/M +Sella/M +Selle/ZM +Sellers/M +Selma/M +Selznick/M +Semarang/M +Seminole/SM +Semiramis/M +Semite/SM +Semitic/MS +Semtex +Sen +Sena/M +Senate/MS +Sendai/M +Seneca/MS +Senegal/M +Senegalese +Senior/S +Sennacherib/M +Sennett/M +Sensurround/M +Seoul/M +Sephardi/M +Sephira/M +Sepoy/M +Sept/M +September/MS +Septuagint/MS +Sequoia/M +Sequoya/M +Serafin/M +Serb/MS +Serbia/M +Serbian/S +Serbo/M +Serena/M +Serene/M +Serengeti/M +Serge/M +Sergeant/M +Sergei/M +Sergent/M +Sergio/M +Serpens/M +Serra/M +Serrano/M +Set/M +Seth/M +Seton/M +Seumas/M +Seurat/M +Seuss/M +Sevastopol/M +Severn/M +Severus/M +Seville/M +Seward/M +Sextans/M +Sexton/M +Seychelles +Seyfert +Seymour/M +Señora/M +Sgt +Shackleton/M +Shadow/M +Shae/M +Shafer/M +Shaffer/M +Shaina/M +Shaine/M +Shaker/S +Shakespeare/M +Shakespearean/S +Shakespearian +Shalna/M +Shalne/M +Shalom/M +Shamus/M +Shana/M +Shanan/M +Shanda/M +Shandee/M +Shandeigh/M +Shandie/M +Shandra/M +Shandy/M +Shane/M +Shanghai/GM +Shanghaiing/M +Shani/M +Shanie/M +Shanna/M +Shannah/M +Shannan/M +Shannen/M +Shannon/M +Shanon/M +Shanta/M +Shantee/M +Shantung/M +Shapiro/M +Shara/M +Sharai/M +Shari'a +Shari/M +Sharia/M +Sharity/M +Sharl/M +Sharla/M +Sharleen/M +Sharlene/M +Sharline/M +Sharon/M +Sharona/M +Sharp/M +Sharpe/M +Sharron/M +Sharyl/M +Shasta/M +Shaughn/M +Shaula/M +Shaun/M +Shauna/M +Shavian +Shavuot/M +Shaw/M +Shawano/M +Shawn/M +Shawna/M +Shawnee/SM +Shay/M +Shayla/M +Shaylah/M +Shaylyn/M +Shaylynn/M +Shayna/M +Shayne/M +Shcharansky/M +Shea/M +Sheba/M +Shebeli/M +Sheboygan/M +Shedir/M +Sheela/M +Sheelagh/M +Sheelah/M +Sheena/M +Sheeree/M +Sheetrock +Sheff/M +Sheffie/M +Sheffield/RMZ +Sheffielder/M +Sheffy/M +Sheila/M +Sheilah/M +Shel/MY +Shela/M +Shelagh/M +Shelba/M +Shelbi/M +Shelby/M +Shelden/M +Sheldon/M +Shelia/M +Shell/M +Shelley/M +Shelli/M +Shellie/M +Shelly/M +Shelton/M +Shem/M +Shena/M +Shenandoah/M +Shenyang/M +Sheol/M +Shep/M +Shepard/M +Shepherd/M +Sheppard/M +Shepperd/M +Sher/M +Sheratan/M +Sheraton/M +Sheree/M +Sheri/M +Sheridan/M +Sherie/M +Sherill/M +Sherilyn/M +Sherline/M +Sherlock/M +Sherlocke/M +Sherm/M +Sherman/M +Shermie/M +Shermy/M +Sherpa/SM +Sherri/M +Sherrie/M +Sherry/M +Sherwin/M +Sherwood/M +Sherwynd/M +Sherye/M +Sheryl/M +Shetland/S +Shevardnadze/M +Shi'ite +Shields/M +Shiite/SM +Shijiazhuang +Shikoku/M +Shillong/M +Shiloh/M +Shina/M +Shinto/MS +Shintoism/S +Shintoist/MS +Shir/M +Shiraz/M +Shirl/M +Shirlee/M +Shirleen/M +Shirlene/M +Shirley/M +Shirline/M +Shiva/M +Shmuel/M +Shockley/M +Shoji/M +Sholom/M +Shorewood/M +Short/M +Shorthorn/M +Shoshana/M +Shoshanna/M +Shoshone/SM +Shostakovitch/M +Shreveport/M +Shropshire/M +Shu/M +Shulman/M +Shurlock/M +Shurlocke/M +Shurwood/M +Shuttleworth +Shuttleworth's +Shylock/M +Shylockian/M +Si/M +Siam/M +Siamese/M +Sian's +Siana/M +Sianna/M +Sib/M +Sibbie/M +Sibby/M +Sibeal/M +Sibel/M +Sibelius/M +Sibella/M +Sibelle/M +Siberia/M +Siberian/S +Sibilla/M +Sibley/M +Sibyl/M +Sibylla/M +Sibylle/M +Sicilian/S +Siciliana/M +Sicily/M +Sid/M +Sidnee/M +Sidney/M +Sidoney/M +Sidonia/M +Sidonnie/M +Siegel/M +Siegfried/M +Sieglinda/M +Siegmund/M +Siemens/M +Siena/M +Sierpinski/M +Siffre/M +Sig/M +Sigfrid/M +Sigfried/M +Sigismond/M +Sigismondo/M +Sigismund/M +Sigismundo/M +Sigmund/M +Signor/M +Signora/M +Sigrid/M +Sigurd/M +Sigvard/M +Sihanouk/M +Sikh/MS +Sikhism/MS +Sikhs +Sikkim/M +Sikkimese +Sikorsky/M +Silas/M +Sile/M +Sileas/M +Silesia/M +Silurian/S +Silva/M +Silvain/M +Silvan/M +Silvana/M +Silvano/M +Silvanus/M +Silverman/M +Silverstein/M +Silvester/M +Silvia/M +Silvie/M +Silvio/M +Sim/MS +Simenon/M +Simeon/M +Simla/M +Simmonds/M +Simmons/M +Simmonsville/M +Simms/M +Simon/M +Simona/M +Simone/M +Simonette/M +Simonne/M +Simpson/M +Simula/M +Sinai/M +Sinatra/M +Sinclair/M +Sinclare/M +Sindbad/M +Sindee/M +Sindhi/M +Singapore/M +Singaporean/S +Singborg/M +Singer/M +Singleton/M +Sinhalese/M +Sinkiang/M +Siobhan/M +Sioux/M +Siouxie/M +Sir/MS +Sirius/M +Sisely/M +Sisile/M +Sissie/M +Sissy/M +Sistine +Sisyphean +Sisyphus/M +Siusan/M +Siva/M +Siward/M +Sjaelland/M +Skell/M +Skelly/M +Skinner/M +Skip/M +Skipp/RM +Skipper/M +Skippie/M +Skippy/M +Skipton/M +Skopje/M +Sky/M +Skye/M +Skylab/M +Skylar/M +Skyler/M +Slade/M +Slater/M +Slav/MS +Slavic/M +Slavonic/M +Slesinger/M +Sloan/M +Sloane/M +Slocum/M +Slovak/S +Slovakia/M +Slovakian/S +Slovene/S +Slovenia/M +Slovenian/S +Sly/M +Sm/M +Small/M +Smallwood/M +Smetana/M +Smirnoff/M +Smith/M +Smithfield/M +Smithson/M +Smithsonian/M +Smithtown/M +Smitty/M +Smokey/M +Smolensk/M +Smollett/M +Smucker/M +Smuts/M +Smyrna/M +Sn/M +Snake +Snead/M +Sneed/M +Snell/M +Snider/M +Snodgrass/M +Snoopy/M +Snow/M +Snowbelt/SM +Snyder/M +Soc +Socorro/M +Socrates/M +Socratic/S +Soddy/M +Sodom/M +Sofia/M +Sofie/M +Soho/M +Sol/MY +Solis/M +Sollie/M +Solly/M +Solomon/SM +Solon/M +Soloviev/M +Solzhenitsyn/M +Somali/MS +Somalia/M +Somalian/S +Somerset/M +Somerville/M +Somme/M +Somoza/M +Son/M +Sondheim/M +Sondra/M +Sonenberg/M +Songhai/M +Songhua/M +Sonia/M +Sonja/M +Sonni/M +Sonnie/M +Sonnnie/M +Sonny/M +Sonoma/M +Sonora/M +Sontag/M +Sony/M +Sonya/M +Sophey/M +Sophi/M +Sophia/SM +Sophie/M +Sophoclean +Sophocles/M +Sophronia/M +Sopwith/M +Sorbonne/M +Sorcha/M +Sorensen/M +Sorenson/M +Sorrentine/M +Sosa/M +Sosanna/M +Soto/M +Souphanouvong/M +Sousa/M +South/M +Southampton/M +Southeast/MS +Southerner/MS +Southey/M +Southfield/M +Souths +Southwest/MS +Soviet/S +Soweto/M +Soyinka/M +Soyuz/M +Sp/M +Spaatz/M +Spacewar/M +Spackle +Spafford/M +Spahn/M +Spain/M +Spalding/M +Spam/M +Span +Spanglish/S +Spaniard/SM +Spanish/M +Sparkman/M +Sparks +Sparta/M +Spartacus/M +Spartan/S +Speaker's +Spears +Spence/RM +Spencer/M +Spencerian +Spengler/M +Spenglerian +Spense/MR +Spenser/M +Spenserian +Sperry/M +Sphinx/M +Spica/M +Spiegel/M +Spielberg/M +Spike/M +Spillane/M +Spinoza/M +Spiro/M +Spitz/M +Spock/M +Spokane/M +Sposato/M +Springfield/M +Springsteen/M +Sprint/M +Sproul/M +Spuds/M +Sputnik +Squanto +Squaresville/M +Squibb/GM +Squibbing/M +Sr +Srinagar/M +St/M +Sta/M +Stace/M +Stacee/M +Stacey/M +Staci/M +Stacia/M +Stacie/M +Stacy/M +Stael/M +Stafani/M +Staffard/M +Stafford/M +Staffordshire/M +Staford/M +Stahl/M +Staley/M +Stalin/SM +Stalingrad/M +Stalinist +Stallone/M +Stamford/M +Stan/YMS +Standford/M +Standish/M +Stanfield/M +Stanford/M +Stanislas/M +Stanislaus/M +Stanislavsky/M +Stanislaw/M +Stanleigh/M +Stanley/M +Stanly/M +Stanton/M +Stanwood/M +Stapleton/M +Star/M +Stargate/M +Stark/M +Starkey/M +Starla/M +Starlene/M +Starlin/M +Starr/M +Statehouse's +Staten/M +Statler/M +Stauffer/M +Stavro/MS +Ste/M +Stearn/SM +Stearne/M +Steele/M +Steen/M +Stefa/M +Stefan/M +Stefania/M +Stefanie/M +Stefano/M +Steffane/M +Steffen/M +Steffi/M +Steffie/M +Stein/RM +Steinbeck/SM +Steinberg/M +Steinem/M +Steiner/M +Steinmetz/M +Steinway/M +Stella/M +Stendhal/M +Stendler/M +Stengel/M +Stepha/M +Stephan/M +Stephana/M +Stephani/M +Stephanie/M +Stephannie/M +Stephanus/M +Stephen/MS +Stephenie/M +Stephenson/M +Stephi/M +Stephie/M +Stephine/M +Sterling/M +Stern/M +Sternberg/M +Sterne/M +Sterno +Stesha/M +Stetson/SM +Steuben/M +Stevana/M +Steve/M +Steven/MS +Stevena/M +Stevenson/M +Stevie/M +Stevy/M +Steward/M +Stewart/M +Stieglitz/M +Stillman/M +Stillmann/M +Stillwell/M +Stilton/MS +Stimson/M +Stine/M +Stinky/M +Stirling/M +Stockhausen/M +Stockholm/M +Stockton/M +Stoddard/M +Stoic/MS +Stoicism/SM +Stokes/M +Stone/M +Stonehenge/M +Stoppard/M +Storm/M +Stormi/M +Stormie/M +Stormy/M +Stouffer/M +Stout/M +Stowe/M +Strabo/M +Stradivari/SM +Stradivarius/M +Strasbourg/M +Stratford/M +Strauss +Stravinsky/M +Streisand/M +Strickland/M +Strindberg/M +Strom/M +Stromberg/M +Stromboli/M +Strong/M +Strongheart/M +Stu/M +Stuart/MS +Stubblefield/MS +Studebaker/M +Sturm/M +Stuttgart/M +Stuyvesant/M +Stygian +Styrofoam/S +Styx/M +Suarez/M +Subaru/M +Sucre/M +Sudan/M +Sudanese/M +Sudanic/M +Sudetenland/M +Sue/M +Suellen/M +Suetonius/M +Suez/M +Suffolk/M +Sufi/M +Sufism/M +Suharto/M +Sui/M +Sukarno/M +Sukey/M +Suki/M +Sukkot/S +Sukkoth's +Sula/M +Sulawesi/M +Suleiman/M +Sulla/M +Sullivan/M +Sully/M +Sulzberger/M +Sumatra/M +Sumatran/S +Sumeria/M +Sumerian/M +Summer/SM +Summerdale/M +Sumner/M +Sumter/M +Sun +Sunbelt/M +Sundanese/M +Sundas +Sunday/MS +Sung/M +Sunni/MS +Sunnite/SM +Sunny/M +Sunnyvale/M +Sunshine/M +Superior/M +Superman/M +Supt/M +Surabaya/M +Surat/M +Surinam's +Suriname +Surinamese +Surya/M +Sus +Susan/M +Susana/M +Susanetta/M +Susann/M +Susanna/M +Susannah/M +Susanne/M +Susette/M +Susi/M +Susie/M +Susquehanna/M +Sussex/M +Susy/M +Sutherlan/M +Sutherland/M +Sutton/M +Suva/M +Suwanee/M +Suzann/M +Suzanna/M +Suzanne/M +Suzette/M +Suzhou/M +Suzi/M +Suzie/M +Suzuki/M +Suzy/M +Svalbard/M +Sven/M +Svend/M +Svengali +Sverdlovsk/M +Svetlana/M +Swabian/SM +Swahili/MS +Swanee/M +Swansea/M +Swanson/M +Swarthmore/M +Swartz/M +Swazi/SM +Swaziland/M +Swed/MN +Swede/SM +Sweden/M +Swedenborg/M +Swedish +Sweeney/SM +Sweet/M +Swen/M +Swenson/M +Swift/M +Swinburne/M +Swink/M +Swiss/S +Switz/MR +Switzer/M +Switzerland/M +Sybil/M +Sybila/M +Sybilla/M +Sybille/M +Sybyl/M +Syd/M +Sydel/M +Sydelle/M +Sydney/M +Sykes/M +Sylas/M +Sylow/M +Sylvan/M +Sylvania/M +Sylvester/M +Sylvia/M +Sylvie/M +Syman/M +Symington/M +Symon/M +Synge/M +Syracuse/M +Syria/M +Syriac/M +Syrian/SM +Szilard/M +Szymborska/M +T'ang +T's +T/G +TA +TB +TBA +TCP +TD +TDD +TEFL +TELNET/M +TENEX/M +TESL +TESOL +TEirtza/M +THC +TKO +TLC +TM +TN +TNT +TOEFL +TRW +TTL +TV/M +TVA +TVs +TWA/M +TWX +TX +Ta/M +Tab/MR +Tabasco/MS +Tabatha/M +Tabb/M +Tabbatha/M +Tabbi/M +Tabbie/M +Tabbitha/M +Tabby/M +Taber/M +Tabernacle/S +Tabina/M +Tabitha/M +Tabor/M +Tabriz/SM +Tacitus/M +Tacoma/M +Tad/M +Tadd/M +Taddeo/M +Taddeusz/M +Tadeas/M +Tadeo/M +Tades +Tadio/M +Tadzhikistan's +Tadzhikstan/M +Taegu/M +Taejon/M +Taffy/M +Taft/M +Tagalog/SM +Tagore/M +Tagus/M +Tahiti/M +Tahitian/S +Tahoe/M +Taichung/M +Tailor/M +Tainan/M +Taine/M +Taipei/M +Tait/M +Taite/M +Taiwan/M +Taiwanese +Taiyuan/M +Tajikistan +Taklamakan/M +Talbert/M +Talbot/M +Talia/M +Taliesin/M +Talladega/M +Tallahassee/M +Tallahatchie/M +Tallahoosa/M +Tallchief/M +Talley/M +Talleyrand/M +Tallia/M +Tallie/M +Tallinn/M +Tallou/M +Tallulah/M +Tally/M +Talmud/MS +Talmudic +Talmudist/MS +Talya/M +Talyah/M +Tam/M +Tamar/M +Tamara/M +Tamarah/M +Tamarra/M +Tamas +Tameka/M +Tamera/M +Tamerlane/M +Tami/M +Tamika/M +Tamiko/M +Tamil/MS +Tamma/M +Tammany/M +Tammara/M +Tammi/M +Tammie/M +Tammy/M +Tampa/M +Tampax/M +Tamqrah/M +Tamra/M +Tan/M +Tana/M +Tanaka/M +Tananarive/M +Tancred/M +Tandi/M +Tandie/M +Tandy/M +Taney/M +Tanganyika/M +Tangier/M +Tangshan/M +Tanhya/M +Tani/M +Tania/M +Tanisha/M +Tanitansy/M +Tann/RM +Tannenbaum/M +Tanner/M +Tanney/M +Tannhäuser/M +Tannie/M +Tanny/M +Tansy/M +Tantalus/M +Tanya/M +Tanzania/M +Tanzanian/S +Tao/M +Taoism/MS +Taoist/MS +Tapdance/M +Tara/M +Tarah/M +Tarawa/M +Tarazed/M +Tarbell/M +Tarim/M +Tarkington/M +Tarra/M +Tarrah/M +Tarrance/M +Tarrytown/M +Tartar's +Tartary/M +Tartuffe/M +Taryn/M +Tarzan/M +Tasha/M +Tashkent/M +Tasia/M +Tasmania/M +Tasmanian/S +Tass/M +Tatar/SM +Tate/M +Tatiana/M +Tatiania/M +Tatum/M +Taurus/SM +Tawney/M +Tawnya/M +Tawsha/M +Taylor/SM +Tb +Tbilisi/M +Tc/M +Tchaikovsky/M +Te +TeX/M +Teador/M +Teasdale/M +Technicolor/MS +Technion/M +Tecumseh/M +Ted/M +Tedd/M +Tedda/M +Teddi/M +Teddie/M +Teddy/M +Tedi/M +Tedie/M +Tedman/M +Tedmund/M +Tedra/M +Teena/M +Teflon/MS +Tegucigalpa/M +Teheran's +Tehran +Tektronix/M +TelePrompTer/S +TelePrompter/M +Teledyne/M +Telefunken/M +Telemachus/M +Telemann/M +Teletype/SM +Telex/M +Tell/MR +Teller/M +Telnet/M +Telugu/M +Temp/M +Tempe/M +Temple/M +Templeman/M +Templeton/M +Tenex/M +Tenn/M +Tenneco/M +Tennessean/S +Tennessee/M +Tenney/M +Tennyson/M +Tenochtitlan/M +Teodoor/M +Teodor/M +Teodora/M +Teodorico/M +Teodoro/M +Tera/M +Terence/M +Terencio/M +Teresa/M +Terese/M +Tereshkova/M +Teresina/M +Teresita/M +Teressa/M +Teri/M +Teriann/M +Terkel/M +Terpsichore/M +Terra/M +Terran/M +Terrance/M +Terre/M +Terrel/M +Terrell/M +Terrence/M +Terri/M +Terrie/M +Terrijo/M +Terrill/M +Territorial/SM +Territory's +Terry/M +Terrye/M +Tersina/M +Tertiary +Terza/M +Tesla/M +Tess/M +Tessa/M +Tessi/M +Tessie/M +Tessy/M +Tethys/M +Tetons +Teuton/SM +Teutonic +Tex/M +Texaco/M +Texan/S +Texas/MS +Textron/M +Th/M +Thacher/M +Thackeray/M +Thad/M +Thaddeus/M +Thaddus/M +Thadeus/M +Thai/S +Thailand/M +Thain/M +Thaine/M +Thales/M +Thalia/M +Thames +Thane/M +Thanh/M +Thanksgiving/S +Thant/M +Thar/M +Thatch/MR +Thatcher/M +Thaxter/M +Thayer/M +Thayne/M +Thea/M +Theadora/M +Thebault/M +Thebes +Theda/M +Thedric/M +Thedrick/M +Theiler/M +Thekla/M +Thelma/M +Themistocles/M +Theo/M +Theobald/M +Theocritus/M +Theodor/M +Theodora/M +Theodore/M +Theodoric/M +Theodosia/M +Theodosian +Theodosius/M +Theosophy +Theravada/M +Theresa/M +Therese/M +Theresina/M +Theresita/M +Theressa/M +Therine/M +Thermos/SM +Theron/M +Theseus/M +Thespian/S +Thespis/M +Thessalonian +Thessaloníki/M +Thessaly/M +Thia/M +Thibaud/M +Thibaut/M +Thiensville/M +Thieu/M +Thimbu/M +Thimphu +Thom/M +Thoma/SM +Thomasa/M +Thomasin/M +Thomasina/M +Thomasine/M +Thomism/M +Thomistic +Thompson/M +Thomson/M +Thor/M +Thorazine +Thoreau/M +Thorin/M +Thorn/M +Thornburg/M +Thorndike/M +Thornie/M +Thornton/M +Thorny/M +Thorpe/M +Thorstein/M +Thorsten/M +Thorvald/M +Thoth/M +Thrace/M +Thracian/M +Throneberry/M +Thruway/MS +Thu +Thucydides/M +Thule/M +Thunderbird/M +Thur/MS +Thurber/M +Thurman/M +Thursday/SM +Thurstan/M +Thurston/M +Ti/M +Tia/M +Tianjin +Tiber/M +Tiberius/M +Tibet/M +Tibetan/S +Tibold/M +Tiburon/M +Ticonderoga/M +Tiebold/M +Tiebout/M +Tieck/M +Tiena/M +Tienanmen/M +Tientsin's +Tierney/M +Tiertza/M +Tiff/M +Tiffani/M +Tiffanie/M +Tiffany/M +Tiffi/M +Tiffie/M +Tiffy/M +Tigris/M +Tijuana/M +Tilda/M +Tildi/M +Tildie/M +Tildy/M +Tiler/M +Tillich/M +Tillie/M +Tillman/M +Tilly/M +Tim/MS +Timbuktu/M +Timex/M +Timi/M +Timmi/M +Timmie/M +Timmy/M +Timofei/M +Timon/M +Timoteo/M +Timothea/M +Timothee/M +Timotheus/M +Timothy/M +Timur/M +Tina/M +Tine/M +Ting/M +Tinkertoy +Tinseltown/M +Tintoretto/M +Tioga/M +Tiphani/M +Tiphanie/M +Tiphany/M +Tippecanoe/M +Tipperary/M +Tirana's +Tirane +Tiresias/M +Tirol/M +Tirolean/S +Tirrell/M +Tish/M +Tisha/M +Titan/SM +Titania/M +Titanic/M +Titian/M +Titicaca/M +Tito/SM +Titus/M +Tl/M +Tlaloc/M +Tlingit/M +Tm/M +Tobago/M +Tobe/M +Tobey/M +Tobi/M +Tobiah/M +Tobias/M +Tobie/M +Tobin/M +Tobit/M +Toby/M +Tobye/M +Tocantins/M +Tocqueville +Tod/M +Todd/M +Toddie/M +Toddy/M +Togo/M +Togolese/M +Toiboid/M +Toinette/M +Tojo/M +Tokay/M +Tokugawa/M +Tokyo/M +Tokyoite/MS +Toland/M +Toledo/SM +Tolkien +Tolley/M +Tolstoy/M +Tolyatti/M +Tom/M +Toma/SM +Tomasina/M +Tomasine/M +Tomaso/M +Tombaugh/M +Tombigbee/M +Tome/M +Tomi/M +Tomkin/M +Tomlin/M +Tommi/M +Tommie/M +Tommy/M +Tompkins/M +Tomsk/M +Tonga/M +Tongan/SM +Toni/M +Tonia/M +Tonie/M +Tonio/M +Tonnie/M +Tonto/M +Tony/M +Tonya/M +Tonye/M +Toomey/M +Tootsie/M +Topeka/M +Topsy/M +Torah/M +Torahs +Tore/M +Torey/M +Tori/M +Torie/M +Torin/M +Toronto/M +Torquemada/M +Torr/XM +Torrance/M +Torre/MS +Torrence/M +Torrens/M +Torrey/M +Torricelli/M +Torrie/M +Torrin/M +Torry/M +Tortola/M +Tortuga/M +Tory/SM +Tosca/M +Toscanini/M +Toshiba/M +Toto/M +Toulouse/M +Tova/M +Tove/M +Town/M +Townes +Towney/M +Townie/M +Townley/M +Townsend/M +Towny/M +Towsley/M +Toynbee/M +Toyoda/M +Toyota/M +Trace/M +Tracee/M +Tracey/M +Traci/M +Tracie/M +Tractarians +Tracy/M +Trafalgar/M +Trajan/M +Tran/M +Transcaucasia/M +Transite/M +Transputer/M +Transvaal/M +Transylvania/M +Trappist/MS +Trastevere/M +Traver/MS +Travis/M +Travus/M +Treadwell/M +Treasury/SM +Treblinka/M +Trefor/M +Trekkie/M +Tremain/M +Tremaine/M +Tremayne/M +Trenna/M +Trent/M +Trenton/M +Tresa/M +Trescha/M +Tressa/M +Trev/RM +Trevar/M +Trevelyan/M +Trever/M +Trevino/M +Trevor/M +Trey/M +Triangulum/M +Trianon/M +Triassic +Tricia/M +Trieste/M +Trimble/M +Trimurti/M +Trina/M +Trinidad/M +Trinity/MS +Trip/M +Tripoli/M +Tripp/M +Trippe/M +Tris +Trish/M +Trisha/M +Trista/M +Tristam/M +Tristan/M +Triton/M +Trix/M +Trixi/M +Trixie/M +Trixy/M +Trobriand/M +Trojan/MS +Trollope/M +Trondheim/M +Tropez/M +Trotsky/M +Troutman/M +Troy/M +Troyes +Trstram/M +Truckee/M +Truda/M +Trude/M +Trudeau/M +Trudey/M +Trudi/M +Trudie/M +Trudy/M +Trueman/M +Trujillo/M +Trula/M +Trumaine/M +Truman/M +Trumann/M +Trumbull/M +Trump/M +Truth +Tsimshian/M +Tsiolkovsky/M +Tsitsihar/M +Tsunematsu/M +Tswana/M +Tuamotu/M +Tuareg/M +Tubman/M +Tuck/RM +Tucker/M +Tuckie/M +Tucky/M +Tucson/M +Tucuman/M +Tudor/MS +Tue/S +Tuesday/SM +Tulane/M +Tull/M +Tulley/M +Tully/M +Tulsa/M +Tums/M +Tungus/M +Tunguska/M +Tunis/M +Tunisia/M +Tunisian/S +Tupi/M +Tupperware +Tupungato/M +Turgenev/M +Turin/M +Turing/M +Turk/SM +Turkestan/M +Turkey/M +Turkic/SM +Turkish +Turkmenistan/M +Turner/M +Turpin/M +Tuscaloosa/M +Tuscan +Tuscany/M +Tuscarora/M +Tuscon/M +Tuskegee/M +Tussuad/M +Tut/M +Tutankhamen/M +Tutsi +Tuttle/M +Tuvalu +Twain/M +Tweed/M +Tweedledee/M +Tweedledum/M +Twila/M +Twinkie +Twp +Twyla/M +Ty/M +Tybalt/M +Tybi/M +Tybie/M +Tye/M +Tylenol/M +Tyler/M +Tymon/M +Tymothy/M +Tynan/M +Tyndale/M +Tyndall/M +Tyne/M +Typhon/M +Tyree/M +Tyrol's +Tyrolean/S +Tyrone/M +Tyrus/M +Tyson/M +Tzeltal/M +U +UAR +UART +UAW +UCLA/M +UFO/S +UHF +UK +UL +ULTRIX/M +UN +UNESCO +UNICEF +UNIX/M +UPC +UPI +UPS +URL +US +USA +USAF +USART +USC/M +USCG +USDA +USG/M +USIA +USMC +USN +USO +USP +USPS +USS +USSR +UT +UV +Ubangi/M +Ubuntu +Ubuntu's +UbuntuOne +UbuntuOne's +Ucayali/M +Uccello/M +Udale/M +Udall/M +Udell/M +Ufa/M +Uganda/M +Ugandan/S +Ugo/M +Uighur +Ujungpandang/M +Ukraine/M +Ukrainian/S +Ula/M +Ulberto/M +Ulick/M +Ulises/M +Ulla/M +Ullman/M +Ulric/M +Ulrica/M +Ulrich/M +Ulrick/M +Ulrika/M +Ulrikaumeko/M +Ulrike/M +Ulster/M +Ultrasuede +Ultrix/M +Ulyanovsk/M +Ulysses/M +Umberto/M +Umbriel/M +Umeko/M +Una/M +Underwood/M +Ungava/M +UniPlus/M +UniSoft/M +Unibus/M +Unicode/M +Union/MS +Unionist/SM +Uniroyal/M +Unisys/M +Unitarian/MS +Unitarianism/SM +Univac/M +Unix/M +Unukalhai/M +Upanishads +Updike/M +Upton/M +Ur/M +Ural/MS +Urania/M +Uranus/M +Urbain/M +Urban/M +Urbana/M +Urbano/M +Urbanus/M +Urdu/M +Urey/M +Uri/SM +Uriah/M +Uriel/M +Urquhart/M +Ursa/M +Ursala/M +Ursola/M +Urson/M +Ursula/M +Ursulina/M +Ursuline/M +Uruguay/M +Uruguayan/S +Urumqi +Usenet/M +Usenix/M +Ustinov/M +Uta/M +Utah/M +Utahan/SM +Ute/M +Utica/M +Utopia/MS +Utopian/S +Utrecht/M +Utrillo/M +Uzbek/M +Uzbekistan +Uzi/M +V +V's +VA +VAR +VAT +VAX/M +VAXes +VCR +VD +VDT +VDU +VF +VFW +VG +VGA +VHF +VHS +VI +VIP/S +VISTA +VJ +VLF +VLSI +VMS/M +VOA +VP +VT +VTOL +Va/M +Vachel/M +Vaclav/M +Vader/M +Vaduz/M +Vail/M +Val/MY +Valaree/M +Valaria/M +Valarie/M +Valdemar/M +Valdez/M +Vale/M +Valeda/M +Valencia/MS +Valene/M +Valenka/M +Valentia/M +Valentijn/M +Valentin/M +Valentina/M +Valentine/M +Valentino/M +Valenzuela/M +Valera/M +Valeria/M +Valerian/M +Valerie/M +Valerye/M +Valhalla/M +Valida/M +Valina/M +Valium/S +Valkyrie/SM +Valle/M +Vallejo +Valletta/M +Valli/M +Vallie/M +Vally/M +Valma/M +Valois/M +Valparaiso/M +Valry/M +Valéry/M +Van/M +Vance/M +Vancouver/M +Vanda/M +Vandal/MS +Vandenberg/M +Vanderbilt/M +Vanderburgh/M +Vanderpoel/M +Vandyke/SM +Vanessa/M +Vang/M +Vania/M +Vanna/M +Vanni/M +Vannie/M +Vanny/M +Vanuatu +Vanya/M +Vanzetti/M +Varanasi/M +Varese/M +Vargas/M +Varian/M +Varityping/M +Vaseline/DSMG +Vasili/MS +Vasily/M +Vasquez/M +Vassar/M +Vassili/M +Vassily/M +Vatican/M +Vaudois +Vaughan/M +Vaughn/M +Vax/M +Vazquez/M +Veblen/M +Veda/MS +Vedanta/M +Vega/SM +Vegemite/M +Vela/M +Velcro/SM +Velez/M +Vella/M +Velma/M +Velveeta/M +Velvet/M +Velásquez/M +Velázquez +Venetian/SM +Venezuela/M +Venezuelan/S +Venice/M +Venita/M +Venn/M +Ventura/M +Venus/S +Venusian/S +Vera/M +Veracruz/M +Veradis +Verde/M +Verderer/M +Verdi/M +Vere/M +Verena/M +Verene/M +Verge/M +Vergil's +Veriee/M +Verile/M +Verina/M +Verine/M +Verla/M +Verlag/M +Verlaine/M +Vermeer/M +Vermont/ZRM +Vermonter/M +Vern/NM +Verna/M +Verne/M +Vernen/M +Verney/M +Vernice/M +Vernon/M +Vernor/M +Verona/M +Veronese/M +Veronica/M +Veronika/M +Veronike/M +Veronique/M +Versailles/M +Versatec/M +Vesalius/M +Vespasian/M +Vespucci/M +Vesta/M +Vesuvius/M +Vevay/M +Vi/M +Viagra/M +Vic/M +Vicente/M +Vichy/M +Vick/ZM +Vickers/M +Vicki/M +Vickie/M +Vicksburg/M +Vicky/M +Victoir/M +Victor/M +Victoria/M +Victorian/S +Victorianism/S +Victrola/SM +Vida/M +Vidal/M +Vidovic/M +Vidovik/M +Vienna/M +Viennese/M +Vientiane/M +Viet/M +Vietcong/M +Vietminh/M +Vietnam/M +Vietnamese/M +Vijayawada/M +Viki/M +Viking/MS +Vikki/M +Vikky/M +Vikram/M +Vila +Vilhelmina/M +Villa/M +Villarreal/M +Villon/M +Vilma/M +Vilnius/M +Vilyui/M +Vin/M +Vina/M +Vince/M +Vincent/MS +Vincenty/M +Vincenz/M +Vinci/M +Vindemiatrix/M +Vinita/M +Vinni/M +Vinnie/M +Vinny/M +Vinson/M +Viola/M +Violante/M +Viole/M +Violet/M +Violetta/M +Violette/M +Virge/M +Virgie/M +Virgil/M +Virgilio/M +Virgina/M +Virginia/M +Virginian/S +Virginie/M +Virgo/MS +Visa/M +Visakhapatnam's +Visayans +Vishnu/M +Visigoth/M +Visigoths +Vistula/M +Vita/M +Vite/M +Vitia/M +Vitim/M +Vito/M +Vitoria/M +Vittoria/M +Vittorio/M +Vitus/M +Viv/M +Viva/M +Vivaldi +Vivekananda/M +Vivi/MN +Vivia/M +Vivian/M +Viviana/M +Vivianna/M +Vivianne/M +Vivie/M +Vivien/M +Viviene/M +Vivienne/M +Viviyan/M +Vivyan/M +Vivyanne/M +Vlad/M +Vladamir/M +Vladimir/M +Vladivostok/M +Vogel/M +Vol/M +Volga/M +Volgograd/M +Volkswagen/SM +Volstead/M +Volta/M +Voltaire/M +Volterra/M +Volvo/M +Von/M +Vonda/M +Vonnegut/M +Vonni/M +Vonnie/M +Vonny/M +Voronezh/M +Vorster/M +Vt/M +Vulcan/M +Vulg/M +Vulgate/SM +Vyky/M +W's +W/T +WA +WAC +WASP +WATS +WC +WFF +WHO +WI +WNW +WP +WSW +WV +WW +WWI +WWII +WWW +WY +WYSIWYG +Waals +Wabash/M +Wac/S +Wacke/M +Waco/M +Wade/M +Wadsworth/M +Wafs +Wagner/M +Wagnerian +Wahl/M +Waikiki/M +Wain/M +Wainwright/M +Wait/MR +Waite/M +Waiter/M +Wake/M +Wakefield/M +Waksman/M +Walbridge/M +Walcott/M +Wald/MN +Waldemar/M +Walden/M +Waldensian +Waldheim/M +Waldo/M +Waldon/M +Waldorf/M +Wales +Walesa/M +Walford/M +Walgreen/M +Walker/M +Walkman/S +Wall/SMR +Wallace/M +Wallache/M +Wallas/M +Wallenstein/M +Waller/M +Wallie/M +Wallis +Walliw/M +Walloon/SM +Wally/M +Walpole/M +Walpurgisnacht +Walsh/M +Walt/ZMR +Walter/M +Walther/M +Walton/M +Walworth/M +Waly/M +Wanamaker/M +Wanda/M +Wandie/M +Wandis/M +Waneta/M +Wang/M +Wanids/M +Wankel/M +Wansee/M +Wansley/M +Ward/MN +Warde/M +Warden/M +Ware/MG +Warfield/M +Warhol/M +Waring/M +Warner/M +Warnock/M +Warren/M +Warsaw/M +Warwick/M +Wasatch/M +Wash/M +Washburn/M +Washington/M +Washingtonian/S +Washoe/M +Wasp's +Wasserman/M +Wassermann/M +Wat/ZM +Watanabe/M +Waterbury/M +Watergate/M +Waterhouse/M +Waterloo/SM +Waters/M +Watertown/M +Watkins +Watson/M +Watt/MS +Watteau/M +Wattenberg/M +Watterson/M +Watusi/M +Waugh/M +Waukesha/M +Waunona/M +Waupaca/M +Waupun/M +Wausau/M +Wauwatosa/M +Wave/S +Waveland/M +Waverley/M +Waverly/M +Way/M +Waylan/M +Wayland/M +Waylen/M +Waylin/M +Waylon/M +Wayne/M +Waynesboro/M +Weatherford/M +Weaver/M +Web/MR +Webb/RM +Webber/M +Weber/M +Webern/M +Webster/MS +Websterville/M +Wed/M +Weddell/M +Wedgwood/M +Wednesday/SM +Weeks/M +Wehr/M +Wei/M +Weibull/M +Weidar/M +Weider/M +Weidman/M +Weierstrass/M +Weill/M +Weinberg/M +Weiner/M +Weinstein/M +Weisenheimer/M +Weiss/M +Weissman/M +Weissmuller/M +Weizmann/M +Welbie/M +Welby/M +Welcher/M +Welches +Weldon/M +Weldwood/M +Welland/M +Weller/M +Welles/M +Wellesley/M +Wellington/MS +Wellman/M +Wells/M +Wellsville/M +Welmers/M +Welsh +Welshman/M +Welshmen +Welshwoman/M +Welshwomen +Wenda/M +Wendall/M +Wendel/M +Wendeline/M +Wendell/M +Wendi/M +Wendie/M +Wendy/M +Wendye/M +Wenona/M +Wenonah/M +Wentworth/M +Werner/M +Wernher/M +Werther/M +Wes +Wesley/M +Wesleyan +Wessex/M +Wesson/M +West/MS +Westbrook/M +Westbrooke/M +Westchester/M +Western/ZRS +Westfield/M +Westhampton/M +Westinghouse/M +Westleigh/M +Westley/M +Westminster/M +Westmore/M +Weston/M +Westphalia/M +Westport/M +Westwood/M +Weyden/M +Weyerhauser/M +Weylin/M +Wezen/M +Whalen/M +Wharton/M +Wheaties/M +Wheatland/M +Wheaton/M +Wheatstone/M +Wheeler/M +Wheeling/M +Wheelock/M +Whelan/M +Wheller/M +Whig/SM +Whippany/M +Whipple/M +Whistler/M +Whit/M +Whitaker/M +Whitby/M +Whitcomb/M +White/MS +Whitefield/M +Whitehall/M +Whitehead/M +Whitehorse/M +Whiteleaf/M +Whiteley/M +Whitewater/M +Whitfield/M +Whitley/M +Whitlock/M +Whitman/M +Whitney/M +Whitsunday/MS +Whittaker/M +Whittier +Wiatt/M +Wichita/M +Wieland/M +Wiemar/M +Wier/M +Wiesel/M +Wiggins +Wigner/M +Wilberforce/M +Wilbert/M +Wilbur/M +Wilburn/M +Wilburt/M +Wilcox/M +Wilda/M +Wilde/MR +Wilden/M +Wilder/M +Wildon/M +Wileen/M +Wilek/M +Wiley/M +Wilford/M +Wilfred/M +Wilfredo/M +Wilfrid/M +Wilhelm/M +Wilhelmina/M +Wilhelmine/M +Wilie/M +Wilkerson/M +Wilkes/M +Wilkins/M +Wilkinson/M +Will/M +Willa/M +Willabella/M +Willamette/M +Willamina/M +Willard/M +Willcox/M +Willdon/M +Willem/M +Willemstad/M +Willetta/M +Willette/M +Willey/M +Willi/MS +William/SM +Williamsburg/M +Williamson/M +Willie/M +Willied/M +Willisson/M +Willoughby/M +Willow/M +Willy/SDM +Willyt/M +Wilma/M +Wilmar/M +Wilmer/M +Wilmette/M +Wilmington/M +Wilona/M +Wilone/M +Wilow/M +Wilshire/M +Wilson/M +Wilsonian +Wilt/M +Wilton/M +Wimbledon/M +Win/M +Winchell/M +Winchester/MS +Windham/M +Windhoek/M +Windows +Windsor/MS +Windward/M +Windy/M +Winehead/M +Winesap/M +Winfield/M +Winfred/M +Winfrey/M +Wini/M +Winifield/M +Winifred/M +Winkle/M +Winn/M +Winna/M +Winnah/M +Winne/M +Winnebago/M +Winnetka/M +Winni/M +Winnie/M +Winnifred/M +Winnipeg/M +Winny/M +Winograd/M +Winona/M +Winonah/M +Winooski/M +Winsborough/M +Winsett/M +Winslow/M +Winston/M +Winters +Winthrop/M +Wis/M +Wisc +Wisconsin/M +Wisconsinite/SM +Wise/M +Wisenheimer/M +Wit/M +Witherspoon/M +Witt/M +Wittgenstein/M +Wittie/M +Witty/M +Witwatersrand/M +Wm/M +Wodehouse/M +Wolcott/M +Wolf/M +Wolfe/M +Wolff/M +Wolfgang/M +Wolfie/M +Wolfy/M +Wollongong/M +Wollstonecraft/M +Wolsey/M +Wolverhampton/M +Wolverton/M +Wong/M +Wood/SM +Woodard/M +Woodberry/M +Woodbury/M +Woodhull/M +Woodie/M +Woodlawn/M +Woodman/M +Woodrow/M +Woodstock/M +Woodward/MS +Woody/M +Woolf/M +Woolongong/M +Woolworth/M +Woonsocket/M +Wooster/M +Wooten/M +Worcester/SM +Worcestershire/M +Worden/M +Wordsworth/M +Workman/M +Worms/M +Worth/M +Worthington/M +Worthy/M +Wotan/M +Wozniak/M +Wrangell/M +Wren/MS +Wrennie/M +Wright/M +Wrigley/M +Wroclaw +Wronskian/M +Wu/M +Wuhan/M +Wurlitzer/M +Wyatan/M +Wyatt/M +Wycherley/M +Wycliffe/M +Wye/MH +Wyeth/M +Wylie/M +Wylma/M +Wyman/M +Wyn/M +Wyndham/M +Wynn/M +Wynne/M +Wynnie/M +Wynny/M +Wyo/M +Wyoming/M +Wyomingite/SM +X +X's +XEmacs/M +XL +XML +XOR +XS +XXL +Xanadu +Xanthippe/M +Xanthus/M +Xavier/M +Xaviera/M +Xe/M +Xebec/M +Xena/M +Xenakis/M +Xenia/M +Xenix/M +Xenophon/M +Xenos +Xerox/MGSD +Xerxes/M +Xever/M +Xhosa/M +Xi'an +Xian/S +Xiaoping/M +Ximenes/M +Ximenez/M +Ximian/SM +Xingu/M +Xmas/SM +Xochipilli/M +Xuzhou/M +Xylia/M +Xylina/M +Xymenes/M +Y +Y's +YMCA +YMHA +YMMV +YT +YWCA +YWHA +Yacc/M +Yagi/M +Yahweh/M +Yakima/M +Yakut/M +Yakutsk/M +Yale/M +Yalies/M +Yalonda/M +Yalow/M +Yalta/M +Yalu/M +Yamaha/M +Yamoussoukro +Yanaton/M +Yance/M +Yancey/M +Yancy/M +Yang/M +Yangon +Yangtze/M +Yank/MS +Yankee/SM +Yaounde/M +Yaqui/M +Yard/M +Yardley/M +Yaroslavl/M +Yasmeen/M +Yasmin/M +Yates +Yb/M +Yeager/M +Yeats/M +Yehudi/M +Yehudit/M +Yekaterinburg/M +Yelena/M +Yellowknife/M +Yellowstone/M +Yeltsin +Yemen/M +Yemeni/S +Yemenite/SM +Yenisei/M +Yentl/M +Yerevan/M +Yerkes/M +Yesenia/M +Yetta/M +Yettie/M +Yetty/M +Yevette/M +Yevtushenko/M +Yggdrasil/M +Yiddish/M +Ymir/M +Ynes/M +Ynez/M +Yoda/M +Yoder/M +Yoknapatawpha/M +Yoko/M +Yokohama/M +Yolanda/M +Yolande/M +Yolane/M +Yolanthe/M +Yong/M +Yonkers/M +Yorgo/MS +Yorick/M +York/ZRMS +Yorke/M +Yorker/M +Yorkshire/MS +Yorktown/M +Yoruba/M +Yosemite/M +Yoshi/M +Yoshiko/M +Yost/M +Young/M +Youngstown/M +Yovonnda/M +Ypres/M +Ypsilanti/M +Ysabel/M +Yuba/M +Yucatan +Yugo/M +Yugoslav/M +Yugoslavia/M +Yugoslavian/S +Yuh/M +Yuki/M +Yukon/M +Yul/M +Yule/MS +Yuletide/S +Yulma/M +Yuma/M +Yunnan/M +Yuri/M +Yurik/M +Yves/M +Yvette/M +Yvon/M +Yvonne/M +Yvor/M +Z/X +Zabrina/M +Zaccaria/M +Zach/M +Zacharia/SM +Zachariah/M +Zacharie/M +Zachary/M +Zacherie/M +Zachery/M +Zack/M +Zackariah/M +Zagreb/M +Zahara/M +Zaire/M +Zairian/S +Zak/M +Zambezi/M +Zambia/M +Zambian/S +Zamboni +Zamenhof/M +Zamora/M +Zan/M +Zandra/M +Zane/M +Zaneta/M +Zanuck/M +Zanzibar/M +Zapata/M +Zaporozhye/M +Zappa/M +Zara/M +Zarah/M +Zared/M +Zaria/M +Zarla/M +Zea/M +Zealand/M +Zeb/M +Zebadiah/M +Zebedee/M +Zebulen/M +Zebulon/M +Zechariah/M +Zed/M +Zedekiah/M +Zedong/M +Zeffirelli/M +Zeiss/M +Zeke/M +Zelda/M +Zelig/M +Zellerbach/M +Zelma/M +Zen/M +Zena/M +Zenger/M +Zenia/M +Zennist/M +Zeno/M +Zephaniah/M +Zephyrus/M +Zeppelin's +Zerk/M +Zeus/M +Zhdanov/M +Zhengzhou +Zhivago/M +Zhukov/M +Zia/M +Zibo/M +Ziegfeld/MS +Ziegler/M +Ziggy/M +Zilvia/M +Zimbabwe/M +Zimbabwean/S +Zimmerman/M +Zion/SM +Zionism/MS +Zionist/MS +Zita/M +Zitella/M +Zn/M +Zoe/M +Zola/M +Zollie/M +Zolly/M +Zomba/M +Zonda/M +Zondra/M +Zonnya/M +Zora/M +Zorah/M +Zorana/M +Zorina/M +Zorine/M +Zorn/M +Zoroaster/M +Zoroastrian/S +Zoroastrianism/MS +Zorro/M +Zosma/M +Zr/M +Zs +Zsazsa/M +Zsigmondy/M +Zubenelgenubi/M +Zubeneschamali/M +Zukor/M +Zulema/M +Zulu/MS +Zululand/M +Zuni/S +Zuzana/M +Zwingli/M +Zworykin/M +Zürich/M +a +aardvark/SM +ab/DY +aback +abacus/SM +abaft +abalone/SM +abandon/LGDRS +abandoner/M +abandonment/SM +abase/LGDSR +abasement/S +abaser/M +abash/SDLG +abashed/UY +abashment/MS +abate/DSRLG +abated/U +abatement/MS +abater/M +abattoir/SM +abbess/SM +abbey/MS +abbot/MS +abbr +abbrev +abbreviate/XDSNG +abbreviated/UA +abbreviates/A +abbreviating/A +abbreviation/M +abbé/S +abdicate/NGDSX +abdication/M +abdomen/SM +abdominal/YS +abduct/DGS +abduction/SM +abductor/SM +abeam +aberrant/YS +aberration/SM +aberrational +abet/S +abetted +abetting +abettor/SM +abeyance/MS +abeyant +abhor/S +abhorred +abhorrence/MS +abhorrent/Y +abhorrer/M +abhorring +abidance/MS +abide/JGSR +abider/M +abiding/Y +ability/IMES +abject/SGPDY +abjection/MS +abjectness/SM +abjuration/SM +abjuratory +abjure/ZGSRD +abjurer/M +ablate/VGNSDX +ablation/M +ablative/SY +ablaze +able/U +abler/E +ables/E +ablest +abloom +ablution/MS +abnegate/NGSDX +abnegation/M +abnormal/SY +abnormality/SM +aboard +abode/GMDS +abolish/LZRSDG +abolisher/M +abolishment/MS +abolition/SM +abolitionism/SM +abolitionist/SM +abominable +abominably +abominate/XSDGN +abomination/M +aboriginal/YS +aborigine/SM +aborning +abort/SRDVG +abortion/MS +abortionist/MS +abortive/PY +abortiveness/M +abound/GDS +about/S +above/S +aboveboard +aboveground +abracadabra/S +abrade/SRDG +abrader/M +abrasion/MS +abrasive/SYMP +abrasiveness/S +abreaction/MS +abreast +abridge/DSRG +abridged/U +abridger/M +abridgment/SM +abroad +abrogate/XDSNG +abrogation/M +abrogator/SM +abrupt/TRYP +abruptness/SM +abs/M +abscess/GDSM +abscissa/SM +abscission/SM +abscond/SDRZG +absconder/M +abseil/SGDR +absence/SM +absent/SGDRY +absentee/MS +absenteeism/SM +absentia/M +absentminded/PY +absentmindedness/S +absinthe/SM +absolute/NPRSYTX +absoluteness/SM +absolution/M +absolutism/MS +absolutist/SM +absolve/GDSR +absolver/M +absorb/ASGD +absorbed/U +absorbency/MS +absorbent/MS +absorber/SM +absorbing/Y +absorption/MS +absorptive +absorptivity/M +abstain/GSDRZ +abstainer/M +abstemious/YP +abstemiousness/MS +abstention/SM +abstinence/MS +abstinent/Y +abstract/PTVGRDYS +abstracted/YP +abstractedness/SM +abstracter/M +abstraction/SM +abstractionism/M +abstractionist/SM +abstractness/SM +abstractor/MS +abstruse/PRYT +abstruseness/SM +absurd/PRYST +absurdity/SM +absurdness/SM +abundance/SM +abundant/Y +abuse/GVZDSRB +abused/E +abuser/M +abuses/E +abusing/E +abusive/YP +abusiveness/SM +abut/LS +abutment/SM +abutted +abutter/MS +abutting +abuzz +abysmal/Y +abyss/SM +abyssal +ac/DRG +acacia/SM +academe/MS +academia/SM +academic/S +academical/Y +academician/SM +academicianship +academy/SM +acanthus/MS +accede/SDG +accelerate/NGSDXV +accelerated/U +accelerating/Y +acceleration/M +accelerator/SM +accelerometer/SM +accent/SGMD +accented/U +accentual/Y +accentuate/XNGSD +accentuation/M +accept/RDBSZVG +acceptability's/U +acceptability/SM +acceptable/P +acceptableness/SM +acceptably/U +acceptance/SM +acceptant +acceptation/SM +accepted/Y +accepter/M +accepting/PY +acceptor/MS +access/SDMG +accessed/A +accessibility/IMS +accessible/IU +accessibly/I +accession/SMDG +accessors +accessory/SM +accidence/M +accident/MS +accidental/SPY +accidentalness/M +acclaim/SDRG +acclaimer/M +acclamation/MS +acclimate/XSDGN +acclimation/M +acclimatisation +acclimatise/DG +acclimatization/AMS +acclimatize/RSDGZ +acclimatized/U +acclimatizes/A +acclivity/SM +accolade/GDSM +accommodate/XVNGSD +accommodated/U +accommodating/Y +accommodation/M +accommodative/P +accommodativeness/M +accompanied/U +accompanier/M +accompaniment/MS +accompanist/SM +accompany/DRSG +accomplice/MS +accomplish/SRDLZG +accomplished/U +accomplisher/M +accomplishment/SM +accord/SZGMRD +accordance/SM +accordant/Y +accorder/M +according/Y +accordion/MS +accordionist/SM +accost/SGD +account/BMDSGJ +accountability's/U +accountability/MS +accountable/U +accountableness/M +accountably/U +accountancy/SM +accountant/MS +accounted/U +accounting/M +accouter/GSD +accouterment's +accouterments +accoutrement/M +accredit/SGD +accreditation/SM +accredited/U +accretion/SM +accrual/MS +accrue/SDG +acct +acculturate/XSDVNG +acculturation/M +accumulate/VNGSDX +accumulation/M +accumulative/YP +accumulativeness/M +accumulator/MS +accuracy/IMS +accurate/IY +accurateness/SM +accursed/YP +accursedness/SM +accusal/M +accusation/SM +accusative/S +accusatory +accuse/SRDZG +accused/M +accuser/M +accusing/Y +accustom/SGD +accustomed/P +accustomedness/M +ace/SM +aced/M +acerbate/DSG +acerbic +acerbically +acerbity/MS +acetaminophen/S +acetate/MS +acetic +acetone/SM +acetonic +acetylene/MS +ache/DSG +ached/A +achene/SM +aches/A +achievable/U +achieve/LZGRSDB +achieved/UA +achievement/SM +achiever/M +aching/Y +achoo +achromatic +achy/TR +acid/SMYP +acidic +acidification/M +acidify/NSDG +acidity/SM +acidness/M +acidoses +acidosis/M +acidulous +acing/M +acknowledge/GZDRS +acknowledgeable +acknowledged/U +acknowledgedly +acknowledgement/SAM +acknowledger/M +acknowledgment/SAM +acme/SM +acne/MDS +acolyte/MS +aconite/MS +acorn/SM +acoustic/S +acoustical/Y +acoustician/M +acoustics/M +acquaint/GASD +acquaintance/MS +acquaintanceship/S +acquainted/U +acquiesce/GSD +acquiescence/SM +acquiescent/Y +acquirable +acquire/ASDG +acquirement/SM +acquisition's/A +acquisition/SM +acquisitive/PY +acquisitiveness/MS +acquit/S +acquittal/MS +acquittance/M +acquitted +acquitter/M +acquitting +acre/MS +acreage/MS +acrid/TPRY +acridity/MS +acridness/SM +acrimonious/YP +acrimoniousness/MS +acrimony/MS +acrobat/SM +acrobatic/S +acrobatically +acrobatics/M +acronym/SM +acrophobia/SM +acropolis/SM +across +acrostic/SM +acrylate/M +acrylic/S +act's +act/SADVG +acting/S +actinic +actinide/SM +actinium/MS +actinometer/MS +action's/IA +action/DMSGB +actions/AI +activate/AXCDSNGI +activated/U +activation/AMCI +activator/SM +active/APY +actively/I +activeness/MS +actives +activism/MS +activist/MS +activities/A +activity/MSI +actor/MAS +actress/SM +actual/SY +actuality/SM +actualization/MAS +actualize/GSD +actualizes/A +actuarial/Y +actuary/MS +actuate/GNXSD +actuation/M +actuator/SM +acuity/MS +acumen/SM +acupressure/S +acupuncture/SM +acupuncturist/S +acute/YTSRP +acuteness/MS +acyclic +acyclically +acyclovir/S +ad's +ad/AS +adage/MS +adagio/S +adamant/SY +adapt/SRDBZVG +adaptability/MS +adaptable/U +adaptation/MS +adapted/P +adaptedness/M +adapter/M +adapting/A +adaption +adaptive/U +adaptively +adaptiveness/M +adaptivity +add/ZGBSDR +addend/SM +addenda +addendum/M +adder/M +addict/SGVD +addiction/MS +addictive/P +addition/MS +additional/Y +additive/YMS +additivity +addle/GDS +address/MDRSZGB +addressability +addressable/U +addressed/A +addressee/SM +addresser/M +addresses/A +adduce/GRSD +adducer/M +adduct/DGVS +adduction/M +adductor/M +adenine/SM +adenoid/S +adenoidal +adept/RYPTS +adeptness/MS +adequacy/IMS +adequate/IPY +adequateness's/I +adequateness/SM +adhere/ZGRSD +adherence/SM +adherent/YMS +adherer/M +adhesion/MS +adhesive/PYMS +adhesiveness/MS +adiabatic +adiabatically +adieu/S +adipose/S +adiós +adj +adjacency/MS +adjacent/Y +adjectival/Y +adjective/MYS +adjoin/SDG +adjoint/M +adjourn/DGLS +adjournment/SM +adjudge/DSG +adjudicate/VNGXSD +adjudication/M +adjudicator/SM +adjudicatory +adjunct/VSYM +adjuration/SM +adjure/GSD +adjust/DRALGSB +adjustable/U +adjustably +adjusted/U +adjuster's/A +adjuster/SM +adjustive +adjustment/MAS +adjustor's +adjutant/SM +adman/M +admen +administer/GDJS +administrable +administrate/XSDVNG +administration/M +administrative/Y +administrator/MS +administratrix/M +admirable/P +admirableness/M +admirably +admiral/SM +admiralty/MS +admiration/MS +admire/RSDZBG +admirer/M +admiring/Y +admissibility/ISM +admissible/I +admissibly +admission/AMS +admit/AS +admittance/MS +admitted/A +admittedly +admitting/A +admix/SDG +admixture/SM +admonish/GLSRD +admonisher/M +admonishing/Y +admonishment/SM +admonition/MS +admonitory +ado/MS +adobe/MS +adolescence/MS +adolescent/SYM +adopt/RDSBZVG +adopted/AU +adopter/M +adoption/MS +adoptive/Y +adopts/A +adorable/P +adorableness/SM +adorably +adoration/SM +adore/DSRGZB +adorer/M +adoring/Y +adorn/SGLD +adorned/U +adornment/SM +adrenal/YS +adrenalin +adrenaline/MS +adrift +adroit/RTYP +adroitness/MS +ads +adsorb/GSD +adsorbate/M +adsorbent/S +adsorption/MS +adsorptive/Y +adulate/GNDSX +adulation/M +adulator/SM +adulatory +adult/MYPS +adulterant/SM +adulterate/NGSDX +adulterated/U +adulteration/M +adulterer/SM +adulteress/MS +adulterous/Y +adultery/SM +adulthood/MS +adultness/M +adumbrate/XSDVGN +adumbration/M +adumbrative/Y +adv +advance/DSRLZG +advancement/MS +advancer/M +advantage/GMEDS +advantageous/EY +advantageousness/M +advent/SVM +adventist/S +adventitious/PY +adventitiousness/M +adventive/Y +adventure/SRDGMZ +adventurer/M +adventuresome +adventuress/SM +adventurous/YP +adventurousness/SM +adverb/SM +adverbial/MYS +adversarial +adversary/SM +adverse/DSRPYTG +adverseness/MS +adversity/SM +advert/GSD +advertise/JGZSRDL +advertised/U +advertisement/SM +advertiser/M +advertising/M +advertorial/S +advice/SM +advisability/SIM +advisable/I +advisableness/M +advisably +advise/ZRSDGLB +advised/YU +advisedly/I +advisee/MS +advisement/MS +adviser/M +advisor's +advisor/S +advisory/S +advocacy/SM +advocate/NGVDS +advocation/M +advt +adz/MDSG +adze's +aegis/SM +aeolian +aeon's +aerate/XNGSD +aeration/M +aerator/MS +aerial/SMY +aerialist/MS +aerie/SRMT +aeroacoustic +aerobatic/S +aerobic/S +aerobically +aerodrome/SM +aerodynamic/S +aerodynamically +aerodynamics/M +aeronautic/S +aeronautical/Y +aeronautics/M +aerosol/MS +aerosolize/D +aerospace/SM +aesthete/S +aesthetic/U +aesthetically +aestheticism/MS +aesthetics/M +aether/M +aetiology/M +afar/S +affability/MS +affable/TR +affably +affair/SM +affect/EGSD +affectation/MS +affected/UEYP +affectedness/EM +affecter/M +affecting/Y +affection/EMS +affectionate/UY +affectioned +affectioning +affective/MY +afferent/YS +affiance/GDS +affidavit/SM +affiliate/EXSDNG +affiliated/U +affiliation/EM +affine +affinity/SM +affirm/ASDG +affirmation/SAM +affirmative/SY +affix/SDG +afflatus/MS +afflict/GVDS +affliction/SM +afflictive/Y +affluence/SM +affluent/YS +afford/DSBG +afforest/A +afforestation/SM +afforested +afforesting +afforests +affray/MDSG +affricate/VNMS +affrication/M +affricative/M +affright +affront/GSDM +afghan/MS +aficionado/MS +afield +afire +aflame +afloat +aflutter +afoot +afore +aforementioned +aforesaid +aforethought/S +afoul +afraid/U +afresh +afro +aft/ZR +afterbirth/M +afterbirths +afterburner/MS +aftercare/SM +aftereffect/MS +afterglow/MS +afterimage/MS +afterlife/M +afterlives +aftermath/M +aftermaths +aftermost +afternoon/SM +afters/M +aftershave/S +aftershock/SM +aftertaste/SM +afterthought/MS +afterward/S +afterworld/MS +again +against +agapae +agape/S +agar/MS +agate/SM +agave/SM +age/GJDRSMZ +aged/PY +agedness/M +ageism/S +ageist/S +ageless/YP +agelessness/MS +agency/SM +agenda/MS +agent/AMS +agented +agenting +agentive +ageratum/M +agglomerate/XNGVDS +agglomeration/M +agglutinate/VNGXSD +agglutination/M +agglutinin/MS +aggrandize/LDSG +aggrandizement/SM +aggravate/SDNGX +aggravating/Y +aggravation/M +aggregate/EGNVD +aggregated/U +aggregately +aggregateness/M +aggregates +aggregation/SM +aggregative/Y +aggression/SM +aggressive/U +aggressively +aggressiveness/S +aggressor/MS +aggrieve/GDS +aggrieved/Y +aghast +agile/YTR +agility/MS +agitate/XVNGSD +agitated/Y +agitation/M +agitator/SM +agitprop/MS +agleam +aglitter +aglow +agnostic/SM +agnosticism/MS +ago +agog +agonize/ZGRSD +agonized/Y +agonizedly/S +agonizing/Y +agony/SM +agoraphobia/MS +agoraphobic/S +agrarian/S +agrarianism/MS +agree/LEBDS +agreeable/EP +agreeableness/SME +agreeably/E +agreeing/E +agreement/ESM +agreer/S +agribusiness/SM +agricultural/Y +agriculturalist/S +agriculture/MS +agriculturist/SM +agrochemicals +agronomic/S +agronomist/SM +agronomy/MS +aground +ague/MS +ah +aha/S +ahead +ahem/S +ahoy/S +aid/ZGDRS +aide/MS +aided/U +aider/M +aigrette/SM +ail/LSDG +aileron/MS +ailment/SM +aim/ZSGDR +aimer/M +aimless/YP +aimlessness/MS +ain't +air/MDRTZGJS +airbag/MS +airbase/S +airborne +airbrush/SDMG +airbus/SM +aircraft/MS +aircrew/M +airdrop/MS +airdropped +airdropping +airfare/S +airfield/MS +airflow/SM +airfoil/MS +airframe/MS +airfreight/SGD +airhead/MS +airily +airiness/MS +airing/M +airless/P +airlessness/S +airlift/MDSG +airline/SRMZ +airliner/M +airlock/MS +airmail/DSG +airman/M +airmass +airmen +airpark +airplane/SM +airplay/S +airport/MS +airship/MS +airsick/P +airsickness/SM +airspace/SM +airspeed/SM +airstrip/MS +airtight/P +airtightness/M +airtime +airwaves +airway/SM +airworthiness/SM +airworthy/PTR +airy/PRT +aisle/DSGM +aitch/MS +ajar +aka +akimbo +akin +ala/MS +alabaster/MS +alack/S +alacrity/SM +alanine/M +alarm/SDG +alarming/Y +alarmist/MS +alas/S +alb/MS +alba/M +albacore/SM +albatross/SM +albedo/M +albeit +albinism/SM +albino/MS +album/MNXS +albumen/M +albumin/MS +albuminous +alchemical +alchemist/SM +alchemy/MS +alcohol/MS +alcoholic/MS +alcoholically +alcoholism/SM +alcove/MSD +aldehyde/M +alder/SM +alderman/M +aldermen +alderwoman +alderwomen +ale/MVS +aleatory +alee +alehouse/MS +alembic/SM +aleph/M +alert/STZGPRDY +alerted/Y +alertness/MS +alewife/M +alewives +alfalfa/MS +alfresco +alga/M +algae +algaecide +algal +algebra/MS +algebraic +algebraical/Y +algebraist/M +alginate/SM +algorithm/MS +algorithmic +algorithmically +alias/GSD +alibi/MDSG +alien/RDGMBS +alienable/IU +alienate/SDNGX +alienation/M +alienist/MS +alight/DSG +align/LASDG +aligned/U +aligner/SM +alignment/SAM +alike/U +alikeness/M +aliment/SDMG +alimentary +alimony/MS +alinement's +aliquot/S +alive/P +aliveness/MS +aliyah/M +aliyahs +alkali/M +alkalies +alkaline +alkalinity/MS +alkalize/SDG +alkaloid/MS +alkyd/S +alkyl/M +all-time +all/S +allay/GDS +allegation/SM +allege/SDG +alleged/Y +allegiance/SM +allegiant +allegoric +allegorical/YP +allegoricalness/M +allegorist/MS +allegory/SM +allegretto/MS +allegri +allegro/MS +allele/SM +alleluia/S +allemande/M +allergen/MS +allergenic +allergic +allergically +allergist/MS +allergy/MS +alleviate/SDVGNX +alleviation/M +alleviator/MS +alley/MS +alleyway/MS +alliance/MS +allier +allies/M +alligator/DMGS +alliterate/XVNGSD +alliteration/M +alliterative/Y +allocable/U +allocatable +allocate/ACSDNGX +allocated/U +allocation/AMC +allocative +allocator/AMS +allophone/MS +allophonic +allot/SDL +allotment/MS +allotments/A +allotrope/M +allotropic +allots/A +allotted/A +allotter/M +allotting/A +allover/S +allow/SBGD +allowable/P +allowableness/M +allowably +allowance/GSDM +allowed/Y +allowing/E +allows/E +alloy/SGMD +alloyed/U +allspice/MS +allude/GSD +allure/GLSD +allurement/SM +alluring/Y +allusion/MS +allusive/PY +allusiveness/MS +alluvial/S +alluvions +alluvium/MS +ally/ASDG +alma +almagest +almanac/MS +almightiness/M +almighty/P +almond/SM +almoner/MS +almost +alms/A +almshouse/SM +almsman/M +alnico +aloe/MS +aloft +aloha/SM +alone/P +aloneness/M +along +alongshore +alongside +aloof/YP +aloofness/MS +aloud +alp/MS +alpaca/SM +alpha/MS +alphabet/SGDM +alphabetic/S +alphabetical/Y +alphabetization/SM +alphabetize/SRDGZ +alphabetizer/M +alphanumeric/S +alphanumerical/Y +alpine/S +already +alright +also +alt/RZS +altar/MS +altarpiece/SM +alter/RDZBG +alterable/UI +alteration/MS +altercate/NX +altercation/M +altered/U +alternate/SDVGNYX +alternation/M +alternative/YMSP +alternativeness/M +alternator/MS +although +altimeter/SM +altitude/SM +alto/SM +altogether/S +altruism/SM +altruist/SM +altruistic +altruistically +alum/SM +alumina/SM +aluminum/MS +alumna/M +alumnae +alumni +alumnus/MS +alundum +alveolar/Y +alveoli +alveolus/M +alway/S +am/AS +amain +amalgam/MS +amalgamate/VNGXSD +amalgamation/M +amanuenses +amanuensis/M +amaranth/M +amaranths +amaretto/S +amaryllis/MS +amass/GRSD +amasser/M +amateur/SM +amateurish/YP +amateurishness/MS +amateurism/MS +amatory +amaze/LDSRGZ +amazed/Y +amazement/MS +amazing/Y +amazon/MS +amazonian +ambassador/MS +ambassadorial +ambassadorship/MS +ambassadress/SM +amber/MS +ambergris/SM +ambiance/MS +ambidexterity/MS +ambidextrous/Y +ambience's +ambient/S +ambiguity/MS +ambiguous/YP +ambiguously/U +ambiguousness/M +ambit/M +ambition/GMDS +ambitious/PY +ambitiousness/MS +ambivalence/SM +ambivalent/Y +amble/GZDSR +ambler/M +ambrose +ambrosia/SM +ambrosial/Y +ambulance/MS +ambulant/S +ambulate/DSNGX +ambulation/M +ambulatory/S +ambuscade/MGSRD +ambuscader/M +ambush/MZRSDG +ambusher/M +ameba's +ameliorate/XVGNSD +amelioration/M +amen/DRGTSB +amenability/SM +amenably +amend/SBRDGL +amended/U +amender/M +amendment/SM +amends/M +amenity/MS +amenorrhea/M +amerce/SDLG +amercement/MS +americanized +americium/MS +amethyst/MS +amethystine +amiability/MS +amiable/RPT +amiableness/M +amiably +amicability/SM +amicable/P +amicableness/M +amicably +amid/S +amide/SM +amidships +amidst +amigo/MS +amines +amino/M +aminobenzoic +amir's +amiss +amity/SM +ammeter/MS +ammo/MS +ammonia/MS +ammoniac +ammonium/M +ammunition/MS +amnesia/SM +amnesiac/MS +amnesic/S +amnesty/GMSD +amniocenteses +amniocentesis/M +amnion/SM +amniotic +amoeba/SM +amoebic +amoeboid +amok/MS +among +amongst +amontillado/MS +amoral/Y +amorality/MS +amorous/PY +amorousness/SM +amorphous/PY +amorphousness/MS +amortization/SUM +amortize/SDG +amortized/U +amount/SMRDZG +amour/MS +amp/SGMDY +amperage/SM +ampere/MS +ampersand/MS +amphetamine/MS +amphibian/SM +amphibious/PY +amphibiousness/M +amphibology/M +amphitheater/SM +amphora/M +amphorae +ample/PTR +ampleness/M +amplification/M +amplifier/M +amplify/DRSXGNZ +amplitude/MS +ampoule's +ampule/SM +amputate/DSNGX +amputation/M +amputee/SM +ams +amt +amuck's +amulet/SM +amuse/LDSRGVZ +amused/Y +amusement/SM +amuser/M +amusing/YP +amusingness/M +amyl/M +amylase/MS +an/CS +anabolic +anabolism/MS +anachronism/SM +anachronistic +anachronistically +anaconda/MS +anaerobe/SM +anaerobic +anaerobically +anaglyph/M +anagram/MS +anagrammatic +anagrammatically +anagrammed +anagramming +anal/Y +analgesia/MS +analgesic/S +analog/SM +analogical/Y +analogize/SDG +analogous/YP +analogousness/MS +analogue/SM +analogy/MS +analysand/MS +analyses +analysis/AM +analyst/SM +analytic/S +analytical/Y +analyticity/S +analytics/M +analyzable/U +analyze/DRSZGA +analyzed/U +analyzer/M +anamorphic +anapaest's +anapest/SM +anapestic/S +anaphora/M +anaphoric +anaphorically +anaplasmosis/M +anarchic +anarchical/Y +anarchism/MS +anarchist/MS +anarchistic +anarchy/MS +anastigmatic +anastomoses +anastomosis/M +anastomotic +anathema/MS +anathematize/GSD +anatomic +anatomical/YS +anatomist/MS +anatomize/GSD +anatomy/MS +ancestor/SMDG +ancestral/Y +ancestress/SM +ancestry/SM +anchor/SGDM +anchorage/SM +anchored/U +anchorite/MS +anchoritism/M +anchorman/M +anchormen +anchorpeople +anchorperson/S +anchorwoman +anchorwomen +anchovy/MS +ancient/SRYTP +ancientness/MS +ancillary/S +and/DZGS +andante/S +andiron/MS +androgen/SM +androgenic +androgynous +androgyny/SM +android/MS +anecdotal/Y +anecdote/SM +anechoic +anemia/SM +anemic/S +anemically +anemometer/MS +anemometry/M +anemone/SM +anent +aneroid +anesthesia/MS +anesthesiologist/MS +anesthesiology/SM +anesthetic/SM +anesthetically +anesthetist/MS +anesthetization/SM +anesthetize/ZSRDG +anesthetizer/M +aneurysm/MS +anew +angel/MDSG +angelfish/SM +angelic +angelica/MS +angelical/Y +anger/GDMS +angina/MS +angiography +angioplasty/S +angiosperm/MS +angle/GMZDSRJ +angler/M +angleworm/MS +anglicize/SDG +angling/M +angora/MS +angrily +angriness/M +angry/RTP +angst/MS +angstrom/MS +anguish/DSMG +angular/Y +angularity/MS +anhydride/M +anhydrite/M +anhydrous/Y +aniline/SM +animadversion/SM +animadvert/DSG +animal/MYPS +animalcule/MS +animate/YNGXDSP +animated/A +animatedly +animately/I +animateness/MI +animates/A +animating/A +animation/AMS +animator/SM +animism/SM +animist/S +animistic +animized +animosity/MS +animus/SM +anion/MS +anionic/S +anise/MS +aniseed/MS +aniseikonic +anisette/SM +anisotropic +anisotropy/MS +ankh/M +ankhs +ankle/GMDS +anklebone/SM +anklet/MS +annal/MNS +annalist/MS +anneal/DRSZG +annealer/M +annelid/MS +annex/GSD +annexation/SM +annexe/M +annihilate/XSDVGN +annihilation/M +annihilator/MS +anniversary/MS +annotate/VNGXSD +annotated/U +annotation/M +annotator/MS +announce/ZGLRSD +announced/U +announcement/SM +announcer/M +annoy/ZGSRD +annoyance/MS +annoyer/M +annoying/Y +annual/YS +annualized +annuitant/MS +annuity/MS +annul/SL +annular/YS +annuli +annulled +annulling +annulment/MS +annulus/M +annum +annunciate/XNGSD +annunciation/M +annunciator/SM +anode/SM +anodic +anodize/GDS +anodyne/SM +anoint/DRLGS +anointer/M +anointment/SM +anomalous/YP +anomalousness/M +anomaly/MS +anomic +anomie/M +anon/S +anonymity/MS +anonymous/YP +anonymousness/M +anopheles/M +anorak/SM +anorectic/S +anorexia/SM +anorexic/S +another/M +ans/M +answer/MZGBSDR +answerable/U +answered/U +answerer/M +ant/GSMD +antacid/MS +antagonism/MS +antagonist/MS +antagonistic +antagonistically +antagonize/GZRSD +antagonized/U +antagonizing/U +antarctic +ante/MS +anteater/MS +antebellum +antecedence/MS +antecedent/SMY +antechamber/SM +antedate/GDS +antediluvian/S +anteing +antelope/MS +antenatal +antenna/MS +antennae +anterior/SY +anteroom/SM +anthem/MGDS +anther/MS +anthill/S +anthologist/MS +anthologize/GDS +anthology/SM +anthraces +anthracite/MS +anthrax/M +anthropic +anthropocentric +anthropogenic +anthropoid/S +anthropological/Y +anthropologist/MS +anthropology/SM +anthropometric/S +anthropometry/M +anthropomorphic +anthropomorphically +anthropomorphism/SM +anthropomorphizing +anthropomorphous +anti/S +antiabortion +antiabortionist/S +antiaircraft +antibacterial/S +antibiotic/SM +antibody/MS +antic/MS +anticancer +anticipate/XVGNSD +anticipated/U +anticipation/M +anticipative/Y +anticipatory +anticked +anticking +anticlerical/S +anticlimactic +anticlimactically +anticlimax/SM +anticline/SM +anticlockwise +anticoagulant/S +anticoagulation/M +anticommunism/SM +anticommunist/SM +anticompetitive +anticyclone/MS +anticyclonic +antidemocratic +antidepressant/SM +antidisestablishmentarianism/M +antidote/DSMG +antifascist/SM +antiformant +antifreeze/SM +antifundamentalist/M +antigen/MS +antigenic +antigenicity/SM +antigone +antihero/M +antiheroes +antihistamine/MS +antihistorical +antiknock/MS +antilabor +antilogarithm/SM +antilogs +antimacassar/SM +antimalarial/S +antimatter/SM +antimicrobial/S +antimissile/S +antimony/SM +anting/M +antinomian +antinomy/M +antinuclear +antioxidant/MS +antiparticle/SM +antipasti +antipasto/MS +antipathetic +antipathy/SM +antipersonnel +antiperspirant/MS +antiphon/SM +antiphonal/SY +antipodal/S +antipode/MS +antipodean/S +antipollution/S +antipoverty +antiquarian/MS +antiquarianism/MS +antiquary/SM +antiquate/NGSD +antiquation/M +antique/MGDS +antiquity/SM +antiredeposition +antiresonance/M +antiresonator +antisemitic +antisemitism/M +antisepses +antisepsis/M +antiseptic/S +antiseptically +antiserum/SM +antislavery/S +antisocial/Y +antispasmodic/S +antisubmarine +antisymmetric +antisymmetry +antitank +antitheses +antithesis/M +antithetic +antithetical/Y +antithyroid +antitoxin/MS +antitrust/MR +antivenin/MS +antiviral/S +antivivisectionist/S +antiwar +antler/SDM +antonym/SM +antonymous +antral +antsy/RT +anus/SM +anvil/MDSG +anxiety/MS +anxious/PY +anxiousness/SM +any +anybody/S +anyhow +anymore +anyone/MS +anyplace +anything/S +anytime +anyway/S +anywhere/S +anywise +aorta/MS +aortic +apace +apache/MS +apart/LP +apartheid/SM +apartment/MS +apartness/M +apathetic +apathetically +apathy/SM +apatite/MS +ape/MDRSG +aped/A +apelike +aper/A +aperiodic +aperiodically +aperiodicity/M +aperitif/S +aperture/MDS +apex/MS +aphasia/SM +aphasic/S +aphelia +aphelion/SM +aphid/MS +aphonic +aphorism/MS +aphoristic +aphoristically +aphrodisiac/SM +apiarist/SM +apiary/SM +apical/YS +apices's +apiece +apish/YP +apishness/M +aplenty +aplomb/SM +apocalypse/MS +apocalyptic +apocrypha/M +apocryphal/YP +apocryphalness/M +apogee/MS +apolar +apolitical/Y +apologetic/S +apologetically/U +apologetics/M +apologia/SM +apologist/MS +apologize/GZSRD +apologizer/M +apologizes/A +apologizing/U +apology/MS +apoplectic +apoplexy/SM +apostasy/SM +apostate/SM +apostatize/DSG +apostle/SM +apostleship/SM +apostolic +apostrophe/SM +apostrophized +apothecary/MS +apothegm/MS +apotheoses +apotheosis/M +apotheosized +apotheosizes +apotheosizing +appall/SDG +appalling/Y +appaloosa/S +appanage/M +apparatus/SM +apparel/SGMD +apparency +apparent/U +apparently/I +apparentness/M +apparition/SM +appeal/SGMDRZ +appealer/M +appealing/UY +appear/AEGDS +appearance/AMES +appearer/S +appease/DSRGZL +appeased/U +appeasement/MS +appeaser/M +appellant/MS +appellate/VNX +appellation/M +appellative/MY +append/SGZDR +appendage/MS +appendectomy/SM +appendices +appendicitis/SM +appendix/SM +appertain/DSG +appetite/MVS +appetizer/SM +appetizing/YU +applaud/ZGSDR +applauder/M +applause/MS +apple/MS +applecart/M +applejack/MS +applesauce/SM +applet/S +appliance/SM +applicabilities +applicability/IM +applicable/I +applicably +applicant/MS +applicate/V +application/MA +applicative/Y +applicator/MS +applier/SM +appliqué/MSG +appliquéd +apply/AGSDXN +appoint/ELSADG +appointee/SM +appointer/MS +appointive +appointment/ASEM +apportion/GADLS +apportionment/SAM +appose/SDG +apposite/XYNVP +appositeness/MS +apposition/M +appositive/SY +appraisal/SAM +appraise/ZGDRS +appraised/A +appraisees +appraiser/M +appraises/A +appraising/Y +appreciable/I +appreciably/I +appreciate/XDSNGV +appreciated/U +appreciation/M +appreciative/PIY +appreciativeness/MI +appreciator/MS +appreciatory +apprehend/DRSG +apprehender/M +apprehensible +apprehension/SM +apprehensive/YP +apprehensiveness/SM +apprentice/DSGM +apprenticeship/SM +apprise/DSG +apprizer/SM +apprizingly +apprizings +approach/BRSDZG +approachability/UM +approachable/UI +approacher/M +approbate/NX +approbation/EMS +appropriable +appropriate/XDSGNVYTP +appropriated/U +appropriately/I +appropriateness/SMI +appropriation/M +appropriator/SM +approval/ESM +approve/DSREG +approved/U +approver's/E +approver/SM +approving/YE +approx +approximate/XGNVYDS +approximation/M +approximative/Y +appurtenance/MS +appurtenant/S +apricot/MS +apron/SDMG +apropos +apse/MS +apsis/M +apt/UPYI +apter +aptest +aptitude/SM +aptness's/U +aptness/SMI +aqua/SM +aquaculture/MS +aqualung/SM +aquamarine/SM +aquanaut/SM +aquaplane/GSDM +aquarium/MS +aquatic/S +aquatically +aquavit/SM +aqueduct/MS +aqueous/Y +aquiculture's +aquifer/SM +aquiline +arabesque/SM +arability/MS +arable/S +arachnid/MS +arachnoid/M +arachnophobia +arbiter/MS +arbitrage/GMZRSD +arbitrager/M +arbitrageur/S +arbitrament/MS +arbitrarily +arbitrariness/MS +arbitrary/P +arbitrate/SDXVNG +arbitration/M +arbitrator/SM +arbor/DMS +arboreal/Y +arbores +arboretum/MS +arborvitae/MS +arbutus/SM +arc/DSGM +arcade/SDMG +arcana/M +arcane/P +arch/PGVZTMYDSR +archaeological/Y +archaeologist/SM +archaic/P +archaically +archaism/SM +archaist/MS +archaize/GDRSZ +archaizer/M +archangel/SM +archbishop/SM +archbishopric/SM +archdeacon/MS +archdiocesan +archdiocese/SM +archduchess/MS +archduke/MS +archenemy/SM +archeologist's +archeology/MS +archer/M +archery/MS +archetypal +archetype/SM +archfiend/SM +archfool +archiepiscopal +arching/M +archipelago/SM +architect/MS +architectonic/S +architectonics/M +architectural/Y +architecture/SM +architrave/MS +archival +archive/DRSGMZ +archived/U +archivist/MS +archness/MS +archway/SM +arclike +arcsine +arctangent +arctic/S +ardency/M +ardent/Y +ardor/SM +arduous/YP +arduousness/SM +are/BS +area/SM +areal +areawide +aren't +arena/SM +arenaceous +argent/MS +arginine/MS +argon/MS +argonaut/S +argosy/SM +argot/SM +arguable/IU +arguably/IU +argue/DSRGZ +arguer/M +argument/SM +argumentation/SM +argumentative/YP +argumentativeness/MS +argyle/S +aria/SM +arid/TYRP +aridity/SM +aridness/M +aright +arise/GJSR +arisen +aristocracy/SM +aristocrat/MS +aristocratic +aristocratically +arithmetic/MS +arithmetical/Y +arithmetician/SM +arithmetize/SD +ark/MS +arm's +arm/ASEDG +armada/SM +armadillo/MS +armament's/E +armament/EAS +armature/MGSD +armband/SM +armchair/MS +armed/U +armer/MES +armful/SM +armhole/MS +arming/M +armistice/MS +armless +armlet/SM +armload/M +armor/ZRDMGS +armored/U +armorer/M +armorial/S +armory/DSM +armpit/MS +armrest/MS +army/SM +aroma/SM +aromatherapist/S +aromatherapy/S +aromatic/SP +aromatically +aromaticity/M +aromaticness/M +arose +around +arousal/MS +arouse/GSD +aroused/U +arpeggio/SM +arr/TV +arrack/M +arraign/SDGL +arraignment/MS +arrange/ZDSRLG +arrangeable/A +arranged/EA +arrangement/AMSE +arranger/M +arranges/EA +arranging/EA +arrant/Y +arras/SM +array/ESGMD +arrayer +arrear/SM +arrest/ADSG +arrestee/MS +arrester/MS +arresting/Y +arrestor/MS +arrhythmia/SM +arrhythmic +arrhythmical +arrival/MS +arrive/SRDG +arriver/M +arrogance/MS +arrogant/Y +arrogate/XNGDS +arrogation/M +arrow/SDMG +arrowhead/SM +arrowroot/MS +arroyo/MS +arsenal/MS +arsenate/M +arsenic/MS +arsenide/M +arsine/MS +arson/SM +arsonist/MS +art/SM +artefact's +arterial/SY +arteriolar +arteriole/SM +arterioscleroses +arteriosclerosis/M +artery/SM +artesian +artful/YP +artfulness/SM +arthritic/S +arthritides +arthritis/M +arthrogram/MS +arthropod/SM +arthroscope/S +arthroscopic +artichoke/SM +article/GMDS +articulable/I +articular +articulate/VGNYXPSD +articulated/EU +articulately/I +articulateness/IMS +articulates/I +articulation/M +articulator/SM +articulatory +artifact/MS +artifice/ZRSM +artificer/M +artificial/PY +artificiality/MS +artificialness/M +artillerist +artillery/SM +artilleryman/M +artillerymen +artiness/MS +artisan/SM +artist/MS +artiste/SM +artistic/I +artistically/I +artistry/SM +artless/YP +artlessness/MS +artsy/RT +artwork/MS +arty/TPR +arum/MS +as +asap +asbestos/MS +ascend/ADGS +ascendancy/MS +ascendant/SY +ascender/SM +ascension/SM +ascent/SM +ascertain/DSBLG +ascertainment/MS +ascetic/SM +ascetically +asceticism/MS +ascot/MS +ascribe/GSDB +ascription/MS +ascriptive +aseptic/S +aseptically +asexual/Y +asexuality/MS +ash/MNDRSG +ashame/D +ashamed/UY +ashcan/SM +ashlar/GSDM +ashman/M +ashore +ashram/SM +ashtray/MS +ashy/RT +aside/S +asinine/Y +asininity/MS +ask/DRZGS +askance +asked/U +asker/M +askew/P +aslant +asleep +asocial/S +asp/MNRXS +asparagus/MS +aspartame/S +aspect/SM +aspen/M +asper/M +asperity/SM +aspersion/SM +asphalt/MDRSG +asphodel/MS +asphyxia/MS +asphyxiate/GNXSD +asphyxiation/M +aspic/MS +aspidistra/MS +aspirant/MS +aspirate/NGDSX +aspiration/M +aspirational +aspirator/SM +aspire/GSRD +aspirer/M +aspirin/SM +asplenium +ass/MNS +assail/BGDS +assailable/U +assailant/SM +assassin/MS +assassinate/DSGNX +assassination/M +assault/SGVMDR +assaulter/M +assaultive/YP +assay/SZGRD +assayer/M +assemblage/MS +assemble/ADSREG +assembled/U +assembler/EMS +assemblies/A +assembly/EAM +assemblyman/M +assemblymen +assemblywoman +assemblywomen +assent/SGMRD +assert/ADGS +asserter/MS +assertion/AMS +assertional +assertive/PY +assertiveness/SM +assess/BLSDG +assessed/A +assesses/A +assessment/SAM +assessor/MS +asset/SM +asseverate/XSDNG +asseveration/M +asshole/MS! +assiduity/SM +assiduous/PY +assiduousness/SM +assign/ALBSGD +assignation/MS +assigned/U +assignee/MS +assigner/MS +assignment/MAS +assignor/MS +assigns/CU +assimilate/VNGXSD +assimilation/M +assimilationist/M +assist/RDGS +assistance/SM +assistant/SM +assistantship/SM +assisted/U +assister/M +assize/MGSD +assn +assoc +associable +associate/SDEXNG +associated/U +associateship +association/ME +associational +associative/Y +associativity/S +associator/MS +assonance/SM +assonant/S +assort/LRDSG +assorter/M +assortment/SM +asst +assuage/SDG +assuaged/U +assumability +assume/SRDBJG +assumer/M +assuming/UA +assumption/SM +assumptive +assurance/AMS +assure/AGSD +assured/PYS +assuredness/M +assurer/SM +assuring/YA +astatine/MS +aster/ESM +asteria +asterisk/SGMD +asterisked/U +astern +asteroid/SM +asteroidal +asthma/MS +asthmatic/S +astigmatic/S +astigmatism/SM +astir +astonish/GSDL +astonishing/Y +astonishment/SM +astound/SDG +astounding/Y +astraddle +astrakhan/SM +astral/SY +astray +astride +astringency/SM +astringent/YS +astrolabe/MS +astrologer/MS +astrological/Y +astrologist/M +astrology/SM +astronaut/SM +astronautic/S +astronautical +astronautics/M +astronomer/MS +astronomic +astronomical/Y +astronomy/SM +astrophysical +astrophysicist/SM +astrophysics/M +astute/RTYP +astuteness/MS +asunder +asylum/MS +asymmetric +asymmetrical/Y +asymmetry/MS +asymptomatic +asymptomatically +asymptote/MS +asymptotic/Y +asymptotically +asynchronism/M +asynchronous/Y +asynchrony +at +atavism/MS +atavist/MS +atavistic +ataxia/MS +ataxic/S +ate/S +atelier/SM +atemporal +atheism/SM +atheist/SM +atheistic +atheroscleroses +atherosclerosis/M +athirst +athlete/MS +athletic/S +athletically +athleticism/M +athletics/M +athwart +atilt +atlantes +atlas/SM +atmosphere/DSM +atmospheric/S +atmospherically +atoll/MS +atom/SM +atomic/S +atomically +atomicity/M +atomics/M +atomistic +atomization/SM +atomize/GZDRS +atomizer/M +atonal/Y +atonality/MS +atone/LDSG +atonement/SM +atop +atria +atrial +atrium/M +atrocious/YP +atrociousness/SM +atrocity/SM +atrophic +atrophy/DSGM +atropine/SM +attach/BLGZMDRS +attached/UA +attacher/M +attachment/ASM +attaché/S +attack/GBZSDR +attacker/M +attain/AGSD +attainabilities +attainability/UM +attainable/U +attainableness/M +attainably/U +attainder/MS +attained/U +attainer/MS +attainment/MS +attar/MS +attempt/ADSG +attempter/MS +attend/SGZDR +attendance/MS +attendant/SM +attended/U +attendee/SM +attender/M +attention/IMS +attentional +attentionality +attentive/YIP +attentiveness/IMS +attenuate/SDXGN +attenuated/U +attenuation/M +attenuator/MS +attest/GSDR +attestation/SM +attested/U +attester/M +attic/MS +attire/SDG +attitude/MS +attitudinal/Y +attitudinize/SDG +attn +attorney/SM +attract/BSDGV +attractant/SM +attraction/MS +attractive/UYP +attractiveness/UM +attractivenesses +attractor/MS +attributable/U +attribute/BVNGRSDX +attributed/U +attributer/M +attribution/M +attributional +attributive/SY +attrition/MS +attune/SDG +atty +atwitter +atypical/Y +aubergine/MS +auburn/SM +auction/MDSG +auctioneer/SDMG +audacious/PY +audaciousness/SM +audacity/MS +audibility/MSI +audible/I +audibles +audibly/I +audience/MS +audio/SM +audiogram/SM +audiological +audiologist/MS +audiology/SM +audiometer/MS +audiometric +audiometry/M +audiophile/SM +audiotape/S +audiovisual/S +audit/SMDVG +audited/U +audition/MDSG +auditor/MS +auditorium/MS +auditory/S +auger/SM +aught/S +augment/DRZGS +augmentation/SM +augmentative/S +augmenter/M +augur/GDMS +augury/SM +august/STPYR +augustness/SM +auk/MS +aunt/MYS +auntie/MS +aunty's +aura/SM +aural/Y +aureole/GMSD +aureomycin +auric +auricle/SM +auricular +aurora/SM +auroral +auscultate/XDSNG +auscultation/M +auspice/SM +auspicious/IPY +auspiciousness/IM +auspiciousnesses +austere/TYRP +austereness/M +austerity/SM +austral +australes +australites +authentic/UI +authentically +authenticate/GNDSX +authenticated/U +authentication/M +authenticator/MS +authenticity/MS +author/DMGS +authoress/S +authorial +authoritarian/S +authoritarianism/MS +authoritative/PY +authoritativeness/SM +authority/SM +authorization/MAS +authorize/AGDS +authorized/U +authorizer/SM +authorizes/U +authorship/MS +autism/MS +autistic/S +auto/SDMG +autobahn/MS +autobiographer/MS +autobiographic +autobiographical/Y +autobiography/MS +autoclave/SDGM +autocollimator/M +autocorrelate/GNSDX +autocorrelation/M +autocracy/SM +autocrat/SM +autocratic +autocratically +autodial/R +autodidact/MS +autofluorescence +autograph/MDG +autographs +autoignition/M +autoimmune +autoimmunity/S +autoloader +automaker/S +automata's +automate/NGDSX +automatic/S +automatically +automation/M +automatism/SM +automatize/DSG +automaton/SM +automobile/GDSM +automorphism/SM +automotive +autonavigator/SM +autonomic/S +autonomous/Y +autonomy/MS +autopilot/SM +autopsy/MDSG +autoregressive +autorepeat/GS +autostart +autosuggestibility/M +autotransformer/M +autoworker/S +autumn/MS +autumnal/Y +aux +auxiliary/S +auxin/MS +av/ZR +avail/BSZGRD +availability/USM +available/U +availableness/M +availably +availing/U +avalanche/MGSD +avant +avarice/SM +avaricious/PY +avariciousness/M +avast/S +avatar/MS +avaunt/S +avdp +ave/S +avenge/ZGSRD +avenged/U +avenger/M +avenue/MS +average/DSPGYM +averred +averrer +averring +avers/V +averse/YNXP +averseness/M +aversion/M +avert/GSD +aves/C +avg +avian/S +aviary/SM +aviate/NX +aviation/M +aviator/SM +aviatrices +aviatrix/SM +avid/TPYR +avidity/MS +avionic/S +avionics/M +avitaminoses +avitaminosis/M +avocado/MS +avocation/SM +avocational +avoid/ZRDBGS +avoidable/U +avoidably/U +avoidance/SM +avoider/M +avoirdupois/MS +avouch/GDS +avow/GEDS +avowal/EMS +avowed/Y +avower/M +avuncular +aw/GD +await/SDG +awake/GS +awaken/SADG +awakened/U +awakener/M +awakening/S +award/RDSZG +awarder/M +aware/TRP +awareness/MSU +awash +away/PS +awe/SM +aweigh +awesome/PY +awesomeness/SM +awestruck +awful/YP +awfuller +awfullest +awfulness/SM +awhile/S +awkward/PRYT +awkwardness/MS +awl/MS +awn/MDJGS +awning/DM +awoke +awoken +awry/RT +ax/DRSZGM +axehead/S +axeman +axial/Y +axillary +axiological/Y +axiology/M +axiom/SM +axiomatic/S +axiomatically +axiomatization/MS +axiomatize/GDS +axion/SM +axis/SM +axle/MS +axletree/MS +axolotl/SM +axon/SM +ayah/M +ayahs +ayatollah +ayatollahs +aye/MZRS +azalea/SM +azimuth/M +azimuthal/Y +azimuths +azure/MS +b/KGD +baa/SDG +babbitt/GDS +babble/RSDGZ +babbler/M +babe/SM +babel/S +baboon/MS +babushka/MS +baby/TDSRMG +babyhood/MS +babyish +babysat +babysit/S +babysitter/S +babysitting +baccalaureate/MS +baccarat/SM +bacchanal/SM +bacchanalia +bacchanalian/S +bachelor/SM +bachelorhood/SM +bacillary +bacilli +bacillus/MS +back/GZDRMSJ +backache/SM +backarrow +backbench/ZR +backbencher/M +backbit/ZGJR +backbite/S +backbiter/M +backbitten +backboard/SM +backbone/SM +backbreaking +backchaining +backcloth/M +backdate/GDS +backdrop/MS +backdropped +backdropping +backed/U +backer/M +backfield/SM +backfill/SDG +backfire/GDS +backgammon/MS +background/SDRMZG +backhand/RDMSZG +backhanded/Y +backhander/M +backhoe/S +backing/M +backlash/GRSDM +backless +backlog/MS +backlogged +backlogging +backorder +backpack/ZGSMRD +backpacker/M +backpedal/DGS +backplane/MS +backplate/SM +backrest/MS +backscatter/SMDG +backseat/S +backside/SM +backslapper/MS +backslapping/M +backslash/DSG +backslid/RZG +backslide/S +backslider/M +backspace/GSD +backspin/SM +backstabber/M +backstabbing +backstage +backstair/S +backstitch/GDSM +backstop/MS +backstopped +backstopping +backstreet/M +backstretch/SM +backstroke/GMDS +backtalk/S +backtrack/SDRGZ +backup/SM +backward/YSP +backwardness/MS +backwash/SDMG +backwater/SM +backwood/S +backwoodsman/M +backwoodsmen +backyard/MS +bacon/SRM +baconer/M +bacteria/MS +bacterial/Y +bactericidal +bactericide/SM +bacteriologic +bacteriological +bacteriologist/MS +bacteriology/SM +bacterium/M +bad/PSNY +badder +baddest +baddie/MS +bade +badge/DSRGMZ +badger/DMG +badinage/DSMG +badland/S +badman/M +badmen +badminton/MS +badmouth/DG +badmouths +badness/SM +baffle/RSDGZL +bafflement/MS +baffler/M +baffling/Y +bag/SM +bagatelle/MS +bagel/SM +bagful/MS +baggage/SM +baggageman +baggagemen +bagged/M +bagger/SM +baggily +bagginess/MS +bagging/M +baggy/PRST +bagpipe/RSMZ +bagpiper/M +baguette/SM +bah +bahs +bail/GSMYDRB +bailiff/SM +bailiwick/MS +bailout/MS +bailsman/M +bailsmen +bairn/SM +bait/GSMDR +baiter/M +baize/GMDS +bake/ZGJDRS +baked/U +bakehouse/M +baker/M +bakery/SM +bakeshop/S +baking/M +baklava/M +baksheesh/SM +balaclava/MS +balalaika/MS +balance's +balance/USDG +balanced/A +balancedness +balancer/MS +balboa/SM +balcony/MSD +bald/PYDRGST +balderdash/MS +baldfaced +baldness/MS +baldric/SM +baldy +bale/MZGDRS +baleen/MS +baleful/YP +balefuller +balefullest +balefulness/MS +baler/M +balk/GDRS +balkanization +balkanize/DG +balker/M +balkiness/M +balky/PRT +ball/GZMSDR +ballad/SM +ballade/MS +balladeer/MS +balladry/MS +ballast/SGMD +ballcock/S +baller/M +ballerina/MS +ballet/MS +balletic +ballfields +ballgame/S +ballistic/S +ballistics/M +balloon/RDMZGS +balloonist/S +ballot/MRDGS +balloter/M +ballpark/SM +ballplayer/SM +ballpoint/SM +ballroom/SM +ballsy/TR +ballyhoo/SGMD +balm/MS +balminess/SM +balmy/PRT +baloney/SM +balsa/MS +balsam/GMDS +balsamic +baluster/MS +balustrade/SM +bamboo/SM +bamboozle/GSD +ban/SGMD +banal/TYR +banality/MS +banana/SM +band's +band/EDGS +bandage/RSDMG +bandager/M +bandanna/SM +bandbox/MS +bandeau/M +bandeaux +bander/M +banding/M +bandit/MS +banditry/MS +bandmaster/MS +bandoleer/SM +bandpass +bandsman/M +bandsmen +bandstand/SM +bandstop +bandwagon/MS +bandwidth/M +bandwidths +bandy/TGRSD +bane/MS +baneful/Y +banefuller +banefullest +bang/GDRZMS +banger/M +bangkok +bangle/MS +bani +banish/RSDGL +banisher/M +banishment/MS +banister/MS +banjo/MS +banjoist/SM +bank/GZJDRMBS +bankbook/SM +bankcard/S +banker/M +banking/M +banknote/S +bankroll/DMSG +bankrupt/DMGS +bankruptcy/MS +banned/U +banner/SDMG +banning/U +bannister's +bannock/SM +banns +banquet/SZGJMRD +banqueter/M +banquette/MS +bans/U +banshee/MS +bantam/MS +bantamweight/MS +banter/RDSG +banterer/M +bantering/Y +banyan/MS +banzai/S +baobab/SM +baptism/SM +baptismal/Y +baptist/MS +baptistery/MS +baptistry's +baptize/SRDZG +baptized/U +baptizer/M +baptizes/U +bar/TGMDRS +barb/DRMSGZ +barbarian/MS +barbarianism/MS +barbaric +barbarically +barbarism/MS +barbarity/SM +barbarize/SDG +barbarous/PY +barbarousness/M +barbecue/DRSMG +barbed/P +barbel/MS +barbell/SM +barbeque's +barber/DMG +barbered/U +barberry/MS +barbershop/MS +barbital/M +barbiturate/MS +barbwire/SM +barcarole/SM +bard/MDSG +bardic +bare/YSP +bareback/D +barefaced/YP +barefacedness/M +barefoot/D +barehanded +bareheaded +barelegged +bareness/MS +barf/YDSG +barfly/SM +bargain/ZGSDRM +bargainer/M +barge/DSGM +bargeman/M +bargemen +bargepole/M +barhop/S +barhopped +barhopping +baritone/MS +barium/MS +bark/GZDRMS +barked/C +barkeep/SRZ +barkeeper/M +barker/M +barks/C +barley/MS +barleycorn/MS +barmaid/SM +barman/M +barmen +barn/GDSM +barnacle/MDS +barnful +barnsful +barnstorm/DRGZS +barnstormer/M +barnyard/MS +barometer/MS +barometric +barometrically +baron/SM +baronage/MS +baroness/MS +baronet/MS +baronetcy/SM +baronial +barony/SM +baroque/SPMY +barque's +barrack/SDRG +barracker/M +barracuda/MS +barrage/MGSD +barre/GMDSJ +barred/ECU +barrel/SGMD +barren/SPRT +barrenness/SM +barrette/SM +barricade/SDMG +barrier/MS +barring/R +barrio/SM +barrister/MS +barroom/SM +barrow/MS +bars/ECU +barstool/SM +bartend/ZR +bartender/M +barter/SRDZG +barterer/M +barycenter +barycentre's +barycentric +baryon/SM +bas/DRSTG +basal/Y +basalt/SM +basaltic +base's +base/CGRSDL +baseball/MS +baseband +baseboard/MS +baseless +baseline/SM +basely +baseman/M +basemen +basement/CSM +baseness/MS +baseplate/M +basetting +bash/JGDSR +bashful/PY +bashfulness/MS +basic/S +basically +basil/MS +basilar +basilica/SM +basilisk/SM +basin/DMS +basinful/S +basis/M +bask/GSD +basket/SM +basketball/MS +basketry/MS +basketwork/SM +basophilic +bass/SM +basset/GMDS +bassinet/SM +bassist/MS +basso/MS +bassoon/MS +bassoonist/MS +basswood/SM +bast/SGZMDR +bastard/MYS +bastardization/MS +bastardize/SDG +bastardized/U +bastardy/MS +baste/NXS +baster/M +basting/M +bastion/DM +bat/SMDRG +batch/MRSDG +bate/KGSADC +bated/U +bater/AC +bath/JMDSRGZ +bathe +bather/M +bathetic +bathhouse/SM +bathmat/S +bathos/SM +bathrobe/MS +bathroom/SDM +baths +bathtub/MS +bathwater +bathyscaphe's +bathysphere/MS +batik/DMSG +batiste/SM +batman/M +batmen +baton/SM +batsman/M +batsmen +battalion/MS +batted +batten/SDMG +batter/SRDZG +battery/MS +batting/MS +battle/GMZRSDL +battledore/MS +battledress +battlefield/SM +battlefront/SM +battleground/SM +battlement/SMD +battler/M +battleship/MS +batty/RT +batwings +bauble/SM +baud/M +baulk/GSDM +bauxite/SM +bawd/SM +bawdily +bawdiness/MS +bawdy/PRST +bawl/SGDR +bawler/M +bay/GSMDY +bayberry/MS +bayonet/SGMD +bayou/MS +bazaar/MS +bazillion/S +bazooka/MS +bbl +bdrm +be/KS +beach/MSDG +beachcomber/SM +beachhead/SM +beachwear/M +beacon/DMSG +bead/SJGMD +beading/M +beadle/SM +beadsman/M +beadworker +beady/TR +beagle/SDGM +beak/ZSDRM +beaker/M +beam/MDRSGZ +bean/DRMGZS +beanbag/SM +beanie/SM +beanpole/MS +beanstalk/SM +bear/ZBRSJG +bearable/U +bearably/U +beard/DSGM +bearded/P +beardless +bearer/M +bearing/M +bearish/PY +bearishness/SM +bearlike +bearskin/MS +beast/SJMY +beasties +beastings/M +beastliness/MS +beastly/PTR +beat/NRGSBZJ +beatable/U +beatably/U +beaten/U +beater/M +beatific +beatifically +beatification/M +beatify/GNXDS +beating/M +beatitude/MS +beatnik/SM +beau/MS +beaut/SM +beauteous/YP +beauteousness/M +beautician/MS +beautification/M +beautifier/M +beautiful/PTYR +beautifully/U +beautifulness/M +beautify/SRDNGXZ +beauty/SM +beaux's +beaver/DMSG +bebop/MS +becalm/GDS +became +because +beck/GSDM +beckon/SDG +becloud/SGD +become/GJS +becoming/UY +bed/MS +bedaub/GDS +bedazzle/GLDS +bedazzlement/SM +bedbug/SM +bedchamber/M +bedclothes +bedded +bedder/MS +bedding/MS +bedeck/DGS +bedevil/DGLS +bedevilment/SM +bedfast +bedfellow/MS +bedim/S +bedimmed +bedimming +bedizen/DGS +bedlam/MS +bedlinen +bedmaker/SM +bedmate/MS +bedpan/SM +bedpost/SM +bedraggle/GSD +bedridden +bedrock/SM +bedroll/SM +bedroom/DMS +bedsheets +bedside/MS +bedsit +bedsitter/M +bedsore/MS +bedspread/SM +bedspring/SM +bedstead/SM +bedstraw/M +bedtime/SM +bee/MZGJRS +beebread/MS +beech/MRSN +beechnut/MS +beechwood +beef/GZSDRM +beefburger/SM +beefcake/MS +beefiness/MS +beefsteak/MS +beefy/TRP +beehive/MS +beekeeper/MS +beekeeping/SM +beeline/MGSD +been/S +beep/GZSMDR +beeper/M +beer/M +beermat/S +beery/TR +beeswax/DSMG +beet/SM +beetle/GMRSD +beetroot/M +beeves/M +befall/SGN +befell +befit/SM +befitted +befitting/Y +befog/S +befogged +befogging +before +beforehand +befoul/GSD +befriend/DGS +befuddle/GLDS +befuddlement/SM +beg/S +began +beget/S +begetting +beggar/DYMSG +beggarliness/M +beggarly/P +beggary/MS +begged +begging +begin/S +beginner/MS +beginning/MS +begone/S +begonia/SM +begot +begotten +begrime/SDG +begrudge/GDRS +begrudging/Y +beguile/RSDLZG +beguilement/SM +beguiler/M +beguiling/Y +beguine/SM +begum/MS +begun +behalf/M +behalves +behave/GRSD +behavior/SMD +behavioral/Y +behaviorism/MS +behaviorist/S +behavioristic/S +behead/GSD +beheld +behemoth/M +behemoths +behest/SM +behind/S +behindhand +behold/ZGRNS +beholder/M +behoofs +behoove/SDJMG +behooving/YM +beige/MS +being/M +bejewel/SDG +belabor/MDSG +belate/D +belated/PY +belatedness/M +belay/GSD +belch/GSD +beleaguer/GDS +belfry/SM +belie +belief/ESUM +belier/M +believability's +believability/U +believable/U +believably/U +believe/EZGDRS +believed/U +believer/MUSE +believing/U +belittle/RSDGL +belittlement/MS +belittler/M +bell/GSMD +belladonna/MS +bellboy/MS +belle/MS +belled/A +belletrist/SM +belletristic +bellflower/M +bellhop/MS +bellicose/YP +bellicoseness/M +bellicosity/MS +belligerence/SM +belligerency/MS +belligerent/SMY +belling/A +bellman/M +bellmen +bellow/DGS +bellows/M +bells/A +bellwether/MS +belly/SDGM +bellyache/SRDGM +bellyacher/M +bellybutton/MS +bellyful/MS +bellyfull +belong/DGJS +belonging/MP +belove/D +beloved/S +below/S +belt/GSMD +belted/U +belting/M +beltway/SM +beluga/SM +belvedere/M +bely/DSRG +beman +bemire/SDG +bemoan/GDS +bemuse/GSDL +bemused/Y +bemusement/SM +bench/MRSDG +bencher/M +benchmark/GDMS +bend/BUSG +bended +bender/MS +beneath +benediction/MS +benedictory +benefaction/MS +benefactor/MS +benefactress/S +benefice/MGSD +beneficence/SM +beneficent/Y +beneficial/PY +beneficialness/M +beneficiary/MS +benefit/SRDMZG +benefiter/M +benevolence/SM +benevolent/YP +benevolentness/M +benighted/YP +benightedness/M +benign/Y +benignant +benignity/MS +bent/U +bents +bentwood/SM +benumb/SGD +benzene/MS +benzine/SM +bequeath/GSD +bequeaths +bequest/MS +berate/GSD +bereave/GLSD +bereavement/MS +bereft +beret/SM +berg/NRSM +beribbon/D +beriberi/SM +berkelium/SM +berm/SM +berry/SDMG +berrylike +berserk/SR +berserker/M +berth/MDGJ +berths +beryl/SM +beryllium/MS +bes +beseech/RSJZG +beseecher/M +beseeching/Y +beseem/GDS +beset/S +besetting +beside/S +besiege/SRDZG +besieger/M +besmear/GSD +besmirch/GSD +besom/GMDS +besot/S +besotted +besotting +besought +bespangle/GSD +bespatter/SGD +bespeak/SG +bespectacled +bespoke +bespoken +best/DRSG +bestial/Y +bestiality/MS +bestiary/MS +bestir/S +bestirred +bestirring +bestow/SGD +bestowal/SM +bestrew/DGS +bestrewn +bestridden +bestride/SG +bestrode +bestseller/MS +bestselling +bestubble/D +bet/MS +beta/SM +betake/SG +betaken +betatron/M +betcha +betel/MS +beth/M +bethel/M +bethink/GS +bethought +betide/GSD +betimes +betoken/GSD +betook +betray/SRDZG +betrayal/SM +betrayer/M +betroth/GD +betrothal/SM +betrothed/U +betroths +better/SDLG +betterment/MS +betting +bettor/SM +between/SP +betweenness/M +betwixt +bevel/SJGMRD +beverage/MS +bevy/SM +bewail/GDS +beware/GSD +bewhisker/D +bewigged +bewilder/LDSG +bewildered/PY +bewildering/Y +bewilderment/SM +bewitch/LGDS +bewitching/Y +bewitchment/SM +bey/MS +beyond/S +bezel/MS +bf +bi/M +biannual/Y +bias/DSMPG +biased/U +biathlon/MS +biaxial/Y +bib/MS +bibbed +bibbing +bible/MS +biblical/Y +biblicists +bibliographer/MS +bibliographic/S +bibliographical/Y +bibliography/MS +bibliophile/MS +bibulous +bicameral +bicameralism/MS +bicarb/MS +bicarbonate/MS +bicentenary/S +bicentennial/S +bicep/S +biceps/M +bichromate/DM +bicker/SRDZG +bickerer/M +bickering/M +biconcave +biconnected +biconvex +bicuspid/S +bicycle/RSDMZG +bicycler/M +bicyclist/SM +bid/GMRS +biddable +bidden/U +bidder/MS +bidding/MS +biddy/SM +bide/S +bider/M +bidet/SM +bidiagonal +bidirectional/Y +bids/A +biennial/SY +biennium/SM +bier/M +bifocal/S +bifurcate/SDXGNY +bifurcation/M +big/PYS +bigamist/SM +bigamous +bigamy/SM +bigged +bigger +biggest +biggie/SM +bigging +biggish +bighead/MS +bighearted/P +bigheartedness/S +bighorn/MS +bight/SMDG +bigmouth/M +bigmouths +bigness/SM +bigot/MDSG +bigoted/Y +bigotry/MS +bigwig/MS +biharmonic +bijection/MS +bijective/Y +bijou/M +bijoux +bike/MZGDRS +biker/M +bikini/SMD +bilabial/S +bilateral/PY +bilateralness/M +bilayer/S +bilberry/MS +bile/SM +bilge/GMDS +biliary +bilinear +bilingual/SY +bilingualism/SM +bilious/P +biliousness/SM +bilk/GZSDR +bilker/M +bill/JGZSBMDR +billboard/MDGS +biller/M +billet/MDGS +billfold/MS +billiard/SM +billing/M +billingsgate/SM +billion/SHM +billionaire/MS +billionths +billow/DMGS +billowy/RT +billposters +billy/SM +bimbo/MS +bimetallic/S +bimetallism/MS +bimodal +bimolecular/Y +bimonthly/S +bin/SM +binary/S +binaural/Y +bind/JDRGZS +binder/M +bindery/MS +binding/MPY +bindingness/M +bindle/M +binds/AU +bindweed/MS +bing/GNDM +binge/MS +bingo/MS +binnacle/MS +binned +binning +binocular/SY +binodal +binomial/SYM +binuclear +biochemical/SY +biochemist/MS +biochemistry/MS +biodegradability/S +biodegradable +biodiversity/S +bioengineering/M +bioethics +biofeedback/SM +biog/S +biograph/RZ +biographer/M +biographic +biographical/Y +biography/MS +biol +biologic/S +biological/SY +biologist/SM +biology/MS +biomass/SM +biomedical +biomedicine/M +biometric/S +biometrics/M +biometry/M +biomolecule/S +biomorph +bionic/S +bionically +bionics/M +biophysic/S +biophysical/Y +biophysicist/SM +biophysics/M +biopic/S +biopsy/SDGM +biorhythm/S +bioscience/S +biosphere/MS +biostatistic/S +biosynthesized +biotechnological +biotechnologist +biotechnology/SM +biotic +biotin/SM +bipartisan +bipartisanship/MS +bipartite/YN +bipartition/M +biped/MS +bipedal +biplane/MS +bipolar +bipolarity/MS +biracial +birch/MRSDNG +bird/SMDRGZ +birdbath/M +birdbaths +birdbrain/SDM +birdcage/SM +birder/M +birdhouse/MS +birdie/MSD +birdieing +birdlike +birdlime/MGDS +birdseed/MS +birdsong +birdtables +birdwatch/GZR +birefringence/M +birefringent +biretta/SM +birth's/A +birth/MDG +birthday/SM +birthmark/MS +birthplace/SM +birthrate/MS +birthright/MS +births/A +birthstone/SM +bis +biscuit/MS +bisect/DSG +bisection/MS +bisector/MS +biserial +bisexual/YMS +bisexuality/MS +bishop/DGSM +bishopric/SM +bismuth/M +bismuths +bison/M +bisque/SM +bistable +bistate +bistro/SM +bisyllabic +bit's/C +bit/MRJSZG +bitblt/S +bitch/MSDG +bitchily +bitchiness/MS +bitchy/PTR +bite/S +biter/M +biting/Y +bitmap/SM +bits/C +bitser/M +bitted +bitten +bitter/PSRDYTG +bittern/SM +bitterness/SM +bitternut/M +bitterroot/M +bittersweet/YMSP +bitting +bitty/PRT +bitumen/MS +bituminous +bitwise +bivalent/S +bivalve/MSD +bivariate +bivouac/MS +bivouacked +bivouacking +biweekly/S +biyearly +biz/M +bizarre/YSP +bizarreness/M +bizzes +bk +bl/D +blab/S +blabbed +blabber/GMDS +blabbermouth/M +blabbermouths +blabbing +black/SJTXPYRDNG +blackamoor/SM +blackball/SDMG +blackberry/GMS +blackbird/SGDRM +blackbirder/M +blackboard/SM +blackbody/S +blackcurrant/M +blacken/GDR +blackener/M +blackguard/MDSG +blackhead/SM +blacking/M +blackish +blackjack/SGMD +blackleg/M +blacklist/DRMSG +blackmail/DRMGZS +blackmailer/M +blackness/MS +blackout/SM +blacksmith/MG +blacksmiths +blacksnake/MS +blackspot +blackthorn/MS +blacktop/MS +blacktopped +blacktopping +bladder/MS +bladdernut/M +bladderwort/M +blade/DSGM +blah/MDG +blahs +blame/DSRBGMZ +blameless/YP +blamelessness/SM +blamer/M +blameworthiness/SM +blameworthy/P +blanc/M +blanch/DRSG +blancher/M +blancmange/SM +bland/PYRT +blandish/SDGL +blandishment/MS +blandness/MS +blank/SPGTYRD +blanket/SDRMZG +blanketing/M +blankness/MS +blare/DSG +blarney/DMGS +blaspheme/RSDZG +blasphemer/M +blasphemous/PY +blasphemousness/M +blasphemy/SM +blast/SMRDGZ +blaster/M +blasting/M +blastoff/SM +blasé +blatancy/SM +blatant/YP +blather/DRGS +blatting +blaze/DSRGMZ +blazer/M +blazing/Y +blazon/SGDR +blazoner/M +bldg +bleach/DRSZG +bleached/U +bleacher/M +bleak/TPYRS +bleakness/MS +blear/GDS +blearily +bleariness/SM +bleary/PRT +bleat/RDGS +bleater/M +bleed/ZRJSG +bleeder/M +bleep/GMRDZS +blemish/DSMG +blemished/U +blench/DSG +blend/GZRDS +blender/M +bless/JGSD +blessed/PRYT +blessedness/MS +blessing/M +blew +blight/GSMDR +blighter/M +blimey/S +blimp/MS +blind/JGTZPYRDS +blinded/U +blinder/M +blindfold/SDG +blinding/MY +blindness/MS +blindside/SDG +blink/RDGSZ +blinker/MDG +blinking/U +blinks/M +blintz/SM +blintze/M +blip/MS +blipped +blipping +bliss/SDMG +blissful/PY +blissfulness/MS +blister/SMDG +blistering/Y +blistery +blithe/TYPR +blitheness/SM +blither/G +blithesome +blitz/GSDM +blitzkrieg/SM +blizzard/MS +bloat/SRDGZ +bloater/M +blob/MS +blobbed +blobbing +bloc/MS +block's +block/USDG +blockade/ZMGRSD +blockader/M +blockage/MS +blockbuster/SM +blockbusting/MS +blocker/MS +blockhead/MS +blockhouse/SM +blocky/R +blog +blog's +blogged +blogger +blogging +blogging's +blogs +bloke/SM +blond/SPMRT +blonde's +blondish +blondness/MS +blood/SMDG +bloodbath +bloodbaths +bloodcurdling +bloodhound/SM +bloodied/U +bloodiness/MS +bloodless/PY +bloodlessness/SM +bloodletting/MS +bloodline/SM +bloodmobile/MS +bloodroot/M +bloodshed/SM +bloodshot +bloodsport/S +bloodstain/MDS +bloodstock/SM +bloodstone/M +bloodstream/SM +bloodsucker/SM +bloodsucking/S +bloodthirstily +bloodthirstiness/MS +bloodthirsty/RTP +bloodworm/M +bloody/TPGDRS +bloodymindedness +bloom/SMRDGZ +bloomer/M +bloop/GSZRD +blooper/M +blossom/DMGS +blossomy +blot/MS +blotch/GMDS +blotchy/RT +blotted +blotter/MS +blotting +blotto +blouse/GMSD +blow/GZRS +blower/M +blowfish/M +blowfly/MS +blowgun/SM +blowing/M +blown/U +blowout/MS +blowpipe/SM +blowtorch/SM +blowup/MS +blowy/RST +blowzy/RT +blubber/GSDR +blubbery +bludgeon/GSMD +blue/JMYTGDRSP +blueback +bluebell/MS +blueberry/SM +bluebill/M +bluebird/MS +bluebonnet/SM +bluebook/M +bluebottle/MS +bluebush +bluefish/SM +bluegill/SM +bluegrass/MS +blueing's +blueish +bluejacket/MS +bluejeans +blueness/MS +bluenose/MS +bluepoint/SM +blueprint/GDMS +bluer/M +bluest/M +bluestocking/SM +bluesy/TR +bluet/MS +bluff/SPGTZYRD +bluffer/M +bluffness/MS +bluing/M +bluish/P +bluishness/M +blunder/GSMDRJZ +blunderbuss/MS +blunderer/M +blundering/Y +blunt/PSGTYRD +bluntness/MS +blur/MS +blurb/GSDM +blurred/Y +blurriness/S +blurring/Y +blurry/RPT +blurt/GSRD +blush/RSDGZ +blusher/M +blushing/UY +bluster/SDRZG +blusterer/M +blustering/Y +blusterous +blustery +blvd +boa/SM +boar/MS +board's +board/IS +boarded +boarder/SM +boardgames +boarding/SM +boardinghouse/SM +boardroom/MS +boardwalk/SM +boast/SJRDGZ +boaster/M +boastful/YP +boastfulness/MS +boat/MDRGZJS +boatclubs +boater/M +boathouse/SM +boating/M +boatload/SM +boatman/M +boatmen +boatswain/SM +boatyard/SM +bob/SM +bobbed +bobbin/MS +bobbing/M +bobble/SDGM +bobby/SM +bobbysoxer's +bobcat/MS +bobolink/SM +bobs/M +bobsled/MS +bobsledded +bobsledder/MS +bobsledding/M +bobsleigh/M +bobsleighs +bobtail/SGDM +bobwhite/SM +boccie/SM +bock/GDS +bockwurst +bod/SGMD +bode/S +bodega/MS +bodhisattva +bodice/SM +bodied/M +bodiless +bodily +boding/M +bodkin/SM +body/DSMG +bodybuilder/SM +bodybuilding/S +bodyguard/MS +bodying/M +bodysuit/S +bodyweight +bodywork/SM +bog/MS +bogey/SGMD +bogeyman/M +bogeymen +bogged +bogging +boggle/SDG +boggling/Y +boggy/RT +bogie's +bogus +bogy's +bogyman +bogymen +bohemian/S +bohemianism/S +boil/JSGZDR +boiled/AU +boiler/M +boilermaker/MS +boilerplate/SM +boils/A +boisterous/YP +boisterousness/MS +bola/SM +bold/YRPST +boldface/SDMG +boldness/MS +bole/MS +bolero/MS +bolivar/MS +bolivares +boll/MDSG +bollard/SM +bollix/GSD +bolo/MS +bologna/MS +bolometer/MS +boloney's +bolster/SRDG +bolsterer/M +bolt/MDRGS +bolted/U +bolter/M +bolts/U +bolus/SM +bomb/SGZDRJ +bombard/LDSG +bombardier/MS +bombardment/SM +bombast/RMS +bombastic +bombastically +bomber/M +bombproof +bombshell/SM +bona +bonanza/MS +bonbon/SM +bond/JMDRSGZ +bondage/SM +bonder/M +bondholder/SM +bondman/M +bondmen +bonds/A +bondsman/M +bondsmen +bondwoman/M +bondwomen +bone/MZDRSG +boned/U +bonehead/SDM +boneless +boner/M +bonfire/MS +bong/GDMS +bongo/MS +bonhomie/MS +boniness/MS +bonito/MS +bonjour +bonkers +bonnet/SGMD +bonneted/U +bonnie +bonny/RT +bonsai/SM +bonus/SM +bony/RTP +bonzes +boo/GSDH +boob/DMSG +booby/SM +boodle/GMSD +boogeyman's +boogie/SD +boogieing +boohoo/GDS +book/GZDRMJSB +bookbind/JRGZ +bookbinder/M +bookbindery/SM +bookbinding/M +bookcase/MS +booked/U +bookend/SGD +bookie/SM +booking/M +bookish/PY +bookishness/M +bookkeep/GZJR +bookkeeper/M +bookkeeping/M +booklet/MS +bookmaker/MS +bookmaking/MS +bookmark/MDGS +bookmobile/MS +bookplate/SM +bookseller/SM +bookshelf/M +bookshelves +bookshop/MS +bookstall/MS +bookstore/SM +bookwork/M +bookworm/MS +boolean/S +boom/DRGJS +boomer/M +boomerang/MDSG +boomtown/S +boon/MS +boondocks +boondoggle/DRSGZ +boondoggler/M +boonies +boor/MS +boorish/PY +boorishness/SM +boost/SGZMRD +booster/M +boosterism +boot's +boot/AGDS +bootblack/MS +bootee/MS +booth/M +booths +bootie's +bootlaces +bootleg/S +bootlegged/M +bootlegger/SM +bootlegging/M +bootless +bootprints +bootstrap/SM +bootstrapped +bootstrapping +booty/SM +booze/DSRGMZ +boozer/M +boozy/TR +bop/S +bopped +bopping +borate/MSD +borax/MS +bordello/MS +border/JRDMGS +borderer/M +borderland/SM +borderline/MS +bore/ZGJDRS +boredom/MS +boreholes +borer/M +boric +boring/YMP +born/AIU +borne/U +boron/SM +borosilicate/M +borough/M +boroughs +borrow/JZRDGBS +borrower/M +borrowing/M +borscht/SM +borstal/MS +borzoi/MS +bosh/MS +bosom's +bosom/SGUD +bosomy/RT +boson/SM +boss/DSRMG +bossily +bossiness/MS +bossism/MS +bossy/PTSR +bosun's +bot/S +botanic/S +botanical/SY +botanist/SM +botany/SM +botch/SRDGZ +botcher/M +botfly/M +both/ZR +bother/DG +bothersome +bothy/M +bottle/GMZSRD +bottleneck/GSDM +bottler/M +bottom/SMRDG +bottomless/YP +bottomlessness/M +bottommost +botulin/M +botulinus/M +botulism/SM +boudoir/MS +bouffant/S +bougainvillea/SM +bough/MD +boughs +bought/N +bouillabaisse/MS +bouillon/MS +boulder/GMDS +boulevard/MS +bounce/SRDGZ +bouncer/M +bouncily +bouncing/Y +bouncy/TRP +bound/AUDI +boundary/MS +bounded/UP +boundedness/MU +bounden +bounder/AM +bounders +bounding +boundless/YP +boundlessness/SM +bounds/IA +bounteous/PY +bounteousness/MS +bountiful/PY +bountifulness/SM +bounty/SDM +bouquet/SM +bourbon/SM +bourgeois/M +bourgeoisie/SM +bout/MS +boutique/MS +boutonnière/MS +bovine/YS +bow/SZGNDR +bowdlerization/MS +bowdlerize/GRSD +bowed/U +bowel/GMDS +bower/DMG +bowie +bowing/M +bowl/GZSMDR +bowlder's +bowleg/SM +bowlegged +bowler/M +bowlful/S +bowline/MS +bowling/M +bowman/M +bowmen +bows/R +bowser/M +bowsprit/SM +bowstring/GSMD +bowwow/DMGS +box/DRSJZGM +boxcar/SM +boxer/M +boxful/M +boxing/M +boxlike +boxtops +boxwood/SM +boxy/TPR +boy/MRS +boycott/RDGS +boycotter/M +boyfriend/MS +boyhood/SM +boyish/PY +boyishness/MS +boyscout +boysenberry/SM +bozo/SM +bpi +bps +bra/MS +brace/DSRJGM +braced/U +bracelet/MS +bracer/M +brachia +brachium/M +bracken/SM +bracket/SGMD +bracketed/U +bracketing/M +brackish/P +brackishness/SM +bract/SM +brad/SM +bradawl/M +bradded +bradding +brae/SM +brag/S +braggadocio/SM +braggart/SM +bragged +bragger/MS +braggest +bragging +braid/RDSJG +braider/M +braiding/M +braille/DSG +brain/GSDM +braincell/S +brainchild/M +brainchildren +braininess/MS +brainless/YP +brainlessness/M +brainpower/M +brainstorm/DRMGJS +brainstorming/M +brainteaser/S +brainteasing +brainwash/JGRSD +brainwasher/M +brainwashing/M +brainwave/S +brainy/RPT +braise/SDG +brake/DSGM +brakeman/M +brakemen/M +bramble/DSGM +brambling/M +brambly/RT +bran/SM +branch/MDSJG +branched/U +branching/M +branchlike +brand/SMRDGZ +branded/U +brander/GDM +brandish/GSD +brandy/GDSM +brandywine +branned +branning +brash/PYSRT +brashness/MS +brass/GSDM +brasserie/SM +brassiere/MS +brassily +brassiness/SM +brassy/RSPT +brat/SM +bratty/RT +bratwurst/MS +bravado/M +bravadoes +brave/DSRGYTP +braveness/MS +bravery/MS +bravest/M +bravo/SDG +bravura/SM +brawl/MRDSGZ +brawler/M +brawn/MS +brawniness/SM +brawny/TRP +bray/SDRG +brayer/M +braze/GZDSR +brazen/PYDSG +brazenness/MS +brazer/M +brazier/SM +breach/MDRSGZ +breacher/M +bread/SMDHG +breadbasket/SM +breadboard/SMDG +breadbox/S +breadcrumb/S +breadfruit/MS +breadline/MS +breadth/M +breadths +breadwinner/MS +break/SZRBG +breakable/U +breakables +breakage/MS +breakaway/MS +breakdown/MS +breaker/M +breakfast/RDMGZS +breakfaster/M +breakfront/S +breaking/M +breakneck +breakout/MS +breakpoint/SMDG +breakthrough/SM +breakthroughs +breakup/SM +breakwater/SM +bream/SDG +breast/MDSG +breastbone/MS +breastfed +breastfeed/G +breasting/M +breastplate/SM +breaststroke/SM +breastwork/MS +breath/ZBJMDRSG +breathable/U +breathalyser/S +breathe +breather/M +breathing/M +breathless/PY +breathlessness/SM +breaths +breathtaking/Y +breathy/TR +bred/DG +bredes +breech/MDSG +breeching/M +breed/SZJRG +breeder's +breeder/I +breeding/IM +breeds/I +breeze/GMSD +breezeway/SM +breezily +breeziness/SM +breezy/RPT +bremsstrahlung/M +brethren +breve/SM +brevet/MS +brevetted +brevetting +breviary/SM +brevity/MS +brew/DRGZS +brewer/M +brewery/MS +brewing/M +brewpub/S +briar's +bribe/GZDSR +briber/M +bribery/MS +brick/GRDSM +brickbat/SM +bricklayer/MS +bricklaying/SM +brickmason/S +brickwork/SM +brickyard/M +bridal/S +bride/MS +bridegroom/MS +bridesmaid/MS +bridge/SDGM +bridgeable/U +bridged/U +bridgehead/MS +bridgework/MS +bridging/M +bridle/SDGM +bridled/U +bridleway/S +brief/YRDJPGTS +briefcase/SM +briefed/C +briefing/M +briefness/MS +briefs/C +brier/MS +brig/SM +brigade/GDSM +brigadier/MS +brigand/MS +brigandage/MS +brigantine/MS +bright/GXTPSYNR +brighten/RDZG +brightener/M +brightness/SM +brilliance/MS +brilliancy/MS +brilliant/PSY +brilliantine/MS +brilliantness/M +brim/SM +brimful +brimless +brimmed +brimming +brimstone/MS +brindle/DSM +brine/GMDSR +briner/M +bring/RGZS +bringer/M +brininess/MS +brink/MS +brinkmanship/SM +briny/PTSR +brioche/SM +briquet's +briquette/MGSD +brisk/YRDPGTS +brisket/SM +briskness/MS +bristle/DSGM +bristly/TR +bristol/S +britches +brittle/YTPDRSG +brittleness/MS +bro/SH +broach/DRSG +broacher/M +broad/TXSYRNP +broadband +broadcast/RSGZJ +broadcaster/M +broadcasts/A +broadcloth/M +broadcloths +broaden/JGRDZ +broadleaved +broadloom/SM +broadminded/P +broadness/S +broadsheet/MS +broadside/SDGM +broadsword/MS +brocade/DSGM +broccoli/MS +brochette/SM +brochure/SM +brogan/MS +brogue/MS +broil/RDSGZ +broiler/M +broke/RGZ +broken/YP +brokenhearted/Y +brokenness/MS +broker/DMG +brokerage/MS +bromide/MS +bromidic +bromine/MS +bronc/S +bronchi/M +bronchial +bronchiolar +bronchiole/MS +bronchiolitis +bronchitic/S +bronchitis/MS +broncho's +bronchus/M +bronco/SM +broncobuster/SM +brontosaur/SM +brontosaurus/SM +bronze/SRDGM +bronzed/M +bronzing/M +brooch/MS +brood/SMRDGZ +brooder/M +broodiness/M +brooding/Y +broodmare/SM +broody/PTR +brook/SGDM +brooklet/MS +brookside +broom/SMDG +broomstick/MS +bros/S +broth/ZMR +brothel/MS +brother/DYMG +brotherhood/SM +brotherliness/MS +brotherly/P +broths +brougham/MS +brought +brouhaha/MS +brow/MS +browbeat/NSG +brown/YRDMSJGTP +brownie/MTRS +browning/M +brownish +brownness/MS +brownout/MS +brownstone/MS +brows/SRDGZ +browse +browser/M +brr +brucellosis/M +bruin/MS +bruise/JGSRDZ +bruised/U +bruiser/M +bruit/DSG +brunch/MDSG +brunet/S +brunette/SM +brunt/GSMD +brush/MSRDG +brusher/M +brushfire/MS +brushlike +brushoff/S +brushwood/SM +brushwork/MS +brushy/R +brusque/PYTR +brusqueness/MS +brutal/Y +brutality/SM +brutalization/SM +brutalize/SDG +brutalized/U +brutalizes/AU +brute/DSRGM +brutish/YP +brutishness/SM +bu +bub/MS +bubble/RSDGM +bubblegum/S +bubbler/M +bubbly/TRS +bubo/M +buboes +bubonic +buccaneer/GMDS +buck/GSDRM +buckaroo/SM +buckboard/SM +bucker/M +bucket/SGMD +bucketful/MS +buckeye/SM +buckhorn/M +buckle/RSDGMZ +buckled/U +buckler/MDG +buckles/U +buckling's +buckling/U +buckram/GSDM +bucksaw/SM +buckshot/MS +buckskin/SM +buckteeth +bucktooth/DM +buckwheat/SM +bucolic/S +bucolically +bud/MS +budded +budding/S +buddy/GSDM +budge/GDS +budgerigar/MS +budget/GMRDZS +budgetary +budgeter/M +budgie/MS +budging/U +buff's +buff/ASGD +buffalo/MDG +buffaloes +buffer/RDMSGZ +buffered/U +bufferer/M +buffet/GMDJS +bufflehead/M +buffoon/SM +buffoonery/MS +buffoonish +bug's +bug/CS +bugaboo/SM +bugbear/SM +bugeyed +bugged/C +bugger/SCM! +buggered +buggering +buggery/M +bugging/C +buggy/RSMT +bugle/GMDSRZ +bugler/M +build/SAG +builder/SM +building/SM +buildup/MS +built/AUI +bulb/DMGS +bulblet +bulbous +bulge/DSGM +bulgy/RT +bulimarexia/S +bulimia/MS +bulimic/S +bulk/GDRMS +bulkhead/SDM +bulkiness/SM +bulky/RPT +bull/MDGS +bulldog/SM +bulldogged +bulldogger +bulldogging +bulldoze/GRSDZ +bulldozer/M +bullet/GMDS +bulletin/SGMD +bulletproof/SGD +bullfight/SJGZMR +bullfighter/M +bullfighting/M +bullfinch/MS +bullfrog/SM +bullhead/DMS +bullheaded/YP +bullheadedness/SM +bullhide +bullhorn/SM +bullied/M +bullion/SM +bullish/PY +bullishness/SM +bullock/MS +bullpen/MS +bullring/SM +bullseye +bullshit/MS! +bullshitted/! +bullshitter/S! +bullshitting/! +bullwhackers +bully/TRSDGM +bullyboy/MS +bullying/M +bulrush/SM +bulwark/GMDS +bum/SM +bumble/JGZRSD +bumblebee/MS +bumbler/M +bumbling/Y +bummed/M +bummer/MS +bummest +bumming/M +bump/GZDRS +bumper/DMG +bumpiness/MS +bumpkin/MS +bumptious/PY +bumptiousness/SM +bumpy/PRT +bun/SM +bunch/MSDG +bunchy/RT +bunco's +buncombe's +bundle/GMRSD +bundled/U +bundler/M +bung/GDMS +bungalow/MS +bungee/SM +bunghole/MS +bungle/GZRSD +bungler/M +bungling/Y +bunion/SM +bunk's +bunk/CSGDR +bunker's/C +bunker/SDMG +bunkhouse/SM +bunkmate/MS +bunko's +bunkum/SM +bunny/SM +bunt/GJZDRS +bunting/M +buoy/SMDG +buoyancy/MS +buoyant/Y +bur/MYS +burble/RSDG +burbler/M +burbs +burden's +burden/UGDS +burdensome/PY +burdensomeness/M +burdock/SM +bureau/MS +bureaucracy/MS +bureaucrat/MS +bureaucratic/U +bureaucratically +bureaucratization/MS +bureaucratize/SDG +burg/SZRM +burgeon/GDS +burger/M +burgess/MS +burgh/MRZ +burgher/M +burghs +burglar/SM +burglarize/GDS +burglarproof/DGS +burglary/MS +burgle/SDG +burgomaster/SM +burgundy/S +burial/ASM +buried/U +burier/M +burl/SMDRG +burlap/MS +burler/M +burlesque/SRDMYG +burlesquer/M +burley/M +burliness/SM +burly/PRT +burn/GZSDRBJ +burnable/S +burned/U +burner/M +burning/Y +burnish/GDRSZ +burnisher/M +burnoose/MS +burnout/MS +burnt/YP +burp/SGMD +burr/GSDRM +burrito/S +burro/SM +burrow/GRDMZS +burrower/M +bursa/M +bursae +bursar/MS +bursary/MS +bursitis/MS +burst/SRG +burster/M +bury/ASDG +bus's/A +bus/GMDSJ +busboy/MS +busby/SM +buses/A +busgirl/S +bush/JMDSRG +bushel/MDJSG +bushiness/MS +bushing/M +bushland +bushman/M +bushmaster/SM +bushmen +bushwhack/RDGSZ +bushwhacker/M +bushwhacking/M +bushy/PTR +busily +business/MS +businesslike +businessman/M +businessmen +businesspeople +businessperson/S +businesswoman/M +businesswomen +busk/GRM +busker/M +buskin/SM +buss/D +bust/MSDRGZ +bustard/MS +buster/M +bustle/GSD +bustling/Y +busty/RT +busy/DSRPTG +busybody/MS +busyness/MS +busywork/SM +but/ACS +butane/MS +butch/RSZ +butcher/MDRYG +butcherer/M +butchery/MS +butene/M +butler/SDMG +butt/SGZMDR +butte/MS +butted/A +butter/RDMGZ +butterball/MS +buttercup/SM +buttered/U +butterfat/MS +butterfingered +butterfingers/M +butterfly/MGSD +buttermilk/MS +butternut/MS +butterscotch/SM +buttery/TRS +butting/M +buttock/SGMD +button's +button/SUDG +buttoner/M +buttonhole/GMRSD +buttonholer/M +buttonweed +buttonwood/SM +buttress/MSDG +butyl/M +butyrate/M +buxom/TPYR +buxomness/M +buy/ZGRS +buyback/S +buyer/M +buyout/S +buzz/DSRMGZ +buzzard/MS +buzzer/M +buzzword/SM +buzzy +bx +bxs +by/ZR +bye/MZS +byelaw's +bygone/S +bylaw/SM +byline/RSDGM +byliner/M +bypass/GSDM +bypath/M +bypaths +byplay/S +byproduct/SM +byre/SM +byroad/MS +bystander/SM +byte/SM +byway/SM +byword/SM +byzantine +c/B +ca +cab/SMR +cabal/SM +cabala/MS +caballed +caballero/SM +caballing +cabana/MS +cabaret/SM +cabbage/MGSD +cabbed +cabbing +cabby's +cabdriver/SM +caber/M +cabin/GDMS +cabinet/MS +cabinetmaker/SM +cabinetmaking/MS +cabinetry/SM +cabinetwork/MS +cable/GMDS +cablecast/SG +cablegram/SM +cabochon/MS +caboodle/SM +caboose/MS +cabriolet/MS +cabstand/MS +cacao/SM +cacciatore +cache/DSRGM +cachepot/MS +cachet/MDGS +cackle/RSDGZ +cackler/M +cackly +cacophonist +cacophonous +cacophony/SM +cacti +cactus/M +cad/SM +cadaver/SM +cadaverous/Y +caddish/PY +caddishness/SM +caddy/GSDM +cadence/CSM +cadenced +cadencing +cadent/C +cadenza/MS +cadet/SM +cadge/DSRGZ +cadger/M +cadmium/MS +cadre/SM +caducei +caduceus/M +caesura/SM +cafeteria/SM +caffeine/SM +caftan/SM +café/MS +cage/MZGDRS +caged/U +cager/M +cagey/P +cagier +cagiest +cagily +caginess/MS +cahoot/MS +caiman's +cairn/SDM +caisson/SM +caitiff/MS +cajole/LGZRSD +cajolement/MS +cajoler/M +cajolery/SM +cake/MGDS +cakewalk/SMDG +cal/C +calabash/SM +calaboose/MS +calamari/S +calamine/GSDM +calamitous/YP +calamitousness/M +calamity/MS +calcareous/PY +calcareousness/M +calciferous +calcification/M +calcify/XGNSD +calcimine/GMSD +calcine/SDG +calcite/SM +calcium/SM +calculability/IM +calculable/IP +calculate/AXNGDS +calculated/PY +calculating/U +calculatingly +calculation/AM +calculative +calculator/SM +calculi +calculus/M +caldera/SM +caldron's +calendar/MDGS +calender/MDGS +calf/M +calfskin/SM +caliber/SM +calibrate/XNGSD +calibrated/U +calibrater's +calibrating/A +calibration/M +calibrator/MS +calico/M +calicoes +calif's +californium/SM +caliper/SDMG +caliph/M +caliphate/SM +caliphs +calisthenic/S +calisthenics/M +call/AGRDBS +calla/MS +callback/S +called/U +callee/M +caller/MS +calligraph/RZ +calligrapher/M +calligraphic +calligraphist/MS +calligraphy/MS +calling/SM +calliope/SM +callisthenics's +callosity/MS +callous/PGSDY +callousness/SM +callow/RTSP +callowness/MS +callus/SDMG +calm/PGTYDRS +calming/Y +calmness/MS +caloric/S +calorie/SM +calorific +calorimeter/MS +calorimetric +calorimetry/M +calumet/MS +calumniate/NGSDX +calumniation/M +calumniator/SM +calumnious +calumny/MS +calvary/M +calve/GDS +calves/M +calyces's +calypso/SM +calyx/MS +cam/MS +camaraderie/SM +camber/DMSG +cambial +cambium/SM +cambric/MS +camcorder/S +came/N +camel/SM +camelhair's +camellia/MS +cameo/GSDM +camera/MS +camerae +cameraman/M +cameramen +camerawoman +camerawomen +camion/M +camisole/MS +cammed +camomile's +camouflage/DRSGZM +camouflager/M +camp's +camp/SCGD +campaign/ZMRDSG +campaigner/M +campanile/SM +campanological +campanologist/SM +campanology/MS +camper/SM +campesinos +campest +campfire/SM +campground/MS +camphor/MS +camping/S +campsite/MS +campus/GSDM +campy/RT +camshaft/SM +can't +can/MDRSZGJ +canal/SGMD +canalization/MS +canalize/GSD +canapé/S +canard/MS +canary/SM +canasta/SM +cancan/SM +cancel/RDZGS +cancelate/D +canceled/U +canceler/M +cancellation/MS +cancer/MS +cancerous/Y +candelabra/S +candelabrum/M +candid/TRYPS +candidacy/MS +candidate/SM +candidature/S +candidly/U +candidness/SM +candle/GMZRSD +candlelight/SMR +candlelit +candlepower/SM +candler/M +candlestick/SM +candlewick/MS +candor/MS +candy/GSDM +cane/SM +canebrake/SM +caner/M +canine/S +caning/M +canister/SGMD +canker/SDMG +cankerous +cannabis/MS +canned +cannelloni +canner/SM +cannery/MS +cannibal/SM +cannibalism/MS +cannibalistic +cannibalization/SM +cannibalize/GSD +cannily/U +canniness/UM +canninesses +canning/M +cannister/SM +cannon/SDMG +cannonade/SDGM +cannonball/SGDM +cannot +canny/RPUT +canoe/DSGM +canoeist/SM +canon/SM +canonic +canonical/SY +canonicalization +canonicalize/GSD +canonist/M +canonization/MS +canonize/SDG +canonized/U +canopy/GSDM +canst +cant's +cant/CZGSRD +cantabile/S +cantaloupe/MS +cantankerous/PY +cantankerousness/SM +cantata/SM +canted/IA +canteen/MS +canter/CM +cantered +cantering +canticle/SM +cantilever/SDMG +canto/MS +canton/MGSLD +cantonal +cantonment/SM +cantor/MS +cants/A +canvas/RSDMG +canvasback/MS +canvass/RSDZG +canvasser/M +canyon/MS +cap/MDRSZB +capability/ISM +capable/PI +capableness/IM +capabler +capablest +capably/I +capacious/PY +capaciousness/MS +capacitance/SM +capacitate/V +capacitive/Y +capacitor/MS +capacity/IMS +caparison/SDMG +cape/SM +caper/GDM +capeskin/SM +capillarity/MS +capillary/S +capita/M +capital/SMY +capitalism/SM +capitalist/SM +capitalistic +capitalistically +capitalization/SMA +capitalize/RSDGZ +capitalized/AU +capitalizer/M +capitalizes/A +capitation/CSM +capitol/SM +capitulate/AXNGSD +capitulation/MA +caplet/S +capo/SM +capon/SM +capped/UA +capping/M +cappuccino/MS +caprice/MS +capricious/PY +capriciousness/MS +caps/AU +capsicum/MS +capsize/SDG +capstan/MS +capstone/MS +capsular +capsule/MGSD +capsulize/GSD +capt/V +captain/SGDM +captaincy/MS +caption/GSDRM +captious/PY +captiousness/SM +captivate/XGNSD +captivation/M +captivator/SM +captive/MS +captivity/SM +captor/SM +capture/AGSD +capturer/MS +car/ZGSMDR +caracul's +carafe/SM +caramel/MS +caramelize/SDG +carapace/SM +carapaxes +carat/SM +caravan/DRMGS +caravaner/M +caravansary/MS +caravanserai's +caravel/MS +caraway/MS +carbide/MS +carbine/MS +carbohydrate/MS +carbolic +carbon/MS +carbonaceous +carbonate/SDXMNG +carbonation/M +carbonic +carboniferous +carbonization/SAM +carbonize/ZGRSD +carbonizer's +carbonizer/AS +carbonizes/A +carbonyl/M +carborundum +carboy/MS +carbuncle/SDM +carbuncular +carburetor/MS +carburetter/S +carburettor/SM +carcase/MS +carcass/SM +carcinogen/SM +carcinogenic +carcinogenicity/MS +carcinoma/SM +card's +card/EDRSG +cardamom/MS +cardboard/MS +carder's/E +carder/MS +cardholders +cardiac/S +cardigan/SM +cardinal/SYM +cardinality/SM +carding/M +cardiogram/MS +cardiograph/M +cardiographs +cardioid/M +cardiologist/SM +cardiology/MS +cardiomegaly/M +cardiopulmonary +cardiovascular +cardsharp/ZSMR +care/S +cared/U +careen/DSG +career/SGRDM +careerism/M +careerist/MS +carefree +careful/PY +carefuller +carefullest +carefulness/MS +caregiver/S +careless/YP +carelessness/MS +carer/M +caress/SRDMVG +caresser/M +caressing/Y +caressive/Y +caret/SM +caretaker/SM +careworn +carfare/MS +cargo/M +cargoes +carhop/SM +carhopped +carhopping +caribou/MS +caricature/GMSD +caricaturisation +caricaturist/MS +caricaturization +caries/M +carillon/SM +carillonned +carillonning +caring/U +carious +carjack/GSJDRZ +carload/MSG +carmine/MS +carnage/MS +carnal/Y +carnality/SM +carnation/IMS +carnelian/SM +carney's +carnival/MS +carnivore/SM +carnivorous/YP +carnivorousness/MS +carny/SDG +carob/SM +carol/SGZMRD +caroler/M +carom/GSMD +carotene/MS +carotid/MS +carousal/MS +carouse/SRDZG +carousel/MS +carouser/M +carp/MDRSGZ +carpal/SM +carpel/SM +carpenter/DSMG +carpentering/M +carpentry/MS +carper/M +carpet/MDJGS +carpetbag/MS +carpetbagged +carpetbagger/MS +carpetbagging +carpeting/M +carpi/M +carping/Y +carpool/DGS +carport/MS +carpus/M +carrageen/M +carrel/SM +carriage/SM +carriageway/SM +carrier/M +carrion/SM +carrot/MS +carroty/RT +carrousel's +carry/RSDZG +carryall/MS +carryout/S +carryover/S +carsick/P +carsickness/SM +cart/MDRGSZ +cartage/MS +carte/M +cartel/SM +carter/M +carthorse/MS +cartilage/MS +cartilaginous +cartload/MS +cartographer/MS +cartographic +cartography/MS +carton/GSDM +cartoon/GSDM +cartoonist/MS +cartridge/SM +cartwheel/MRDGS +carve/DSRJGZ +carven +carver/M +carving/M +caryatid/MS +casaba/SM +casbah/M +cascade/MSDG +cascara/MS +case/DSJMGL +casebook/SM +cased/U +caseharden/SGD +casein/SM +caseload/MS +casement/SM +casework/ZMRS +caseworker/M +cash/GZMDSR +cashbook/SM +cashew/MS +cashier/SDMG +cashless +cashmere/MS +casing/M +casino/MS +cask/GSDM +casket/SGMD +cassava/MS +casserole/MGSD +cassette/SM +cassia/MS +cassino's +cassock/SDM +cassowary/SM +cast/GZSJMDR +castanet/SM +castaway/SM +caste/MHS +castellated +caster/M +castigate/XGNSD +castigation/M +castigator/SM +casting/M +castle/GMSD +castoff/S +castor's +castrate/DSNGX +castration/M +casts/A +casual/SYP +casualness/SM +casualty/SM +casuist/MS +casuistic +casuistry/SM +cat/SMRZ +cataclysm/MS +cataclysmal +cataclysmic +catacomb/MS +catafalque/SM +catalepsy/MS +cataleptic/S +catalog/SDRMZG +cataloger/M +catalpa/SM +catalysis/M +catalyst/SM +catalytic +catalytically +catalyze/DSG +catamaran/MS +catapult/MGSD +cataract/MS +catarrh/M +catarrhs +catastrophe/SM +catastrophic +catastrophically +catatonia/MS +catatonic/S +catbird/MS +catboat/SM +catcall/SMDG +catch/BRSJLGZ +catchable/U +catchall/MS +catcher/M +catchment/SM +catchpenny/S +catchphrase/S +catchup/MS +catchword/MS +catchy/TR +catechism/MS +catechist/SM +catechize/SDG +catecholamine/MS +categoric +categorical/Y +categorization/MS +categorize/RSDGZ +categorized/AU +category/MS +catenate/NF +catenation/MF +cater/GRDZ +catercorner +caterer/M +catering/M +caterpillar/SM +caterwaul/DSG +catfish/MS +catgut/SM +catharses +catharsis/M +cathartic/S +cathedral/SM +catheter/SM +catheterize/GSD +cathode/MS +cathodic +catholic/MS +catholicism +catholicity/MS +cation/MS +cationic +catkin/SM +catlike +catnap/SM +catnapped +catnapping +catnip/MS +catsup's +cattail/SM +catted +cattery/M +cattily +cattiness/SM +catting +cattle/M +cattleman/M +cattlemen +catty/PRST +catwalk/MS +caucus/SDMG +caudal/Y +caught/U +cauldron/MS +cauliflower/MS +caulk/JSGZRD +caulker/M +causal/YS +causality/SM +causate/XVN +causation/M +causative/SY +cause/DSRGMZ +caused/U +causeless +causer/M +causerie/MS +causeway/SGDM +caustic/YS +caustically +causticity/MS +cauterization/SM +cauterize/GSD +cauterized/U +caution/GJDRMSZ +cautionary +cautioner/M +cautious/PIY +cautiousness's/I +cautiousness/SM +cavalcade/MS +cavalier/SGYDP +cavalierness/M +cavalry/MS +cavalryman/M +cavalrymen +cave's +cave/GFRSD +caveat/SM +caveatted +caveatting +caveman/M +cavemen +caver/M +cavern/GSDM +cavernous/Y +caviar/MS +cavil/SJRDGZ +caviler/M +caving/MS +cavity/MFS +cavort/SDG +caw/SMDG +cay's +cay/SC +cayenne/SM +cayman/SM +cayuse/SM +cc +cease/DSCG +ceasefire/S +ceaseless/YP +ceaselessness/SM +ceasing/U +ceca +cecal +cecum/M +cedar/SM +cede/FRSDG +ceded/A +ceder's/F +ceder/SM +cedes/A +cedilla/SM +ceding/A +ceilidh/M +ceiling/MDS +celandine/MS +celebrant/MS +celebrate/XSDGN +celebrated/P +celebratedness/M +celebration/M +celebrator/MS +celebratory +celebrity/MS +celerity/SM +celery/SM +celesta/SM +celestial/YS +celibacy/MS +celibate/SM +cell/GMDS +cellar/RDMGS +cellarer/M +cellist/SM +cello/MS +cellophane/SM +cellphone/S +cellular/SY +cellulite/S +celluloid/SM +cellulose/SM +cement/ZGMRDS +cementa +cementer/M +cementum/SM +cemetery/MS +cenobite/MS +cenobitic +cenotaph/M +cenotaphs +censer/MS +censor/GDMS +censored/U +censorial +censorious/YP +censoriousness/MS +censorship/MS +censure/BRSDZMG +censurer/M +census/SDMG +cent/SZMR +centaur/SM +centavo/SM +centenarian/MS +centenary/S +centennial/YS +center's +center/AC +centerboard/SM +centered +centerer/S +centerfold/S +centering/SM +centerline/SM +centerpiece/SM +centigrade/S +centigram/SM +centiliter/MS +centime/SM +centimeter/SM +centipede/MS +central/STRY +centralism/M +centralist/M +centrality/MS +centralization/CAMS +centralize/CGSD +centralizer/SM +centralizes/A +centrefold's +centric/F +centrifugal/SY +centrifugate/NM +centrifugation/M +centrifuge/GMSD +centripetal/Y +centrist/MS +centroid/MS +centurion/MS +century/MS +cephalic/S +ceramic/MS +ceramicist/S +ceramist/MS +cerate/MD +cereal/MS +cerebellar +cerebellum/MS +cerebra +cerebral/SY +cerebrate/XSDGN +cerebration/M +cerebrum/MS +cerement/SM +ceremonial/YSP +ceremonious/YUP +ceremoniousness's/U +ceremoniousness/MS +ceremony/MS +cerise/SM +cerium/MS +cermet/SM +cert/FS +certain/UY +certainer +certainest +certainty/UMS +certifiable +certifiably +certificate/SDGM +certification/AMC +certified/U +certifier/M +certify/DRSZGNX +certiorari/M +certitude/ISM +cerulean/MS +cervical +cervices/M +cervix/M +cesarean/S +cesium/MS +cessation/SM +cession/FAMSK +cesspit/M +cesspool/SM +cetacean/S +cetera/S +cf +cg +ch/VT +chafe/GDSR +chafer/M +chaff/GRDMS +chaffer/DRG +chafferer/M +chaffinch/SM +chagrin/DGMS +chain's +chain/SGUD +chainlike +chainsaw/SGD +chair/SGDM +chairlady/M +chairlift/MS +chairman/MDGS +chairmanship/MS +chairmen +chairperson/MS +chairwoman/M +chairwomen +chaise/SM +chalcedony/MS +chalet/SM +chalice/DSM +chalk/DSMG +chalkboard/SM +chalkiness/S +chalkline +chalky/RPT +challenge/ZGSRD +challenged/U +challenger/M +challenging/Y +challis/SM +chamber/SZGDRM +chamberer/M +chamberlain/MS +chambermaid/MS +chamberpot/S +chambray/MS +chameleon/SM +chamfer/DMGS +chammy's +chamois/DSMG +chamomile/MS +champ/DGSZ +champagne/MS +champaign/M +champion/MDGS +championship/MS +chance/GMRSD +chanced/M +chancel/SM +chancellery/SM +chancellor/SM +chancellorship/SM +chancery/SM +chanciness/S +chancing/M +chancre/SM +chancy/RPT +chandelier/SM +chandler/MS +change/GZRSD +changeabilities +changeability/UM +changeable/U +changeableness/SM +changeably/U +changed/U +changeless +changeling/M +changeover/SM +changer/M +changing/U +channel/MDRZSG +channeler/M +channeling/M +channelization/SM +channelize/GDS +channellings +chanson/SM +chant/SJGZMRD +chanter/M +chanteuse/MS +chantey/SM +chanticleer/SM +chantry/MS +chanty's +chaos/SM +chaotic +chaotically +chap/MS +chaparral/MS +chapbook/SM +chapeau/MS +chapel/MS +chaperon/GMDS +chaperonage/MS +chaperone's +chaperoned/U +chaplain/MS +chaplaincy/MS +chaplet/SM +chapped +chapping +chapter/SGDM +char/GS +charabanc/MS +character/MDSG +characterful +characteristic/SM +characteristically/U +characterizable/MS +characterization/MS +characterize/DRSBZG +characterized/U +characterizer/M +characterless +charade/SM +charbroil/SDG +charcoal/MGSD +chard/SM +chardonnay/S +charge/EGRSDA +chargeable/P +chargeableness/M +charged/U +charger/AME +chargers +charily +chariness/MS +chariot/SMDG +charioteer/GSDM +charisma/M +charismata +charismatic/S +charismatically +charitable/UP +charitableness/UM +charitablenesses +charitably/U +charity/MS +charlady/M +charlatan/SM +charlatanism/MS +charlatanry/SM +charm/SGMZRD +charmer/M +charming/RYT +charmless +charred +charring +chart/SJMRDGBZ +charted/U +charter's +charter/AGDS +chartered/U +charterer/SM +chartist/SM +chartreuse/MS +chartroom/S +charwoman/M +charwomen +chary/PTR +chase/DSRGZ +chaser/M +chasing/M +chasm/SM +chassis/M +chaste/UTR +chastely +chasten/GSD +chasteness/SM +chastise/ZGLDRS +chastisement/SM +chastiser/M +chastity's/U +chastity/SM +chasuble/SM +chat/MS +chateaus +chatted +chattel/MS +chatter/SZGDRY +chatterbox/MS +chatterer/M +chattily +chattiness/SM +chatting +chatty/RTP +chauffeur/GSMD +chauvinism/MS +chauvinist/MS +chauvinistic +chauvinistically +chaw +cheap/YRNTXSP +cheapen/DG +cheapish +cheapness/MS +cheapskate/MS +cheat/RDSGZ +cheater/M +check's/A +check/GZBSRDM +checkable/U +checkbook/MS +checked/UA +checker/DMG +checkerboard/MS +checklist/S +checkmate/MSDG +checkoff/SM +checkout/S +checkpoint/MS +checkroom/MS +checks/A +checksum/SM +checksummed +checksumming +checkup/MS +cheddar/S +cheek/DMGS +cheekbone/SM +cheekily +cheekiness/SM +cheeky/PRT +cheep/GMDS +cheer/YRDGZS +cheerer/M +cheerful/YP +cheerfuller +cheerfullest +cheerfulness/MS +cheerily +cheeriness/SM +cheerio/S +cheerleader/SM +cheerless/PY +cheerlessness/SM +cheers/S +cheery/PTR +cheese/SDGM +cheeseburger/SM +cheesecake/SM +cheesecloth/M +cheesecloths +cheeseparing/S +cheesiness/SM +cheesy/PRT +cheetah/M +cheetahs +chef/SM +cheffed +cheffing +chelate/XDMNG +chelation/M +chem +chemic +chemical/SYM +chemiluminescence/M +chemiluminescent +chemise/SM +chemist/SM +chemistry/SM +chemotherapeutic/S +chemotherapy/SM +chemurgy/SM +chenille/SM +cherish/GDRS +cherisher/M +cheroot/MS +cherry/SM +chert/MS +cherub/SM +cherubic +cherubim/S +chervil/MS +chess/SM +chessboard/SM +chessman/M +chessmen +chest/MRDS +chesterfield/MS +chestful/S +chestnut/SM +chesty/TR +chevalier/SM +cheviot/S +chevron/DMS +chew/GZSDR +chewer/M +chewiness/S +chewy/RTP +chg +chge +chi/MS +chianti/M +chiaroscuro/SM +chic/SYRPT +chicane/MGDS +chicanery/MS +chichi/RTS +chick/XSNM +chickadee/SM +chicken/GDM +chickenfeed +chickenhearted +chickenpox/MS +chickpea/MS +chickweed/MS +chicle/MS +chicness/S +chicory/MS +chide/GDS +chiding/Y +chief/YRMST +chiefdom/MS +chieftain/SM +chiffon/MS +chiffonier/MS +chigger/MS +chignon/MS +chihuahua/S +chilblain/MS +child/GMYD +childbearing/MS +childbirth/M +childbirths +childcare/S +childes +childhood/MS +childish/YP +childishness/SM +childless/P +childlessness/SM +childlike/P +childlikeness/M +childminders +childproof/GSD +childrearing +children/M +chile's +chili/M +chilies +chill/MRDJGTZPS +chiller/M +chilli's +chilliness/MS +chilling/Y +chillness/MS +chilly/TPRS +chimaera's +chimaerical +chime/DSRGMZ +chimer/M +chimera/SM +chimeric +chimerical +chimney/SMD +chimp/MS +chimpanzee/SM +chin/SGDM +china/MS +chinchilla/SM +chine/MS +chink/DMSG +chinless +chinned +chinner/S +chinning +chino/MS +chinstrap/S +chintz/SM +chintzy/TR +chip/SM +chipboard/M +chipmunk/SM +chipped +chipper/DGS +chipping/MS +chiral +chirography/SM +chiropodist/SM +chiropody/MS +chiropractic/MS +chiropractor/SM +chirp/GDS +chirpy/RT +chirrup/DGS +chisel/ZGSJMDR +chiseler/M +chit/SM +chitchat/SM +chitchatted +chitchatting +chitin/SM +chitinous +chitterlings +chivalric +chivalrous/YP +chivalrously/U +chivalrousness/MS +chivalry/SM +chive/GMDS +chivvy/D +chivying +chlamydia/S +chlamydiae +chloral/MS +chlorate/M +chlordane/MS +chloride/MS +chlorinate/XDSGN +chlorinated/C +chlorinates/C +chlorination/M +chlorine/MS +chlorofluorocarbon/S +chloroform/DMSG +chlorophyll/SM +chloroplast/MS +chloroquine/M +chm +chock/SGRDM +chockablock +chocoholic/S +chocolate/MS +chocolaty +choice/RSMTYP +choiceness/M +choir/SDMG +choirboy/MS +choirmaster/SM +choke/DSRGZ +chokeberry/M +chokecherry/SM +choker/M +chokes/M +choking/Y +choler/SM +cholera/SM +choleric +cholesterol/SM +choline/M +cholinesterase/M +chomp/DSG +choose/GZRS +chooser/M +choosiness/S +choosy/RPT +chop/S +chophouse/SM +chopped +chopper/SDMG +choppily +choppiness/MS +chopping +choppy/RPT +chopstick/SM +choral/SY +chorale/MS +chord/SGMD +chordal +chordata +chordate/MS +chording/M +chore/DSGNM +chorea/MS +choreograph/ZGDR +choreographer/M +choreographic +choreographically +choreographs +choreography/MS +chorines +chorion/M +chorister/SM +choroid/S +chortle/ZGDRS +chortler/M +chorus/GDSM +chose/S +chosen/U +chow/DGMS +chowder/SGDM +chrism/SM +chrissake +christen/SAGD +christened/U +christening/SM +chroma/M +chromate/M +chromatic/PS +chromatically +chromaticism/M +chromaticness/M +chromatics/M +chromatin/MS +chromatogram/MS +chromatograph +chromatographic +chromatography/M +chrome/GMSD +chromic +chromite/M +chromium/SM +chromosomal +chromosome/MS +chromosphere/M +chronic/S +chronically +chronicle/SRDMZG +chronicled/U +chronicler/M +chronograph/M +chronographs +chronography +chronological/Y +chronologist/MS +chronology/MS +chronometer/MS +chronometric +chrysalids +chrysalis/SM +chrysanthemum/MS +chub/MS +chubbiness/SM +chubby/RTP +chuck/GSDM +chuckhole/SM +chuckle/DSG +chuckling/Y +chuff/DM +chug/MS +chugged +chugging +chukka/S +chum/MS +chummed +chummily +chumminess/MS +chumming +chummy/SRTP +chump/MDGS +chumping/M +chunk/SGDM +chunkiness/MS +chunky/RPT +chuntering +church/MDSYG +churchgoer/SM +churchgoing/SM +churchliness/M +churchly/P +churchman/M +churchmen +churchwarden/SM +churchwoman/M +churchwomen +churchyard/SM +churl/SM +churlish/YP +churlishness/SM +churn/SGZRDM +churner/M +churning/M +chute/DSGM +chutney/MS +chutzpa/SM +chutzpah/M +chutzpahs +chyme/SM +château/M +châteaux +châtelaine/SM +ciao/S +cicada/MS +cicatrice/S +cicatrix's +cicerone/MS +ciceroni +cider's/C +cider/SM +cigar/SM +cigarette/MS +cigarillo/MS +cilantro/S +cilia/M +ciliate/FDS +ciliately +cilium/M +cinch/MSDG +cinchona/SM +cincture/MGSD +cinder/DMGS +cine/M +cinema/SM +cinematic +cinematographer/MS +cinematographic +cinematography/MS +cinnabar/MS +cinnamon/MS +cipher/MSGD +ciphered/C +ciphers/C +cir +circa +circadian +circle/RSDGM +circler/M +circlet/MS +circuit/GSMD +circuital +circuitous/YP +circuitousness/MS +circuitry/SM +circuity/MS +circulant +circular/PSMY +circularity/SM +circularize/GSD +circularness/M +circulate/ASDNG +circulation/MA +circulations +circulative +circulatory +circumcise/DRSXNG +circumcised/U +circumciser/M +circumcision/M +circumference/SM +circumferential/Y +circumflex/MSDG +circumlocution/MS +circumlocutory +circumnavigate/DSNGX +circumnavigation/M +circumnavigational +circumpolar +circumscribe/GSD +circumscription/SM +circumspect/Y +circumspection/SM +circumsphere +circumstance/SDMG +circumstantial/YS +circumvent/SBGD +circumvention/MS +circus/SM +cirque/SM +cirrhoses +cirrhosis/M +cirrhotic/S +cirri/M +cirrus/M +cistern/SM +cit/DSG +citadel/SM +citation/SMA +citations/I +cite/ISDAG +citified +citizen/SYM +citizenry/SM +citizenship/MS +citrate/DM +citric +citron/MS +citronella/MS +citrus/SM +city/DSM +cityscape/MS +citywide +civet/SM +civic/S +civics/M +civil/UY +civilian/SM +civility/IMS +civilization/AMS +civilizational/MS +civilize/DRSZG +civilized/PU +civilizedness/M +civilizer/M +civilizes/AU +civvies +ck/C +cl/GJ +clack/SDG +clad/U +cladding/SM +clads +claim/CDRSKAEGZ +claimable +claimant/MS +claimed/U +claimer/KMACE +clairvoyance/MS +clairvoyant/YS +clam/MS +clambake/MS +clamber/SDRZG +clamberer/M +clammed +clammily +clamminess/MS +clamming +clammy/TPR +clamor/GDRMSZ +clamorer/M +clamorous/PUY +clamorousness/UM +clamp/MRDGS +clampdown/SM +clamper/M +clamshell/MS +clan/MS +clandestine/YP +clandestineness/M +clang/SGZRD +clanger/M +clangor/MDSG +clangorous/Y +clank/SGDM +clanking/Y +clannish/PY +clannishness/SM +clansman/M +clansmen +clap/S +clapboard/SDGM +clapped +clapper/GMDS +clapping +claptrap/SM +claque/MS +claret/MDGS +clarification/M +clarifier/M +clarify/NGXDRS +clarinet/SM +clarinetist/SM +clarinettist's +clarion/GSMD +clarities +clarity/UM +clash/RSDG +clasher/M +clasp's +clasp/UGSD +clasped/M +clasper/M +class/GRSDM +classer/M +classic/S +classical/Y +classicism/SM +classicist/SM +classics/M +classifiable/U +classification/AMC +classificatory +classified/S +classifier/SM +classify/CNXASDG +classiness/SM +classless/P +classmate/MS +classroom/MS +classwork/M +classy/PRT +clatter/SGDR +clatterer/M +clattering/Y +clattery +clausal +clause/MS +claustrophobia/SM +claustrophobic +clave's/F +clave/RM +clavichord/SM +clavicle/MS +clavier/MS +claw/GDRMS +clawer/M +clay/MDGS +clayey +clayier +clayiest +claymore/MS +clean/UYRDPT +cleanable +cleaner/MS +cleaning/SM +cleanliness/UMS +cleanly/PRTU +cleanness/MSU +cleans/GDRSZ +cleanse +cleanser/M +cleanup/MS +clear/UTRD +clearance/MS +clearcut +clearer/M +clearheaded/PY +clearheadedness/M +clearing/MS +clearinghouse/S +clearly +clearness/MS +clears +clearway/M +cleat/MDSG +cleavage/MS +cleave/RSDGZ +cleaver/M +clef/SM +cleft/MDGS +clematis/MS +clemence +clemency/ISM +clement/IY +clements +clench/UD +clenches +clenching +clerestory/MS +clergy/MS +clergyman/M +clergymen +clergywoman +clergywomen +cleric/SM +clerical/YS +clericalism/SM +clerk/SGYDM +clerkship/MS +clever/RYPT +cleverness/SM +clevis/SM +clew/DMGS +cliché/SM +clichéd +click/GZSRDM +clicker/M +client/SM +clientèle/SM +cliff/SM +cliffhanger/MS +cliffhanging +climacteric/SM +climactic +climate/MS +climatic +climatically +climatological/Y +climatologist/SM +climatology/MS +climax/MDSG +climb/BGZSJRD +climbable/U +climbdown +climbed/U +climber/M +clime/SM +clinch/DRSZG +clincher/M +clinching/Y +cling/U +clinger/MS +clinging +clingy/TR +clinic/MS +clinical/Y +clinician/MS +clink/RDGSZ +clinker/GMD +clinometer/MIS +cliometric/S +cliometrician/S +clip/SM +clipboard/SM +clipped/U +clipper/MS +clipping/SM +clique/SDGM +cliquey +cliquier +cliquiest +cliquish/YP +cliquishness/SM +clitoral +clitorides +clitoris/MS +cloaca/M +cloacae +cloak's +cloak/USDG +cloakroom/MS +clobber/DGS +cloche/MS +clock/SGZRDMJ +clocker/M +clockmaker/M +clockwatcher +clockwise +clockwork/MS +clod/MS +clodded +clodding +cloddish/P +cloddishness/M +clodhopper/SM +clog's +clog/US +clogged/U +clogging/U +cloisonnes +cloisonné +cloister/MDGS +cloistral +clomp/MDSG +clonal +clone/DSRGMZ +clonk/SGD +clop/S +clopped +clopping +close/EDSRG +closed/U +closefisted +closely +closemouthed +closeness/MS +closeout/MS +closer/EM +closers +closest +closet/MDSG +closeup/S +closing/S +closure's/I +closure/EMS +closured +closuring +clot/MS +cloth/GJMSD +clothbound +clothe/UDSG +clothesbrush +clotheshorse/MS +clothesline/SDGM +clothesman +clothesmen +clothespin/MS +clothier/MS +clothing/M +cloths +clotted +clotting +cloture/MDSG +cloud/SGMD +cloudburst/MS +clouded/U +cloudiness/SM +cloudless/YP +cloudlessness/M +cloudscape/SM +cloudy/TPR +clout/GSMD +clove/SRMZ +cloven +clover/M +cloverleaf/MS +clown/DMSG +clownish/PY +clownishness/SM +cloy/DSG +cloying/Y +club/MS +clubbed/M +clubbing/M +clubfeet +clubfoot/DM +clubhouse/SM +clubroom/SM +cluck/GSDM +clue/MGDS +clueless +clump/MDGS +clumpy/RT +clumsily +clumsiness/MS +clumsy/PRT +clung +clunk/SGZRDM +clunky/PRYT +cluster/SGJMD +clustered/AU +clusters/A +clutch/DSG +clutter/GSD +cluttered/U +cm +cnidarian/MS +co/DES +coach/MSRDG +coacher/M +coachman/M +coachmen +coachwork/M +coadjutor/MS +coagulable +coagulant/SM +coagulate/GNXSD +coagulation/M +coagulator/S +coal/MDRGS +coaler/M +coalesce/GDS +coalescence/SM +coalescent +coalface/SM +coalfield/MS +coalition/MS +coalitionist/SM +coalminers +coarse/TYRP +coarsen/SGD +coarseness/SM +coast/SMRDGZ +coastal +coaster/M +coastguard/MS +coastline/SM +coat/MDRGZJS +coated/U +coating/M +coattail/S +coattest +coauthor/MDGS +coax/GZDSR +coaxer/M +coaxial/Y +coaxing/Y +cob/SM +cobalt/MS +cobbed +cobbing +cobble/SRDGMZ +cobbler/M +cobblestone/MSD +coble/M +cobra/MS +cobweb/SM +cobwebbed +cobwebbing +cobwebby/RT +coca/MS +cocaine/MS +cocci/MS +coccus/M +coccyges +coccyx/M +cochineal/SM +cochlea/SM +cochleae +cochlear +cock/GDRMS +cockade/SM +cockamamie +cockatoo/SM +cockatrice/MS +cockcrow/MS +cocker/M +cockerel/MS +cockeye/DM +cockeyed/PY +cockfight/MJSG +cockfighting/M +cockily +cockiness/MS +cockle/SDGM +cocklebur/M +cockleshell/SM +cockney/MS +cockpit/MS +cockroach/SM +cockscomb/SM +cockshies +cocksucker/S! +cocksure +cocktail/GDMS +cocky/RPT +coco/MS +cocoa/SM +coconut/SM +cocoon/GDMS +cod/MDRSZGJ +coda/SM +codded +codding +coddle/GSRD +coddler/M +code's +code/SCZGJRD +codebook/S +codebreak/R +coded/UA +codeine/MS +codename/D +codependency/S +codependent/S +coder/CM +codes/A +codetermine/S +codeword/SM +codex/M +codfish/SM +codger/MS +codices/M +codicil/SM +codification/M +codifier/M +codify/NZXGRSD +coding/M +codling/M +codpiece/MS +coed/SM +coedited +coediting +coeditor/MS +coedits +coeducation/SM +coeducational +coefficient/SYM +coelenterate/MS +coequal/SY +coerce/SRDXVGNZ +coercer/M +coercible/I +coercion/M +coercive/PY +coerciveness/M +coeval/YS +coexist/GDS +coexistence/MS +coexistent +coextensive/Y +cofactor/MS +coffee/SM +coffeecake/SM +coffeecup +coffeehouse/SM +coffeemaker/S +coffeepot/MS +coffer/DMSG +cofferdam/SM +coffin/DMGS +cog/MS +cogency/MS +cogent/Y +cogged +cogging +cogitate/DSXNGV +cogitation/M +cogitator/MS +cognac/SM +cognate/SXYN +cognation/M +cognition/SAM +cognitional +cognitive/SY +cognizable +cognizance/MAI +cognizances/A +cognizant/I +cognomen/SM +cognoscente +cognoscenti +cogwheel/SM +cohabit/SDG +cohabitant/MS +cohabitation/SM +cohabitational +coheir/MS +cohere/GSRD +coherence/SIM +coherencies +coherency/I +coherent/IY +coherer/M +cohesion/MS +cohesive/PY +cohesiveness/SM +coho/MS +cohoes +cohort/SM +coif/SM +coiffed +coiffing +coiffure/MGSD +coil/UGSAD +coin/GZSDRM +coinage's/A +coinage/SM +coincide/GSD +coincidence/MS +coincident/Y +coincidental/Y +coined/U +coiner/M +coinsurance/SM +cointreau +coital/Y +coitus/SM +coke/MGDS +col/SD +cola/SM +colander/SM +colatitude/MS +cold/YRPST +coldblooded +coldish +coldness/MS +coleslaw/SM +coleus/SM +colic/SM +colicky +coliform +coliseum/SM +colitis/MS +coll/G +collaborate/VGNXSD +collaboration/M +collaborative/SY +collaborator/SM +collage/MGSD +collagen/M +collapse/SDG +collapsibility/M +collapsible +collar/DMGS +collarbone/MS +collard/SM +collarless +collate/SDVNGX +collated/U +collateral/SYM +collation/M +collator/MS +colleague/SDGM +collect/SAGD +collected/PY +collectedness/M +collectible/S +collection/AMS +collective/SY +collectivism/SM +collectivist/MS +collectivity/MS +collectivization/MS +collectivize/DSG +collector/MS +colleen/SM +college/SM +collegiality/S +collegian/SM +collegiate/Y +collide/SDG +collie/MZSRD +collier/M +colliery/MS +collimate/C +collimated/U +collimates +collimating +collimation/M +collimator/M +collinear +collinearity/M +collision/SM +collisional +collocate/XSDGN +collocation/M +colloid/MS +colloidal/Y +colloq +colloquial/SY +colloquialism/MS +colloquies +colloquium/SM +colloquy/M +collude/SDG +collusion/SM +collusive +collying +cologne/MSD +colon/SM +colonel/MS +colonelcy/MS +colonial/SPY +colonialism/MS +colonialist/MS +colonist/SM +colonization/ACSM +colonize/ACSDG +colonized/U +colonizer/MS +colonizes/U +colonnade/MSD +colony/SM +colophon/SM +color/SRDMGZJ +colorant/SM +coloration/EMS +coloratura/SM +colorblind/P +colorblindness/S +colored/USE +colorer/M +colorfast/P +colorfastness/SM +colorful/PY +colorfulness/MS +colorimeter/SM +colorimetry +coloring/M +colorization/S +colorize/GSD +colorizing/C +colorless/PY +colorlessness/SM +colors/EA +colossal/Y +colossi +colossus/M +colostomy/SM +colostrum/SM +colt/MRS +colter/M +coltish/PY +coltishness/M +columbine/SM +column/SDM +columnar +columnist/MS +columnize/GSD +com/LJRTZG +coma/SM +comae +comaker/SM +comatose +comb/SGZDRMJ +combat/SVGMD +combatant/SM +combative/PY +combativeness/MS +combed/U +comber/M +combination/ASM +combinational/A +combinator/SM +combinatorial/Y +combinatoric/S +combine/ZGBRSD +combined/AU +combiner/M +combines/A +combining/A +combo/MS +combusted +combustibility/SM +combustible/SI +combustion/MS +combustive +come/IZSRGJ +comeback/SM +comedian/SM +comedic +comedienne/SM +comedown/MS +comedy/SM +comeliness/SM +comely/TPR +comer/IM +comes/M +comestible/MS +comet/SM +cometary +cometh +comeuppance/SM +comfit's +comfit/SE +comfort/ESMDG +comfortability/S +comfortable/U +comfortableness/MS +comfortably/U +comforted/U +comforter/MS +comforting/YE +comfy/RT +comic/MS +comical/Y +comicality/MS +comity/SM +comm +comma/MS +command/SZRDMGL +commandant/MS +commandeer/SDG +commander/M +commanding/Y +commandment/SM +commando/SM +commemorate/SDVNGX +commemoration/M +commemorative/YS +commemorator/S +commence/ALDSG +commencement/AMS +commencer/M +commend/GSADRB +commendably +commendation/ASM +commendatory/A +commender/AM +commensurable/I +commensurate/IY +commensurates +commensuration/SM +comment's +comment/SUGD +commentary/MS +commentate/GSD +commentator/SM +commenter/M +commerce/MGSD +commercial/PYS +commercialism/MS +commercialization/SM +commercialize/GSD +commie/SM +commingle/GSD +commiserate/VGNXSD +commiseration/M +commissar/MS +commissariat/MS +commissary/MS +commission's/A +commission/ASCGD +commissioner/SM +commit/SA +commitment/SM +committable +committal/MA +committals +committed/UA +committee/MS +committeeman/M +committeemen +committeewoman/M +committeewomen +committing/A +commode/MS +commodes/IE +commodious/YIP +commodiousness/MI +commodity/MS +commodore/SM +common/RYUPT +commonality/MS +commonalty/MS +commoner/MS +commonness/MSU +commonplace/SP +commonplaceness/M +commons/M +commonsense +commonweal/SHM +commonwealth/M +commonwealths +commotion/MS +communal/Y +communality/M +commune/XSDNG +communicability/MS +communicable/IU +communicably +communicant/MS +communicate/VNGXSD +communication/M +communicational +communicative/PY +communicativeness/M +communicator/SM +communion/M +communique/S +communism/MS +communist/MS +communistic +communitarian/M +community/MS +communize/SDG +commutable/I +commutate/XVGNSD +commutation/M +commutative/Y +commutativity +commutator/MS +commute/BZGRSD +commuter/M +comp/GSYD +compact/TZGSPRDY +compaction/M +compactness/MS +compactor/MS +companion/GBSMD +companionable/P +companionableness/M +companionably +companionship/MS +companionway/MS +company/MSDG +comparabilities +comparability/IM +comparable/P +comparableness/M +comparably/I +comparative/PYS +comparativeness/M +comparator/SM +compare/GRSDB +comparer/M +comparison/MS +compartment/SDMG +compartmental +compartmentalization/SM +compartmentalize/DSG +compass/MSDG +compassion/MS +compassionate/PSDGY +compassionateness/M +compatibility/IMS +compatible/SI +compatibleness/M +compatibly/I +compatriot/SM +compeer/DSGM +compel/S +compellable +compelled +compelling/YM +compendious +compendium/MS +compensable +compensate/XVNGSD +compensated/U +compensation/M +compensator/M +compensatory +compete/GSD +competence/ISM +competency's +competency/IS +competent/IY +competition/SM +competitive/YP +competitiveness/SM +competitor/MS +compilable/U +compilation/SAM +compile/ASDCG +compiler's +compiler/CS +complacence/S +complacency/SM +complacent/Y +complain/GZRDS +complainant/MS +complainer/M +complaining/YU +complaint/MS +complaisance/SM +complaisant/Y +complected +complement/ZSMRDG +complementariness/M +complementarity +complementary/SP +complementation/M +complementer/M +complete/BTYVNGPRSDX +completed/U +completely/I +completeness/ISM +completer/M +completion/MI +complex/TGPRSDY +complexion/DMS +complexional +complexity/MS +complexness/M +compliance/SM +compliant/Y +complicate/SDG +complicated/YP +complicatedness/M +complication/M +complicator/SM +complicit +complicity/MS +complier/M +compliment/ZSMRDG +complimentary/U +complimenter/M +comply/ZXRSDNG +component/SM +comport/GLSD +comportment/SM +compose/CGASDE +composed/PY +composedness/M +composer/CM +composers +composite/YSDXNG +composition/CMA +compositional/Y +compositions/C +compositor/MS +compost/DMGS +composure/ESM +compote/MS +compound/RDMBGS +compounded/U +compounder/M +comprehend/DGS +comprehending/U +comprehensibility/SIM +comprehensible/PI +comprehensibleness/IM +comprehensibly/I +comprehension/IMS +comprehensive/YPS +comprehensiveness/SM +compress/SDUGC +compressed/Y +compressibility/IM +compressible/I +compression/CSM +compressional +compressive/Y +compressor/MS +comprise/GSD +compromise/SRDGMZ +compromiser/M +compromising/UY +comptroller/SM +compulsion/SM +compulsive/PYS +compulsiveness/MS +compulsivity +compulsorily +compulsory/S +compunction/MS +computability/M +computable/UI +computably +computation/SM +computational/Y +compute/RSDZBG +computed/A +computer/M +computerese +computerization/MS +computerize/SDG +computes/A +computing/A +comrade/YMS +comradely/P +comradeship/MS +con/SGM +concatenate/XSDG +concave/YP +concaveness/MS +conceal/BSZGRDL +concealed/U +concealer/M +concealing/Y +concealment/MS +conceded/Y +conceit/SGDM +conceited/YP +conceitedness/SM +conceivable/IU +conceivably/I +conceive/BGRSD +conceiver/M +concentrate/VNGSDX +concentration/M +concentrator/MS +concentrically +concept/SVM +conception/MS +conceptional +conceptual/Y +conceptuality/M +conceptualization's +conceptualization/A +conceptualizations +conceptualize/DRSG +conceptualizing/A +concern/USGD +concerned/YU +concert's +concert/EDSG +concerted/PY +concertina/MDGS +concertize/GDS +concertmaster/MS +concerto/SM +concession/R +concessionaire/SM +concessional +concessionary +conch/MDG +conchs +concierge/SM +conciliar +conciliate/GNVX +conciliation/ASM +conciliator/MS +conciliatory/A +concise/TYRNPX +conciseness/SM +concision/M +conclave/S +conclude/RSDG +concluder/M +conclusion/SM +conclusive/IPY +conclusiveness/ISM +concoct/RDVGS +concocter/M +concoction/SM +concomitant/YS +concordance/MS +concordant/Y +concordat/SM +concourse +concrete/NGXRSDPYM +concreteness/MS +concretion/M +concubinage/SM +concubine/SM +concupiscence/SM +concupiscent +concur/S +concurrence/MS +concuss/VD +concussion/MS +condemn/ZSGRDB +condemnate/XN +condemnation/M +condemnatory +condemner/M +condensate/NMXS +condensation/M +condense/ZGSD +condenser/M +condensible +condescend +condescending/Y +condescension/MS +condign +condiment/SM +condition's +condition/AGSJD +conditional/UY +conditionals +conditioned/U +conditioner/MS +conditioning/M +condo/SM +condole +condolence/MS +condom/SM +condominium/MS +condone/GRSD +condoner/M +condor/MS +conduce/VGSD +conducive/P +conduciveness/M +conduct/V +conductance/SM +conductibility/SM +conductible +conduction/MS +conductive/Y +conductivity/MS +conductor/MS +conductress/MS +conduit/MS +coneflower/M +coney's +confab/MS +confabbed +confabbing +confabulate/XSDGN +confabulation/M +confect/S +confection/RDMGZS +confectioner/M +confectionery/SM +confectionist +confederacy/MS +confederate/M +confer/SB +conferee/MS +conference/DSGM +conferrable +conferral/SM +conferred +conferrer/SM +conferring +confessed/Y +confession/MS +confessional/SY +confessor/SM +confetti/M +confidant/SM +confidante/SM +confide/ZGRSD +confidence/SM +confident/Y +confidential/PY +confidentiality/MS +confidentialness/M +confider/M +confiding/PY +configuration/ASM +configure/AGSDB +confine/L +confined/U +confinement/MS +confiner/M +confirm/AGDS +confirmation/ASM +confirmatory +confirmed/YP +confirmedness/M +confiscate/DSGNX +confiscation/M +confiscator/MS +confiscatory +conflagration/MS +conflate/NGSDX +conflation/M +conflict/SVGDM +conflicting/Y +confluence/MS +conform/B +conformable/U +conformal +conformance/SM +conformational/Y +conformer/M +conformism/SM +conformist/SM +conformities +conformity/MUI +confound/R +confounded/Y +confront/Z +confrontation/SM +confrontational +confronter/M +confrère/MS +confuse/RBZ +confused/PY +confusedness/M +confusing/Y +confutation/MS +confute/GRSD +confuter/M +conga/MDG +congeal/GSDL +congealment/MS +congenial/U +congeniality/UM +conger/SM +congeries/M +congest/VGSD +congestion/MS +conglomerate/XDSNGVM +conglomeration/M +congrats +congratulate/NGXSD +congratulation/M +congratulatory +congregate/DSXGN +congregation/M +congregational +congregationalism/MS +congregationalist/MS +congress/MSDG +congressional/Y +congressman/M +congressmen +congresspeople +congressperson/S +congresswoman/M +congresswomen +congruence/IM +congruences +congruency/M +congruent/YI +congruential +congruity/MSI +congruous/YIP +congruousness/IM +conic/S +conical/PSY +conicalness/M +conics/M +conifer/MS +coniferous +conjectural/Y +conjecture/GMDRS +conjecturer/M +conjoint +conjugacy +conjugal/Y +conjugate/XVNGYSDP +conjugation/M +conjunct/DSV +conjunctiva/MS +conjunctive/YS +conjunctivitis/SM +conjuration/MS +conjure/RSDZG +conjurer/M +conjuring/M +conk/ZDR +conker/M +conman +conn/GVDR +connect/ADGES +connected/U +connectedly/E +connectedness/ME +connectible +connection/AME +connectionless +connections/E +connective/SYM +connectivity/MS +connector/MS +connexion/MS +conniption/MS +connivance/MS +connive/ZGRSD +conniver/M +connoisseur/MS +connotative/Y +connubial/Y +conquer/RDSBZG +conquerable/U +conquered/AU +conqueror/MS +conquers/A +conquest/ASM +conquistador/MS +consanguineous/Y +consanguinity/SM +conscienceless +conscientious/YP +conscientiousness/MS +conscionable/U +conscious/UYSP +consciousness/MUS +conscription/SM +consecrate/XDSNGV +consecrated/AU +consecrates/A +consecrating/A +consecration/AMS +consecutive/YP +consecutiveness/M +consensus/SM +consent/SZGRD +consenter/M +consenting/Y +consequence +consequent/PSY +consequential/IY +consequentiality/S +consequentialness/M +consequently/I +conservancy/SM +conservation/SM +conservationism +conservationist/SM +conservatism/SM +conservative/SYP +conservativeness/M +conservator/MS +conservatory/MS +consider/GASD +considerable/I +considerables +considerably/I +considerate/XIPNY +considerateness/MSI +consideration/ASMI +considered/U +considerer/M +considering/S +consign/ASGD +consignee/SM +consignment/SM +consist/DSG +consistence/S +consistency/IMS +consistent/IY +consistory/MS +consolable/I +consolation's/E +consolation/MS +consolatory +console/ZBG +consoled/U +consoler/M +consolidate/NGDSX +consolidated/AU +consolidates/A +consolidation/M +consolidator/SM +consoling/Y +consommé/S +consonance/IM +consonances +consonant/MYS +consonantal +consortia +consortium/M +conspectus/MS +conspicuous/YIP +conspicuousness/IMS +conspiracy/MS +conspirator/SM +conspiratorial/Y +constable +constabulary/MS +constance +constancy/IMS +constant/IY +constants +constellation/SM +consternate/XNGSD +consternation/M +constipate/XDSNG +constipation/M +constituency/MS +constituent/SYM +constitute/NGVXDS +constituted/A +constitutes/A +constituting/A +constitution/AMS +constitutional/SY +constitutionality's +constitutionality/US +constitutionally/U +constitutive/Y +constrain +constrained/U +constrainedly +constraint/MS +constrict/SDGV +constriction/MS +constrictor/MS +construable +construct/ASDGV +constructibility +constructible/A +construction/MAS +constructional/Y +constructionist/MS +constructions/C +constructive/YP +constructiveness/SM +constructor/MS +construe/GSD +consul/KMS +consular/S +consulate/MS +consulship/MS +consult/RDVGS +consultancy/S +consultant/MS +consultation/SM +consultative +consulted/A +consulter/M +consumable/S +consume/JZGSDB +consumed/Y +consumer/M +consumerism/MS +consumerist/S +consuming/Y +consummate/DSGVY +consummated/U +consumption/SM +consumptive/YS +cont +cont'd +contact's/A +contact/BGD +contacted/A +contacts/A +contagion/SM +contagious/YP +contagiousness/MS +contain/SLZGBRD +container/M +containerization/SM +containerize/GSD +containment/SM +contaminant/SM +contaminate/SDCXNG +contaminated/AU +contaminates/A +contaminating/A +contamination/CM +contaminative +contaminator/MS +contd +contemn/SGD +contemplate/DVNGX +contemplation/M +contemplative/PSY +contemplativeness/M +contemporaneity/MS +contemporaneous/PY +contemporaneousness/M +contempt/M +contemptible/P +contemptibleness/M +contemptibly +contemptuous/PY +contemptuousness/SM +content/EMDLSG +contented/YP +contentedly/E +contentedness/SM +contention/MS +contentious/PY +contentiousness/SM +contently +contentment's +contentment/ES +conterminous/Y +contestable/I +contestant/SM +contested/U +contextualize/GDS +contiguity/MS +contiguous/YP +contiguousness/M +continence/ISM +continent's +continent/IY +continental/SY +continents +contingency/SM +contingent/SMY +continua +continuable +continual/Y +continuance/ESM +continuant/M +continuation/ESM +continue/ESDG +continuer/M +continuity/SEM +continuous/YE +continuousness/M +continuum/M +contort/VGD +contortion/MS +contortionist/SM +contour +contra/S +contraband/SM +contrabass/M +contraception/SM +contraceptive/S +contract/DG +contractible +contractile +contractual/Y +contradict/GDS +contradiction/MS +contradictorily +contradictoriness/M +contradictory/PS +contradistinction/MS +contraflow/S +contrail/M +contraindicate/SDVNGX +contraindication/M +contralto/SM +contrapositive/S +contraption/MS +contrapuntal/Y +contrariety/MS +contrarily +contrariness/MS +contrariwise +contrary/PS +contrast/SRDVGZ +contrasting/Y +contrastive/Y +contravene/GSRD +contravener/M +contravention/MS +contretemps/M +contribute/XVNZRD +contribution/M +contributive/Y +contributor/SM +contributorily +contributory/S +contrite/NXP +contriteness/M +contrition/M +contrivance/SM +contrive/ZGRSD +contriver/M +control's +control/CS +controllability/M +controllable/IU +controllably/U +controlled/CU +controller/SM +controlling/C +controversial/UY +controversialists +controversy/MS +controvert/DGS +controvertible/I +contumacious/Y +contumacy/MS +contumelious +contumely/MS +contuse/NGXSD +contusion/M +conundrum/SM +conurbation/MS +convalesce/GDS +convalescence/SM +convalescent/S +convect/DSVG +convection/MS +convectional +convector +convene/ASDG +convener/MS +convenience/ISM +convenient/IY +conventicle/SM +convention/MA +conventional/UY +conventionalism/M +conventionalist/M +conventionality/SUM +conventionalize/GDS +conventions +convergence/MS +convergent +conversant/Y +conversation/SM +conversational/Y +conversationalist/SM +conversazione/M +converse/Y +conversion/AM +conversioning +convert/GADS +converted/U +converter/MS +convertibility's/I +convertibility/SM +convertible/PS +convertibleness/M +convex/Y +convexity/MS +convey/BDGS +conveyance/DRSGMZ +conveyancer/M +conveyancing/M +conveyor/MS +convict/SVGD +conviction/MS +convince/RSDZG +convinced/U +convincer/M +convincing/PUY +convincingness/M +convivial/Y +conviviality/MS +convoke/GSD +convolute/XDNY +convolution/M +convolve/C +convolved +convolves +convolving +convoy/GMDS +convulse/SDXVNG +convulsion/M +convulsive/YP +convulsiveness/M +cony/SM +coo/GSD +cook/GZDRMJS +cookbook/SM +cooked/AU +cooker/M +cookery/MS +cookie/SM +cooking/M +cookout/SM +cooks/A +cookware/SM +cooky's +cool/YDRPJGZTS +coolant/SM +cooled/U +cooler/M +coolheaded +coolie/MS +coolness/MS +coon/MS! +coonskin/MS +coop/MDRGZS +cooper/GDM +cooperage/MS +cooperate/VNGXSD +cooperation/M +cooperative/PSY +cooperativeness/SM +cooperator/MS +coordinate/XNGVYPDS +coordinated/U +coordinateness/M +coordination/M +coordinator/MS +coot/MS +cootie/SM +cop/SJMDRG +copay/S +cope/S +coper/M +copied/A +copier/M +copies/A +copilot/SM +coping/M +copious/YP +copiousness/SM +coplanar +copolymer/MS +copora +copped +copper/MSGD +copperhead/MS +copperplate/MS +coppersmith/M +coppersmiths +coppery +coppice's +copping +copra/MS +coprolite/M +coprophagous +cops/GDS +copse/M +copter/SM +copula/MS +copulate/XDSNGV +copulation/M +copulative/S +copy/MZBDSRG +copybook/MS +copycat/SM +copycatted +copycatting +copyist/SM +copyright/MSRDGZ +copyrighter/M +copywriter/MS +coquetry/MS +coquette/DSMG +coquettish/Y +coracle/SM +coral/SM +coralline +corbel/GMDJS +cord/FSAEM +cordage/MS +corded/AE +corder/AM +cordial/PYS +cordiality/MS +cordialness/M +cordillera/MS +cording/MA +cordite/MS +cordless +cordon/DMSG +cordovan/SM +corduroy/GDMS +core/MZGDRS +cored/A +corer/M +corespondent/MS +corgi/MS +coriander/SM +coring/M +cork/GZDRMS +corked/U +corker/M +corks/U +corkscrew/DMGS +corm/MS +cormorant/MS +corn/GZDRMS +cornball/SM +cornbread/S +corncob/SM +corncrake/M +cornea/SM +corneal +corner/GDM +cornerstone/MS +cornet/SM +cornfield/SM +cornflake/S +cornflour/M +cornflower/SM +cornice/GSDM +cornily +corniness/S +cornmeal/S +cornrow/GDS +cornstalk/MS +cornstarch/SM +cornucopia/MS +corny/RPT +corolla/MS +corollary/SM +corona/SM +coronal/MS +coronary/S +coronate/NX +coronation/M +coroner/MS +coronet/DMS +coroutine/SM +corp/S +corpora/MS +corporal/SYM +corporate/INVXS +corporately +corporation/MI +corporatism/M +corporatist +corporeal/IY +corporeality/MS +corporealness/M +corps/SM +corpse/M +corpsman/M +corpsmen +corpulence/MS +corpulent/YP +corpulentness/S +corpus/M +corpuscle/SM +corpuscular +corr +corral/MS +corralled +corralling +correct/BPSDRYTGV +correctable/U +corrected/U +correction/MS +correctional +corrective/YPS +correctly/I +correctness/MSI +corrector/MS +correlate/SDXVNG +correlated/U +correlation/M +correlative/YS +correspond/DSG +correspondence/MS +correspondent/SM +corresponding/Y +corridor/SM +corrigenda +corrigendum/M +corrigible/I +corroborate/GNVXDS +corroborated/U +corroboration/M +corroborative/Y +corroborator/MS +corroboratory +corrode/SDG +corrodible +corrosion/SM +corrosive/YPS +corrosiveness/M +corrugate/NGXSD +corrugation/M +corrupt/DRYPTSGV +corrupted/U +corrupter/M +corruptibility/SMI +corruptible/I +corruption/IM +corruptions +corruptive/Y +corruptness/MS +corsage/MS +corsair/SM +corset/GMDS +cortex/M +cortical/Y +cortices +corticosteroid/SM +cortisone/SM +cortège/MS +corundum/MS +coruscate/XSDGN +coruscation/M +corvette/MS +cos/GDS +cosign/SRDZG +cosignatory/MS +cosily +cosine/MS +cosiness/MS +cosmetic/SM +cosmetically +cosmetician/MS +cosmetologist/MS +cosmetology/MS +cosmic +cosmical/Y +cosmogonist/MS +cosmogony/SM +cosmological/Y +cosmologist/MS +cosmology/SM +cosmonaut/MS +cosmopolitan/SM +cosmopolitanism/MS +cosmos/SM +cosponsor/DSG +cossack/S +cosset/GDS +cost/MYGVJS +costar/S +costarred +costarring +costive/PY +costiveness/M +costless +costliness/SM +costly/RTP +costume/ZMGSRD +costumer/M +cot/SGMD +cotangent/SM +cote/MS +coterie/MS +coterminous/Y +cotillion/SM +cottage/ZMGSRD +cottager/M +cottar's +cotted +cotter/SDM +cotton/GSDM +cottonmouth/M +cottonmouths +cottonseed/MS +cottontail/SM +cottonwood/SM +cottony +cotyledon/MS +couch/MSDG +couching/M +cougar/MS +cough/RDG +cougher/M +coughs +could've +could/T +couldn't +coulomb/SM +coulée/MS +council/SM +councilman/M +councilmen +councilor/MS +councilperson/S +councilwoman/M +councilwomen +counsel/GSDM +counsellings +counselor/MS +count/EGARDS +countability/E +countable/U +countably/U +countdown/SM +counted/U +countenance's +countenance/EGDS +countenancer/M +counter's/E +counter/GSMD +counteract/DSVG +counteraction/SM +counterargument/SM +counterattack/DRMGS +counterbalance/MSDG +counterclaim/GSDM +counterclockwise +counterculture/MS +countercyclical +counterespionage/MS +counterexample/S +counterfeit/ZSGRD +counterfeiter/M +counterflow +counterfoil/MS +counterforce/M +counterinsurgency/MS +counterintelligence/MS +counterintuitive +counterman/M +countermand/DSG +countermeasure/SM +countermen +counteroffensive/SM +counteroffer/SM +counterpane/SM +counterpart/SM +counterpoint/GSDM +counterpoise/GMSD +counterproductive +counterproposal/M +counterrevolution/MS +counterrevolutionary/MS +counters/E +countersign/SDG +countersignature/MS +countersink/SG +counterspy/MS +counterstrike +countersunk +countertenor/SM +countervail/DSG +counterweight/GMDS +countess/MS +countless/Y +countrify/D +country/MS +countryman/M +countrymen +countryside/MS +countrywide +countrywoman/M +countrywomen +county/SM +coup's +coup/ASDG +coupe/MS +couple's +couple/ACU +coupled/CU +coupler's +coupler/C +couplers +couples/CU +couplet/SM +coupling's/C +coupling/SM +coupon/SM +courage/MS +courageous/U +courageously +courageousness/MS +courages/E +courgette/MS +courier/GMDS +course's/AF +course/EGSRDM +courser's/E +courser/SM +courses/FA +coursework +coursing/M +court/GZMYRDS +courteous/PEY +courteousness/EM +courteousnesses +courtesan/MS +courtesied +courtesy/ESM +courtesying +courthouse/MS +courtier/SM +courtliness/MS +courtly/RTP +courtroom/MS +courtship/SM +courtyard/SM +couscous/MS +cousin/YMS +cousinly/U +couture/SM +couturier/SM +covalent/Y +covariance/SM +covariant/S +covariate/SN +covary +cove/DRSMZG +coven/SM +covenant/SGRDM +covenanted/U +covenanter/M +cover/AEGUDS +coverable/E +coverage/MS +coverall/DMS +coverer/AME +covering/MS +coverlet/MS +covers/M +coversheet +covert/YPS +covertness/SM +covet/SGRD +coveter/M +coveting/Y +covetous/PY +covetousness/SM +covey/SM +covington +cow/MDRSZG +coward/MYS +cowardice/MS +cowardliness/MS +cowardly/P +cowbell/MS +cowbird/MS +cowboy/MS +cowcatcher/SM +cowed/Y +cower/RDGZ +cowering/Y +cowgirl/MS +cowhand/S +cowherd/SM +cowhide/MGSD +cowl/SGMD +cowlick/MS +cowling/M +cowman/M +cowmen +coworker/MS +cowpoke/MS +cowpony +cowpox/MS +cowpunch/RZ +cowpuncher/M +cowrie/SM +cowshed/SM +cowslip/MS +cox/MDSG +coxcomb/MS +coxswain/GSMD +coy/CDSG +coyer +coyest +coyly +coyness/MS +coyote/SM +coypu/SM +cozen/SGD +cozenage/MS +cozily +coziness/MS +cozy/DSRTPG +cpd +cpl +cps +crab/MS +crabapple +crabbed/YP +crabbedness/M +crabber/MS +crabbily +crabbiness/S +crabbing/M +crabby/PRT +crabgrass/S +crablike +crack/ZSBYRDG +crackable/U +crackdown/MS +cracker/M +crackerjack/S +crackle/GJDS +crackling/M +crackly/RT +crackpot/SM +crackup/S +cradle/SRDGM +cradler/M +cradling/M +craft/MRDSG +craftily +craftiness/SM +craftsman/M +craftsmanship/SM +craftsmen +craftspeople +craftspersons +craftswoman +craftswomen +crafty/TRP +crag/SM +cragginess/SM +craggy/RTP +cram/S +crammed +crammer/M +cramming +cramp/MRDGS +cramper/M +crampon/SM +cranberry/SM +crane/DSGM +cranelike +cranial +cranium/MS +crank/SGTRDM +crankcase/MS +crankily +crankiness/MS +crankshaft/MS +cranky/TRP +cranny/DSGM +crap/SMDG! +crape/SM +crapped +crappie/M +crapping +crappy/RST +crapshooter/SM +crash/SRDGZ +crasher/M +crashing/Y +crass/TYRP +crassness/MS +crate/DSRGMZ +crater/DMG +cravat/SM +cravatted +cravatting +crave/DSRGJ +craven/SPYDG +cravenness/SM +craver/M +craving/M +craw/SYM +crawdad/S +crawfish's +crawl/RDSGZ +crawler/M +crawlspace/S +crawlway +crawly/TRS +crayfish/GSDM +crayon/GSDM +craze/GMDS +crazily +craziness/MS +crazy/SRTP +creak/SDG +creakily +creakiness/SM +creaky/PTR +cream/SMRDGZ +creamer/M +creamery/MS +creamily +creaminess/SM +creamy/TRP +crease's +crease/IDRSG +creased/CU +creases/C +creasing/C +create/XKVNGADS +created/U +creation/MAK +creationism/MS +creationist/MS +creative/YP +creativeness/SM +creativities +creativity's +creativity/K +creator/MS +creature/YMS +creatureliness/M +creaturely/P +credence/MS +credent +credential/SGMD +credenza/SM +credibility/IMS +credible/I +credibly/I +credit's +credit/EGBSD +creditability/M +creditable/P +creditableness/M +creditably/E +credited/U +creditor/MS +creditworthiness +credo/SM +credulity/ISM +credulous/IY +credulousness/SM +creed's +creed/C +creedal +creeds +creek/SM +creekside +creel/SMDG +creep/SGZR +creeper/M +creepily +creepiness/SM +creepy/PRST +cremate/XDSNG +cremation/M +crematoria +crematorium/MS +crematory/S +creme/S +crenelate/XGNSD +crenelation/M +creole/SM +creosote/MGDS +crepe/DSGM +crept +crescendo/SCM +crescendoed +crescendoing +crescent/MS +cress/S +crest/SGMD +crestfallen/PY +crestfallenness/M +cresting/M +crestless +cretaceous +cretin/MS +cretinism/MS +cretinous +cretonne/SM +crevasse/DSMG +crevice/SM +crew/DMGS +crewel/SM +crewelwork/SM +crewman/M +crewmen +crib/SM +cribbage/SM +cribbed +cribber/SM +cribbing/M +crick/GDSM +cricket/SMZRDG +cricketer/M +cried/C +crier/CM +cries/C +crime/GMDS +criminal/SYM +criminality/MS +criminalization/C +criminalize/GC +criminologist/SM +criminology/MS +crimp/RDGS +crimper/M +crimson/DMSG +cringe/SRDG +cringer/M +crinkle/DSG +crinkly/TRS +crinoline/SM +cripple/GMZDRS +crippler/M +crippling/Y +crises +crisis/M +crisp/PGTYRDS +crisper/M +crispiness/SM +crispness/MS +crispy/RPT +criss +crisscross/GDS +criteria +criterion/M +critic/MS +critical/YP +criticality +critically/U +criticalness/M +criticism/MS +criticize/GSRDZ +criticized/U +criticizer/M +criticizes/A +criticizing/UY +criticizingly/S +critique/MGSD +critter/SM +croak/SRDGZ +croaker/M +croaky/RT +crochet/RDSZJG +crocheter/M +crock/SGRDM +crockery/SM +crocodile/MS +crocus/SM +croft/MRGZS +crofter/M +croissant/MS +crone/SM +crony/SM +crook/SGDM +crooked/TPRY +crookedness/SM +crookneck/MS +croon/SRDGZ +crooner/M +crop/MS +cropland/MS +cropped +cropper/SM +cropping +croquet/MDSG +croquette/SM +crosier/SM +cross/ZTYSRDMPBJG +crossarm +crossbar/SM +crossbarred +crossbarring +crossbeam/MS +crossbones +crossbow/SM +crossbowman/M +crossbowmen +crossbred/S +crossbreed/SG +crosscheck/SGD +crosscurrent/SM +crosscut/SM +crosscutting +crossed/UA +crosses/UA +crossfire/SM +crosshatch/GDS +crossing/M +crossness/MS +crossover/MS +crosspatch/MS +crosspiece/SM +crosspoint +crossproduct/S +crossroad/GSM +crossroads/M +crosstalk/M +crosstown +crosswalk/MS +crossway/M +crosswind/SM +crosswise +crossword/MS +crotch/MDS +crotchet/MS +crotchetiness/M +crotchety/P +crotchless +crouch/DSG +croup/SMDG +croupier/M +croupy/TZR +crow/GDMS +crowbait +crowbar/SM +crowbarred +crowbarring +crowd/MRDSG +crowded/P +crowdedness/M +crowfeet +crowfoot/M +crown/RDMSJG +crowned/U +crowner/M +crozier's +croûton/MS +crucial/Y +crucible/MS +crucifiable +crucifix/SM +crucifixion/MS +cruciform/S +crucify/NGDS +crud/STMR +crudded +crudding +cruddy/TR +crude/YSP +crudeness/MS +crudity/MS +crudités +cruel/YRTSP +cruelness/MS +cruelty/SM +cruet/MS +cruft +crufty +cruise/GZSRD +cruiser/M +cruller/SM +crumb/GSYDM +crumble/DSJG +crumbliness/MS +crumbly/PTRS +crumby/RT +crumminess/S +crummy/SRTP +crump +crumpet/SM +crumple/DSG +crunch/DSRGZ +crunchiness/MS +crunchy/TRP +crupper/MS +crusade/GDSRMZ +crusader/M +cruse/MS +crush/SRDBGZ +crushable/U +crusher/M +crushing/Y +crushproof +crust/GMDS +crustacean/MS +crustal +crustily +crustiness/SM +crusty/SRTP +crutch/MDSG +crux/MS +cry/JGDRSZ +crybaby/MS +cryogenic/S +cryogenics/M +cryostat/M +cryosurgery/SM +crypt's +crypt/CS +cryptanalysis/M +cryptanalyst/M +cryptanalytic +cryptic +cryptically +cryptogram/MS +cryptographer/MS +cryptographic +cryptographically +cryptography/MS +cryptologic +cryptological +cryptologist/M +cryptology/M +crystal/SM +crystalline/S +crystallite/SM +crystallization/AMS +crystallize/SRDZG +crystallized/UA +crystallizes/A +crystallizing/A +crystallographer/MS +crystallographic +crystallography/M +crèche/SM +cs's +cs/EA +ct +ctn +ctr +cu/DG +cub/MDRSZG +cubbed +cubbing +cubbyhole/MS +cube/SM +cuber/M +cubic/YS +cubical/Y +cubicle/SM +cubism/SM +cubist/MS +cubit/MS +cuboid +cuckold/GSDM +cuckoldry/MS +cuckoo/SGDM +cucumber/MS +cud/MS +cuddle/GSD +cuddly/TRP +cudgel/GSJMD +cue/MS +cuff/GSDM +cuisine/MS +culinary +cull/DRGS +cullender's +culler/M +culminate/XSDGN +culmination/M +culotte/S +culpa/SM +culpability/MS +culpable/I +culpableness/M +culpably +culprit/SM +cult/MS +cultism/SM +cultist/SM +cultivable +cultivate/XBSDGN +cultivated/U +cultivation/M +cultivator/SM +cultural/Y +culture/SDGM +cultured/U +culvert/SM +cum/S +cumber/DSG +cumbersome/YP +cumbersomeness/MS +cumbrous +cumin/MS +cummerbund/MS +cumquat's +cumulate/XVNGSD +cumulation/M +cumulative/Y +cumuli +cumulonimbi +cumulonimbus/M +cumulus/M +cuneiform/S +cunnilingus/SM +cunning/RYSPT +cunningness/M +cunt/SM! +cup/MS +cupboard/SM +cupcake/SM +cupful/SM +cupid/S +cupidinously +cupidity/MS +cupola/MDGS +cupped +cupping/M +cupric +cuprous +cur's +cur/IBS +curability/MS +curable/IP +curableness/MI +curably/I +curacy/SM +curare/MS +curate/VGMSD +curative/YS +curator/KMS +curatorial +curb/SJDMG +curbing/M +curbside +curbstone/MS +curd/SMDG +curdle/SDG +cure/KBDRSGZ +cured/U +curer/MK +curettage/SM +curfew/SM +curfs +curia/M +curiae +curie/SM +curio/SM +curiosity/SM +curious/TPRY +curiousness/SM +curium/MS +curl/UDSG +curler/SM +curlew/MS +curlicue/MGDS +curliness/SM +curling/M +curly/PRT +curlycue's +curmudgeon/MYS +currant/SM +curred/AFI +currency's +currency/SF +current/FSY +currently/A +currentness/M +curricle/M +curricula +curricular +curriculum/M +currier/M +curring/FAI +curry/RSDMG +currycomb/DMGS +curs/ASDVG +curse's +curse/A +cursed/YRPT +cursedness/M +cursive/EPYA +cursiveness/EM +cursives +cursor/DMSG +cursorily +cursoriness/SM +cursory/P +curt/TYRP +curtail/LSGDR +curtailer/M +curtailment/SM +curtain/GSMD +curtness/MS +curtsey's +curtsy/SDMG +curvaceous/YP +curvaceousness/S +curvature/MS +curve/DSGM +curved's +curved/A +curvilinear/Y +curvilinearity/M +curving/M +curvy/RT +cushion/SMDG +cushy/TR +cusp/MS +cuspid/MS +cuspidor/MS +cuss's +cuss/EGDSR +cussed/YP +cussedness/M +cusses/F +cussing/F +custard/MS +custodial +custodian/SM +custodianship/MS +custody/MS +custom/SMRZ +customarily +customariness/M +customary/PS +customer/M +customhouse/S +customization/SM +customize/ZGBSRD +cut/MRST +cutaneous/Y +cutaway/SM +cutback/SM +cute/SPY +cuteness/MS +cutesy/RT +cuticle/SM +cutlass/MS +cutler/SM +cutlery/MS +cutlet/SM +cutoff/MS +cutout/SM +cutter/SM +cutthroat/SM +cutting/MYS +cuttle/M +cuttlebone/SM +cuttlefish/MS +cutup/MS +cutworm/MS +cw +cwt +cyan/MS +cyanate/M +cyanic +cyanide/GMSD +cyanogen/M +cybernetic/S +cybernetics/M +cyberpunk/S +cyberspace/S +cyborg/S +cyclamen/MS +cycle's +cycle/ASDG +cycler +cycleway/S +cyclic +cyclical/SY +cycling/M +cyclist/MS +cyclohexanol +cycloid/SM +cycloidal +cyclometer/MS +cyclone/SM +cyclonic +cyclopean +cyclopedia/MS +cyclopes +cyclops +cyclotron/MS +cyder/SM +cygnet/MS +cylinder/GMDS +cylindric +cylindrical/Y +cymbal/SM +cymbalist/MS +cynic/MS +cynical/UY +cynicism/MS +cynosure/SM +cypher/MGSD +cypreses +cypress/SM +cyst/MS +cystic +cytochemistry/M +cytochrome/M +cytologist/MS +cytology/MS +cytolysis/M +cytoplasm/SM +cytoplasmic +cytosine/MS +cytotoxic +czar/SM +czarevitch/M +czarina/SM +czarism/M +czarist/S +czarship +d'Arezzo +d'Estaing +d'art +d'etat +d'etre +d'oeuvre +d's/A +d/JGVX +dB/M +dab/S +dabbed +dabber/MS +dabbing +dabble/RSDZG +dabbler/M +dace/MS +dacha/SM +dachshund/SM +dactyl/MS +dactylic/S +dad/SM +dadaism/S +dadaist/S +daddy/SM +dado/DMG +dadoes +daemon/SM +daemonic +daffiness/S +daffodil/MS +daffy/PTR +daft/TYRP +daftness/MS +dagger/DMSG +daguerreotype/MGDS +dahlia/MS +dailiness/MS +daily/PS +daintily +daintiness/MS +dainty/TPRS +daiquiri/SM +dairy/MJGS +dairying/M +dairyland +dairymaid/SM +dairyman/M +dairymen +dairywoman/M +dairywomen +dais/SM +daisy/SM +dale/SMH +daleth/M +dalliance/SM +dallier/M +dally/ZRSDG +dalmatian/S +dam/MDS +damage/MZGRSD +damageable +damaged/U +damager/M +damaging/Y +damask/DMGS +dame/SM +dammed +damming +dammit/S +damn/GSBRD +damnably +damnation/MS +damned/TR +damnedest/MS +damning/Y +damp/SGZTXYRDNP +damped/U +dampen/RDZG +dampener/M +damper/M +dampness/MS +damsel/MS +damselfly/MS +damson/MS +dance/SRDJGZ +dancelike +dancer/M +dandelion/MS +dander/DMGS +dandify/SDG +dandily +dandle/GSD +dandruff/MS +dandy/TRSM +dang/SGZRD +danger/DMG +dangerous/YP +dangerousness/M +dangle/ZGRSD +dangler/M +dangling/Y +danish/S +dank/TPYR +dankness/MS +danseuse/SM +dapper/PSTRY +dapperness/M +dapple/SDG +dare/ZGDRSJ +daredevil/MS +daredevilry/S +darer/M +daresay +daring/PY +daringness/M +dark/GTXYRDNSP +darken/RDZG +darkener/M +darkish +darkly/TR +darkness/MS +darkroom/SM +darling/YMSP +darlingness/M +darn/GRDZS +darned/TR +darner/M +darning/M +dart/MRDGZS +dartboard/SM +darter/M +dash/GZSRD +dashboard/SM +dasher/M +dashiki/SM +dashing/Y +dastard/MYS +dastardliness/SM +dastardly/P +data/M +database/DSMG +datafile +datagram/MS +dataset/S +date/DRSMZGV +dated/U +datedly +datedness +dateless +dateline/DSMG +dater/M +dative/S +datum/MS +daub/RDSGZ +dauber/M +daughter/MYS +daunt/DSG +daunted/U +daunting/Y +dauntless/PY +dauntlessness/SM +dauphin/SM +davenport/MS +davit/SM +dawdle/ZGRSD +dawdler/M +dawn/GSDM +day/SM +daybed/S +daybreak/SM +daycare/S +daydream/RDMSZG +daydreamer/M +daylight/GSDM +daysack +daytime/SM +daze/DSG +dazed/PY +dazzle/ZGJRSD +dazzler/M +dazzling/Y +db +dbl +deacon/DSMG +deaconess/MS +dead/PTXYRN +deadbeat/SM +deadbolt/S +deaden/RDG +deadener/M +deadening/MY +deadhead/MS +deadline/MGDS +deadliness/SM +deadlock/MGDS +deadly/RPT +deadness/M +deadpan/S +deadpanned +deadpanner +deadpanning +deadwood/SM +deaf/TXPYRN +deafen/JGD +deafening/MY +deafness/MS +deal/RSGZJ +dealer/M +dealership/MS +dealing/M +deallocator +dealt +dean/DMG +deanery/MS +deanship/SM +dear/TYRHPS +dearness/MS +dearth/M +dearths +deary/MS +deassign +death/MY +deathbed/MS +deathblow/SM +deathless/Y +deathlike +deathly/TR +deaths +deathtrap/SM +deathward +deathwatch/MS +deb/MS +debacle/SM +debar/L +debark/G +debarkation/SM +debarment/SM +debarring +debaser/M +debatable/U +debate/BMZ +debater/M +debauch/GDRS +debauched/PY +debauchedness/M +debauchee/SM +debaucher/M +debauchery/SM +debenture/MS +debilitate/NGXSD +debilitation/M +debility/MS +debit/DG +debonair/PY +debonairness/SM +debouch/DSG +debrief/GJ +debris/M +debt/SM +debtor/SM +debut/MDG +decade/MS +decadency/S +decadent/YS +decaf/S +decaffeinate/DSG +decagon/MS +decal/SM +decamp/L +decampment/MS +decapitate/GSD +decapitator/SM +decathlon/SM +decay/GRD +decease/M +decedent/MS +deceit/SM +deceitful/PY +deceitfulness/SM +deceive/ZGRSD +deceived/U +deceiver/M +deceives/U +deceiving/U +deceivingly +decelerate/XNGSD +deceleration/M +decelerator/SM +decency/ISM +decennial/SY +decent/TIYR +deception/SM +deceptive/YP +deceptiveness/SM +decertify/N +dechlorinate/N +decibel/MS +decidability/U +decidable/U +decide/GRSDB +decided/PY +decidedness/M +deciduous/YP +deciduousness/M +decile/SM +deciliter/SM +decimal/SYM +decimate/XNGDS +decimation/M +decimeter/MS +decipher/BRZG +decipherable/IU +decipherer/M +decision/ISM +decisional +decisioned +decisioning +decisive/IPY +decisiveness/MSI +deck/GRDMSJ +deckchair +decker/M +deckhand/S +decking/M +declamation/SM +declamatory +declarable +declaration's/A +declaration/MS +declarative/SY +declarator/MS +declaratory +declare/AGSD +declared/U +declarer/MS +declension/SM +declination/MS +decline/ZGRSD +decliner/M +declivity/SM +deco +decolletes +decolorising +decomposability/M +decomposable/IU +decompose/B +decompress/R +decongestant/S +deconstruction +deconvolution +decor/S +decorate/NGVDSX +decorated/AU +decorates/A +decorating/A +decoration/ASM +decorative/YP +decorativeness/M +decorator/SM +decorous/PIY +decorousness's/I +decorousness/MS +decorticate/GNDS +decortication/M +decorum/MS +decoupage/MGSD +decouple/G +decoy/M +decrease +decreasing/Y +decree/RSM +decreeing +decrement/DMGS +decremental +decrepit +decrepitude/SM +decriminalization/S +decriminalize/DS +decry/G +decrypt/GD +decryption +decustomised +dedicate/AGDS +dedicated/Y +dedication/MS +dedicative +dedicator/MS +dedicatory +deduce/RSDG +deducible +deduct/VG +deductibility/M +deductible/S +deduction/SM +deductive/Y +deed's +deed/IS +deeded +deeding +deejay/MDSG +deem/ADGS +deemphasis +deep/PTXSYRN +deepen/DG +deepish +deepness/MS +deer/SM +deerskin/MS +deerstalker/SM +deerstalking/M +def/Z +deface/LZ +defacement/SM +defaecate +defalcate/NGXSD +defalcation/M +defamation/SM +defamatory +defame/ZR +defamer/M +default/ZR +defaulter/M +defeat/ZGD +defeated/U +defeater/M +defeatism/SM +defeatist/SM +defecate/DSNGX +defecation/M +defect/MDSVG +defection/SM +defective/PYS +defectiveness/MS +defector/MS +defendant/SM +defended/U +defenestrate/GSD +defense/VGSDM +defenseless/PY +defenselessness/MS +defenses/U +defensibility/M +defensible/I +defensibly/I +defensive/PSY +defensiveness/MS +deference/MS +deferent/S +deferential/Y +deferrable +deferral/SM +deferred +deferrer/MS +deferring +deffer +defiance/MS +defiant/Y +defibrillator/M +deficiency/MS +deficient/SY +deficit/MS +defier/M +defile/L +defilement/MS +definable/UI +definably/I +define/AGDRS +defined/U +definer/SM +definite/IPY +definiteness/IMS +definition/ASM +definitional +definitive/SYP +definitiveness/M +defis +deflate/XNGRSDB +deflation/M +deflationary +deflect/DSGV +deflected/U +deflection/MS +deflector/MS +defocus +defocussing +defog +defogger/S +defoliant/SM +defoliator/SM +deform/B +deformational +deformed/U +deformity/SM +defraud/ZGDR +defrauder/M +defrayal/SM +defrost/RZ +defroster/M +deft/TYRP +deftness/MS +defunct/S +defy/RDG +defying/Y +deg +degassing +degauss/GD +degeneracy/MS +degenerate/PY +degenerateness/M +degrade/B +degraded/YP +degradedness/M +degrading/Y +degrease +degree/SM +degum +dehumanize +dehydrator/MS +deice/ZR +deicer/M +deictic +deification/M +deify/SDXGN +deign/DGS +deist/SM +deistic +deity/SM +deja +deject/DSG +dejected/PY +dejectedness/M +dejection/SM +delay/D +delayer/G +delectable/SP +delectableness/M +delectably +delectation/MS +delegable +delete/XBRSDNG +deleted/U +deleterious/PY +deleteriousness/M +deletion/M +delfs +delft/MS +delftware/S +deli/SM +deliberate/PVY +deliberateness/SM +deliberative/PY +deliberativeness/M +delicacy/IMS +delicate/IYP +delicateness/IM +delicatenesses +delicates +delicatessen/MS +delicious/YSP +deliciousness/MS +delicti +delighted/YP +delightedness/M +delightful/YP +delightfulness/M +delineate/SDXVNG +delineation/M +delinquency/MS +delinquent/SYM +deliquesce/GSD +deliquescent +delirious/PY +deliriousness/MS +delirium/SM +deliver/AGSD +deliverable/U +deliverables +deliverance/SM +delivered/U +deliverer/SM +delivery/AM +deliverymen/M +dell/SM +delphinium/SM +delta/MS +deltoid/SM +delude/RSDG +deluder/M +deluding/Y +deluge/SDG +delusion/SM +delusional +delusive/PY +delusiveness/M +deluxe +delve/GZSRD +delver/M +demagnify/N +demagogic +demagogue/GSDM +demagoguery/SM +demagogy/MS +demand/GSRD +demander/M +demanding/U +demandingly +demarcate/SDNGX +demarcation/M +demean/GDS +demeanor/SM +demented/YP +dementedness/M +dementia/MS +demesne/SM +demigod/MS +demijohn/MS +demimondaine/SM +demimonde/SM +demineralization/SM +demise/DMG +demit +demitasse/MS +demitted +demitting +demo/DMPG +democracy/MS +democrat/SM +democratic/U +democratically/U +democratization/MS +democratize/DRSG +democratizes/U +demographer/MS +demographic/S +demographical/Y +demography/MS +demolish/GSRD +demolisher/M +demolition/MS +demon/SM +demonetization/S +demoniac/S +demoniacal/Y +demonic +demonology/M +demonstrable/I +demonstrableness/M +demonstrably/I +demonstrate/XDSNGV +demonstration/M +demonstrative/YUP +demonstrativeness/UM +demonstrativenesses +demonstratives +demonstrator/MS +demoralization/M +demoralizer/M +demoralizing/Y +demote/DGX +demotic/S +demount/B +demulcent/S +demultiplex +demur/RTS +demure/YP +demureness/SM +demurral/MS +demurred +demurrer/MS +demurring +demythologization/M +demythologize/R +den +dendrite/MS +dengue/MS +deniable/U +denial/SM +denier/M +denigrate/VNGXSD +denigration/M +denim/SM +denizen/SMDG +denned +denning +denominate/V +denominational/Y +denote/B +denouement/MS +denounce/LZRSDG +denouncement/SM +denouncer/M +dens/RT +dense/FR +densely +denseness/SM +densitometer/MS +densitometric +densitometry/M +density/MS +dent's +dent/ISGD +dental/YS +dentifrice/SM +dentin/SM +dentine's +dentist/SM +dentistry/MS +dentition/MS +denture/IMS +denuclearize/GSD +denudation/SM +denude/DG +denuder/M +denunciate/VNGSDX +denunciation/M +deny/SRDZG +denying/Y +deodorant/SM +deodorization/SM +deodorize/GZSRD +deodorizer/M +deoxyribonucleic +depart/L +department/MS +departmental/Y +departmentalization/SM +departmentalize/DSG +departure/MS +depend/B +dependability/MS +dependable/P +dependableness/M +dependably +dependence/ISM +dependency/MS +dependent's +dependent/IYS +depict/RDSG +depicted/U +depicter/M +depiction/SM +depilatory/S +deplete/VGNSDX +depletion/M +deplorable/P +deplorableness/M +deplorably +deplore/SRDBG +deplorer/M +deploring/Y +deploy/AGDLS +deployable +deployment/SAM +depolarize +deponent/S +deport/LG +deportation/MS +deportee/SM +deportment/MS +depose +deposit/ADGS +depositary/M +deposition/A +depositor/SAM +depository/MS +deprave/GSRD +depraved/PY +depravedness/M +depraver/M +depravity/SM +deprecate/XSDNG +deprecating/Y +deprecation/M +deprecatory +depreciable +depreciate/XDSNGV +depreciating/Y +depreciation/M +depreciative/Y +depress/V +depressant/S +depressible +depression/MS +depressive/YS +depressor/MS +deprive/GSD +depth/M +depths +deputation/SM +depute/SDG +deputize/DSG +deputy/MS +dequeue +derail/L +derailment/MS +derange/L +derangement/MS +derby/SM +dereference/Z +derelict/S +dereliction/SM +deride/D +deriding/Y +derision/SM +derisive/PY +derisiveness/MS +derisory +derivable/U +derivate/XNV +derivation/M +derivative/SPYM +derivativeness/M +derive/B +derived/U +dermal +dermatitides +dermatitis/MS +dermatological +dermatologist/MS +dermatology/MS +dermis/SM +derogate/XDSNGV +derogation/M +derogatorily +derogatory +derrick/SMDG +derringer/SM +derrière/S +dervish/SM +desalinate/NGSDX +desalination/M +desalinization/MS +desalinize/GSD +desalt/G +descant/M +descend/ZGSDR +descendant/SM +descended/FU +descendent's +descender/M +descending/F +descends/F +descent +describable/I +describe/ZB +description/MS +descriptive/SYP +descriptiveness/MS +descriptor/SM +descry/SDG +desecrate/SRDGNX +desecrater/M +desecration/M +desert/ZGMRDS +deserter/M +desertification +desertion/MS +deserve/J +deserved/YU +deservedness/M +deserving/Y +desiccant/S +desiccate/XNGSD +desiccation/M +desiccator/SM +desiderata +desideratum/M +design/ADGS +designable +designate/VNGSDX +designation/M +designational +designator/SM +designed/Y +designer/M +designing/U +desirabilia +desirability's +desirability/US +desirable/UPS +desirableness's/U +desirableness/SM +desirably/U +desire/BR +desired/U +desirer/M +desirous/PY +desirousness/M +desist/DSG +desk/SM +desktop/S +desolate/PXDRSYNG +desolateness/SM +desolater/M +desolating/Y +desolation/M +desorption/M +despair/SGDR +despairer/M +despairing/Y +desperado/M +desperadoes +desperate/YNXP +desperateness/SM +desperation/M +despicable +despicably +despise/SRDG +despiser/M +despoil/L +despoilment/MS +despond +despondence/S +despondency/MS +despondent/Y +despotic +despotically +despotism/SM +dessert/SM +dessicate/DN +destinate/NX +destination/M +destine/GSD +destiny/MS +destitute/NXP +destituteness/M +destitution/M +destroy/BZGDRS +destroyer/M +destruct/VGSD +destructibility/SMI +destructible/I +destruction/SM +destructive/YP +destructiveness/MS +destructor/M +desuetude/MS +desultorily +desultoriness/M +desultory/P +detach/LSRDBG +detached/YP +detachedness/M +detacher/M +detachment/SM +detailed/YP +detailedness/M +detain/LGRDS +detainee/S +detainer/M +detainment/MS +detect/DBSVG +detectability/U +detectable/U +detectably/U +detected/U +detection/SM +detective/MS +detector/MS +detentes +detention/SM +deter/SL +detergency/M +detergent/SM +deteriorate/XDSNGV +deterioration/M +determent/SM +determinability/M +determinable/IP +determinableness/IM +determinacy/I +determinant/MS +determinate/PYIN +determinateness/IM +determination/IM +determinative/P +determinativeness/M +determine/GASD +determined/U +determinedly +determinedness/M +determiner/SM +determinism's/I +determinism/MS +deterministic/I +deterministically +deterred/U +deterrence/SM +deterrent/SMY +deterring +deters/V +detersive/S +detestable/P +detestableness/M +detestably +detestation/SM +dethrone/L +dethronement/SM +detonable +detonate/XDSNGV +detonated/U +detonation/M +detonator/MS +detour/G +detox/SDG +detoxification/M +detoxify/NXGSD +detract/GVD +detractive/Y +detribalize/GSD +detriment/SM +detrimental/SY +detritus/M +deuce/SDGM +deuced/Y +deus +deuterium/MS +deuteron/M +devastate/XVNGSD +devastating/Y +devastation/M +devastator/SM +develop/ALZSGDR +developed/U +developer/MA +development/ASM +developmental/Y +deviance/MS +deviancy/S +deviant/YMS +deviate/XSDGN +deviated/U +deviating/U +deviation/M +devil/SLMDG +devilish/PY +devilishness/MS +devilment/SM +devilry/MS +deviltry/MS +devious/YP +deviousness/SM +devise/JR +deviser/M +devoice +devolution/MS +devolve/GSD +devote/XN +devoted/Y +devotee/MS +devotion/M +devotional/YS +devour/SRDZG +devourer/M +devout/PRYT +devoutness/MS +dew/MDGS +dewar +dewberry/MS +dewclaw/SM +dewdrop/MS +dewiness/MS +dewlap/MS +dewy/TPR +dexes/I +dexter +dexterity/MS +dexterous/PY +dexterousness/MS +dextrose/SM +dhoti/SM +dhow/MS +diabase/M +diabetes/M +diabetic/S +diabolic +diabolical/YP +diabolicalness/M +diabolism/M +diachronic/P +diacritic/MS +diacritical/YS +diadem/GMDS +diaereses +diaeresis/M +diagnometer/SM +diagnosable/U +diagnose/BGDS +diagnosed/U +diagnosis/M +diagnostic/MS +diagnostically +diagnostician/SM +diagnostics/M +diagonal/YS +diagonalize/GDSB +diagram/MS +diagrammable +diagrammatic +diagrammaticality +diagrammatically +diagrammed +diagrammer/SM +diagramming +dial/MRDSGZJ +dialect/MS +dialectal/Y +dialectic/MS +dialectical/Y +dialed/A +dialer/M +dialing/M +dialog/MS +dialogged +dialogging +dialogue/DS +dials/A +dialysis/M +dialyzed/U +dialyzes +diam +diamagnetic +diameter/MS +diametric +diametrical/Y +diamond/GSMD +diamondback/SM +diapason/MS +diaper/SGDM +diaphanous/YP +diaphanousness/M +diaphragm/SM +diaphragmatic +diarist/SM +diarrhea/MS +diarrheal +diary/MS +diaspora +diastase/SM +diastole/MS +diastolic +diathermy/SM +diathesis/M +diatom/SM +diatomic +diatonic +diatribe/MS +dibble/SDMG +dibs +dice/GDRS +dicer/M +dicey +dichloride/M +dichotomization/M +dichotomize/DSG +dichotomous/PY +dichotomy/SM +dicier +diciest +dicing/M +dick/GZXRDMS! +dickens/M +dicker/DG +dickey/SM +dickier +dickiest +dicky's +dicotyledon/SM +dicotyledonous +dicta/M +dictate/SDNGX +dictation/M +dictator/MS +dictatorial/YP +dictatorialness/M +dictatorship/SM +diction/MS +dictionary/SM +dictum/M +did/AU +didactic/S +didactically +didactics/M +diddle/ZGRSD +diddler/M +didn't +dido/M +didoes +didst +die/DS +dieing +dielectric/MS +diem +diereses +dieresis/M +dies's +dies/U +diesel/GMDS +diet/RDGZSM +dietary/S +dieter/M +dietetic/S +dietetics/M +diethylaminoethyl +diethylstilbestrol/M +dietitian/MS +differ/SZGRD +difference's/I +difference/DSGM +differences/I +different/YI +differentiability +differentiable +differential/SMY +differentiate/XSDNG +differentiated/U +differentiation/M +differentiator/SM +differentness +difficile +difficult/Y +difficulty/SM +diffidence/MS +diffident/Y +diffract/GSD +diffraction/SM +diffractometer/SM +diffuse/PRSDZYVXNG +diffuseness/MS +diffuser/M +diffusible +diffusion/M +diffusional +diffusive/YP +diffusiveness/M +diffusivity/M +dig/TS +digerati +digest/RDVGS +digested/IU +digester/M +digestibility/MS +digestible/I +digestifs +digestion/ISM +digestive/YSP +digger/MS +digging/S +digit/SM +digital/SY +digitalis/M +digitalization/MS +digitalized +digitalizes +digitalizing +digitization/M +digitize/ZGDRS +digitizer/M +dignified/U +dignify/DSG +dignitary/SM +dignity/ISM +digram +digraph/M +digraphs +digress/GVDS +digression/SM +digressive/PY +digressiveness/M +dihedral +dike/DRSMG +diker/M +diktat/SM +dilapidate/XGNSD +dilapidation/M +dilatation/SM +dilate/XVNGSD +dilated/YP +dilation/M +dilator/SM +dilatoriness/M +dilatory/P +dilemma/MS +dilettante/MS +dilettantish +dilettantism/MS +diligence/SM +diligent/YP +diligentness/M +dilithium +dill/SGMD +dilling/R +dillis +dilly/SM +dillydally/GSD +dilogarithm +diluent +dilute/RSDPXYVNG +diluted/U +diluteness/M +dilution/M +dim/RYPZS +dime/SM +dimension/MDGS +dimensional/Y +dimensionality/M +dimensionless +dimer/M +dimethyl/M +dimethylglyoxime +diminish/SDGBJ +diminished/U +diminuendo/SM +diminution/SM +diminutive/SYP +diminutiveness/M +dimity/MS +dimmed/U +dimmer/MS +dimmest +dimming +dimness/SM +dimorphism/M +dimple/MGSD +dimply/RT +dimwit/MS +dimwitted +din/MDRZGS +dinar/SM +dine/S +diner/M +dinette/MS +ding/GD +dingbat/MS +dinghy/SM +dingily +dinginess/SM +dingle/MS +dingo/MS +dingoes +dingus/SM +dingy/PRST +dinky/RST +dinned +dinner/SM +dinnertime/S +dinnerware/MS +dinning +dinosaur/MS +dint/SGMD +diocesan/S +diocese/SM +diode/SM +diopter/MS +diorama/SM +dioxalate +dioxide/MS +dioxin/S +dip/S +diphtheria/SM +diphthong/SM +diplexers +diploid/S +diploma/SMDG +diplomacy/SM +diplomat/MS +diplomata +diplomatic/S +diplomatically +diplomatics/M +diplomatist/SM +dipodic +dipody/M +dipole/MS +dipped +dipper/SM +dipping/S +dippy/TR +dipsomania/SM +dipsomaniac/MS +dipstick/MS +dipterous +diptych/M +diptychs +dire/YTRP +direct/RDYPTSVG +directed/IUA +direction/MIS +directional/SY +directionality +directions/A +directive/SM +directivity/M +directly/I +directness/ISM +director/AMS +directorate/SM +directorial +directorship/SM +directory/SM +directrix/MS +directs/IA +direful/Y +direness/M +dirge/GSDM +dirigible/S +dirk/GDMS +dirndl/MS +dirt/MS +dirtily +dirtiness/SM +dirty/GPRSDT +dis/MB +disable/LZGD +disablement/MS +disabler/M +disabuse +disadvantaged/P +disagreeable/S +disallow/D +disambiguate/DSGNX +disappointed/Y +disappointing/Y +disarming/Y +disarrange/L +disastrous/Y +disband/L +disbandment/SM +disbar/L +disbarment/MS +disbarring +disbelieving/Y +disbursal/S +disburse/GDRSL +disbursement/MS +disburser/M +disc/GDM +discern/SDRGL +discerner/M +discernibility +discernible/I +discernibly +discerning/Y +discernment/MS +discharged/U +disciple/DSMG +discipleship/SM +disciplinarian/SM +disciplinary +discipline/IDM +disciplined/U +discipliner/M +disciplines +disciplining +disclosed/U +disco/MG +discography/MS +discolor/G +discolored/MP +discoloreds/U +discombobulate/SDGNX +discomfit/DG +discomfiture/MS +discommode/DG +disconcerting/Y +disconnect/R +disconnected/P +disconnectedness/S +disconnecter/M +disconsolate/YN +discord/G +discordance/SM +discordant/Y +discorporate/D +discotheque/MS +discount/B +discourage/LGDR +discouragement/MS +discouraging/Y +discover/ADGS +discoverable/I +discovered/U +discoverer/S +discovery/SAM +discreet/TRYP +discreetly/I +discreetness's/I +discreetness/SM +discrepancy/SM +discrepant/Y +discrete/YPNX +discreteness/SM +discretion/IMS +discretionary +discretization +discretized +discriminable +discriminant/MS +discriminate/SDVNGX +discriminated/U +discriminating/YI +discrimination/MI +discriminator/MS +discriminatory +discursiveness/S +discus/SM +discussant/MS +discussed/UA +discusser/M +discussion/SM +disdain/MGSD +disdainful/YP +disdainfulness/M +disease/G +disembowel/SLGD +disembowelment/SM +disengage/L +disfigure/L +disfigurement/MS +disfranchise/L +disfranchisement/MS +disgorge +disgrace/R +disgracer/M +disgruntle/DSLG +disgruntlement/MS +disguise/R +disguised/UY +disguiser/M +disgust +disgusted/Y +disgustful/Y +disgusting/Y +dish/GD +dishabille/SM +disharmonious +dishcloth/M +dishcloths +dishevel/LDGS +dishevelment/MS +dishonest +dishonored/U +dishpan/MS +dishrag/SM +dishtowel/SM +dishwasher/MS +dishwater/SM +disillusion/LGD +disillusionment/SM +disinfectant/MS +disinherit +disinterested/P +disinterestedness/SM +disinvest/L +disjoin +disjointedness/S +disjunct/VS +disjunctive/YS +disk/D +diskette/S +dislike/G +dislodge/LG +dislodgement/M +dismal/PSTRY +dismalness/M +dismantle/L +dismantlement/SM +dismay/D +dismayed/U +dismaying/Y +dismember/LG +dismemberment/MS +dismiss/RZ +dismissive/Y +disoblige/G +disorder/Y +disordered/YP +disorderedness/M +disorderliness/M +disorderly/P +disorganize +disorganized/U +disparage/RSDLG +disparagement/MS +disparager/M +disparaging/Y +disparate/PSY +disparateness/M +dispatch/Z +dispel/S +dispelled +dispelling +dispensable/I +dispensary/MS +dispensate/NX +dispensation/M +dispense/ZGDRSB +dispenser/M +dispersal/MS +dispersant/M +disperse/XDRSZLNGV +dispersed/Y +disperser/M +dispersible +dispersion/M +dispersive/PY +dispersiveness/M +dispirit/DSG +displace/L +display/AGDS +displayed/U +displease/G +displeased/Y +displeasure +disport +disposable/S +disposal/SM +dispose/IGSD +disposition/ISM +dispositional +disproportional +disproportionate/N +disproportionation/M +disprove/B +disputable/I +disputably/I +disputant/SM +disputation/SM +disputatious/Y +dispute/ZBGSRD +disputed/U +disputer/M +disquiet/M +disquieting/Y +disquisition/SM +disregardful +disrepair/M +disreputable/P +disreputableness/M +disrepute/M +disrespect +disrupt/GVDRS +disrupted/U +disrupter/M +disruption/MS +disruptive/YP +disruptor/M +dissatisfy +dissect/DG +dissed +dissemble/ZGRSD +dissembler/M +disseminate/XGNSD +dissemination/M +dissension/SM +dissent/ZGSDR +dissenter/M +dissertation/SM +disservice +disses +dissever +dissidence/SM +dissident/MS +dissimilar/S +dissing +dissipate/XRSDVNG +dissipated/U +dissipatedly +dissipatedness/M +dissipater/M +dissipation/M +dissociable/I +dissociate/DSXNGV +dissociated/U +dissociation/M +dissociative/Y +dissoluble/I +dissolute/PY +dissoluteness/SM +dissolve/ASDG +dissolved/U +dissonance/SM +dissonant/Y +dissuade/GDRS +dissuader/M +dissuasive +dist +distaff/SM +distal/Y +distance/DSMG +distant/YP +distantness/M +distaste +distemper +distend +distension +distention/SM +distillate/XNMS +distillation/M +distillery/MS +distinct/IYVP +distincter +distinctest +distinction/MS +distinctive/YP +distinctiveness/MS +distinctness/MSI +distinguish/BDRSG +distinguishable/I +distinguishably/I +distinguished/U +distinguisher/M +distort/BGDR +distorted/U +distorter/M +distortion/MS +distract/DG +distracted/YP +distractedness/M +distracting/Y +distrait +distraught/Y +distress +distressful +distressing/Y +distribute/ADXSVNGB +distributed/U +distributer +distribution/AM +distributional +distributive/SPY +distributiveness/M +distributivity +distributor/SM +distributorship/M +district's +district/GSAD +distrust/G +disturb/ZGDRS +disturbance/SM +disturbed/U +disturber/M +disturbing/Y +disulfide/M +disuse/M +disyllable/M +ditch/MRSDG +ditcher/M +dither/RDZSG +ditsy/TR +ditto/DMGS +ditty/SDGM +ditz/S +diuresis/M +diuretic/S +diurnal/SY +div/TZGJDRS +diva/MS +divalent/S +divan/SM +dive/S +dived/M +diver/M +diverge/SDG +divergence/SM +divergent/Y +diverse/XYNP +diverseness/MS +diversification/M +diversifier/M +diversify/GSRDNX +diversion/M +diversionary +diversity/SM +divert/GSD +diverticulitis/SM +divertimento/M +divest/LDGS +divestiture/MS +divestment/S +dividable +divide/AGDS +divided/U +dividend/MS +divider/MS +divination/SM +divine/RSDTZYG +diviner/M +divinity/MS +divisibility/IMS +divisible/I +division/SM +divisional +divisive/PY +divisiveness/MS +divisor/SM +divorce/GSDLM +divorcement/MS +divorcée/MS +divot/MS +divulge/GSD +divvy/GSDM +dixieland +dizzily +dizziness/SM +dizzy/PGRSDT +dizzying/Y +djellaba/S +djellabah's +do/TZRHGJ +doable +dobbin/MS +doc/MS +docent/SM +docile/Y +docility/MS +dock/GZSRDM +docker/M +docket/GSMD +dockland/MS +dockside/M +dockworker/S +dockyard/SM +doctor/GSDM +doctoral +doctorate/SM +doctrinaire/S +doctrinal/Y +doctrine/SM +docudrama/S +document/RDMZGS +documentary/MS +documentation/MS +documented/U +dodder/DGS +dodecahedra +dodecahedral +dodecahedron/M +dodge/GZSRD +dodgem/S +dodger/M +dodo/SM +doe/MS +doer/MU +does/AU +doeskin/MS +doesn't +doff/SGD +dog/SM +dogcart/SM +dogcatcher/MS +doge/SM +dogeared +dogfight/GMS +dogfish/SM +dogfought +dogged/PY +doggedness/SM +doggerel/SM +dogging +doggone/RSDTG +doggy/SRMT +doghouse/SM +dogie/SM +dogleg/SM +doglegged +doglegging +dogma/MS +dogmatic/S +dogmatically/U +dogmatics/M +dogmatism/SM +dogmatist/SM +dogsbody/M +dogtooth/M +dogtrot/MS +dogtrotted +dogtrotting +dogwood/SM +dogy's +doh's +doily/SM +doing/MU +doldrum/S +doldrums/M +dole/MGDS +doled/F +doleful/PY +dolefuller +dolefullest +dolefulness/MS +doles/F +doling/F +doll/MDGS +dollar/SM +dollop/GSMD +dolly/SDMG +dolmen/MS +dolomite/SM +dolomitic +dolor/SM +dolorous/Y +dolphin/SM +dolt/MS +doltish/YP +doltishness/SM +domain/MS +dome/DSMG +domestic/S +domestically +domesticate/DSXGN +domesticated/U +domestication/M +domesticity/MS +domicile/SDMG +domiciliary +dominance/MS +dominant/YS +dominate/VNGXSD +domination/M +dominator/M +dominatrices +dominatrix +domineer/DSG +domineering/YP +domineeringness/M +dominion/MS +domino/M +dominoes +don't +don/S +dona/MS +donate/XVGNSD +donation/M +donative/M +done/AUF +dong/GDMS +dongle/S +donkey/MS +donned +donning +donnish/YP +donnishness/M +donnybrook/MS +donor/MS +donut/MS +donutted +donutting +doodad/MS +doodle/SRDZG +doodlebug/MS +doodler/M +doohickey/MS +doom/MDGS +doomsday/SM +door/GDMS +doorbell/SM +doorhandles +doorkeep/RZ +doorkeeper/M +doorknob/SM +doorman/M +doormat/SM +doormen +doornail/M +doorplate/SM +doors/I +doorstep/MS +doorstepped +doorstepping +doorstop/MS +doorway/MS +dooryard/SM +dopa/SM +dopamine +dopant/M +dope/DRSMZG +doper/M +dopey +dopier +dopiest +dopiness/S +dork/S +dorky/RT +dorm/MRZS +dormancy/MS +dormant/S +dormer/M +dormice +dormitory/SM +dormouse/M +dorsal/YS +dory/SM +dos/GDS +dosage/SM +dose/M +dosimeter/MS +dosimetry/M +dossier/MS +dost +dot/MDRSJZG +dotage/SM +dotard/MS +dote/S +doter/M +doting/Y +dotted +dottiness/M +dotting +dotty/PRT +double/GPSRDZ +doubled/UA +doubleheader/MS +doubleness/M +doubler/M +doubles/M +doublespeak/S +doublet/MS +doublethink/M +doubleton/M +doubling/A +doubloon/MS +doubly +doubt/AGSDMB +doubted/U +doubter/SM +doubtful/YP +doubtfulness/SM +doubting/Y +doubtless/YP +doubtlessness/M +douche/GSDM +dough/M +doughs +doughty/RT +doughy/RT +dour/TYRP +dourness/MS +douse/SRDG +douser/M +dove/RSM +dovecote/MS +dovetail/GSDM +dovish +dowager/SM +dowdily +dowdiness/MS +dowdy/TPSR +dowel/GMDS +dower/GDMS +down/GZSRD +downbeat/SM +downcast/S +downdraft/M +downer/M +downfall/NMS +downgrade/GSD +downhearted/PY +downheartedness/MS +downhill/RS +downland +download/DGS +downpipes +downplay/GDS +downpour/MS +downrange +downright/YP +downrightness/M +downriver +downscale/GSD +downside/S +downsize/DSG +downslope +downspout/SM +downstage/S +downstairs +downstate/SR +downstream +downswing/MS +downtime/SM +downtown/MRS +downtowner/M +downtrend/M +downtrodden +downturn/MS +downward/YPS +downwardness/M +downwind +downy/RT +dowry/SM +dowse/GZSRD +dowser/M +doxology/MS +doyen/SM +doyenne/SM +doz/XGNDRS +doze +dozen/GHD +dozenths +dozer/M +dozy +dpt +drab/YSP +drabbed +drabber +drabbest +drabbing +drabness/MS +drachma/MS +draconian +draft/AMDGS +draftee/SM +drafter/MS +draftily +draftiness/SM +drafting/S +draftsman/M +draftsmanship/SM +draftsmen +draftsperson +draftswoman +draftswomen +drafty/PTR +drag/MS +dragged +dragger/M +dragging/Y +draggy/RT +dragnet/MS +dragon/SM +dragonfly/SM +dragonhead/M +dragoon/DMGS +drain/SZGRDM +drainage/MS +drainboard/SM +drained/U +drainer/M +drainpipe/MS +drake/SM +dram/MS +drama/SM +dramatic/S +dramatical/Y +dramatically/U +dramatics/M +dramatist/MS +dramatization/MS +dramatize/SRDZG +dramatized/U +dramatizer/M +dramaturgy/M +drammed +dramming +drank +drape/SRDGZ +draper/M +drapery/MS +drastic +drastically +drat/S +dratted +dratting +draw/ASG +drawable +drawback/MS +drawbridge/SM +drawer/SM +drawing/SM +drawl/RDSG +drawler/M +drawling/Y +drawly +drawn/AI +drawnly +drawnness +drawstring/MS +dray/SMDG +dread/SRDG +dreadful/YPS +dreadfulness/SM +dreadlocks +dreadnought/SM +dream/SMRDZG +dreamboat/SM +dreamed/U +dreamer/M +dreamily +dreaminess/SM +dreaming/Y +dreamland/SM +dreamless/PY +dreamlessness/M +dreamlike +dreamworld/S +dreamy/PTR +drear/S +drearily +dreariness/SM +dreary/TRSP +dredge/MZGSRD +dredger/M +dreg/MS +drench/GDRS +drencher/M +dress/ADRSG +dressage/MS +dressed/U +dresser's/A +dresser/MS +dresses/U +dressiness/SM +dressing/MS +dressmaker/MS +dressmaking/SM +dressy/PTR +drew/A +drib/SM +dribble/DRSGZ +dribbler/M +driblet/SM +dried/U +drier/M +drift/RDZSG +drifter/M +drifting/Y +driftwood/SM +drill/MRDZGS +driller/M +drilling/M +drillmaster/SM +drink/BRSZG +drinkable/S +drinker/M +drip/SM +dripped +dripping/MS +drippy/RT +drive/SRBGZJ +drivel/GZDRS +driveler/M +driven/P +driver/M +driveway/MS +drizzle/DSGM +drizzling/Y +drizzly/TR +drogue/MS +droll/RDSPTG +drollery/SM +drollness/MS +drolly +dromedary/MS +drone/SRDGM +droning/Y +drool/GSRD +droop/SGD +droopiness/MS +drooping/Y +droopy/PRT +drop/SM +drophead +dropkick/S +droplet/SM +dropout/MS +dropped +dropper/SM +dropping/MS +dropsical +dropsy/MS +drosophila/M +dross/SM +drought/SM +drove/SRDGZ +drover/M +drown/RDSJG +drowner/M +drowse/SDG +drowsily +drowsiness/SM +drowsy/PTR +drub/S +drubbed +drubber/MS +drubbing/SM +drudge/MGSRD +drudger/M +drudgery/SM +drudging/Y +drug/SM +drugged +druggie/SRT +drugging +druggist/SM +drugless +drugstore/SM +druid/MS +druidism/MS +drum/SM +drumbeat/SGM +drumhead/M +drumlin/MS +drummed +drummer/SM +drumming +drumstick/SM +drunk/SRNYMT +drunkard/SM +drunken/YP +drunkenness/SM +drupe/SM +druthers +dry/GYDRSTZ +dryad/MS +dryer/MS +dryish +dryness/SM +drys +drystone +drywall/GSD +dual/YS +dualism/MS +dualist/M +dualistic +duality/MS +dub/S +dubbed +dubber/S +dubbin/MS +dubbing/M +dubiety/MS +dubious/YP +dubiousness/SM +ducal +ducat/SM +duce's +duce/CAIKF +duchess/MS +duchy/SM +duck/GSRDM +duckbill/SM +ducker/M +duckling/SM +duckpins +duckpond +duckweed/MS +ducky/RSMT +duct's/A +duct/KMSF +ducted/CFI +ductile/I +ductility/SM +ducting/F +ductless +ducts/CI +ductwork/M +dud/GMDS +dudder +dude/MS +dudgeon/SM +due/PMS +duel/MRDGZSJ +duelist/MS +dueness/M +duenna/MS +duet/MS +duetted +duetting +duff/GZSRDM +duffel/M +duffer/M +dug/S +dugout/SM +duh +duke/DSMG +dukedom/SM +dulcet/SY +dulcify +dulcimer/MS +dull/SRDPGT +dullard/MS +dullness/MS +dully +dulness's +duly/U +dumb/PSGTYRD +dumbbell/MS +dumbfound/GSDR +dumbness/MS +dumbstruck +dumbwaiter/SM +dumdum/MS +dummy/SDMG +dump/SGZRD +dumper/UM +dumpiness/MS +dumpling/MS +dumpster/S +dumpy/PRST +dun/S +dunce/MS +dunderhead/MS +dune/SM +dung/SGDM +dungaree/SM +dungeon/GSMD +dunghill/MS +dunk/GSRD +dunker/M +dunned +dunner +dunnest +dunning +dunno/M +duo/MS +duodecimal/S +duodena +duodenal +duodenum/M +duologue/M +duopolist +duopoly/M +dupe/NGDRSMZ +duper/M +dupion/M +duple +duplex/MSRDG +duplexer/M +duplicability/M +duplicable +duplicate/ADSGNX +duplication/AM +duplicative +duplicator/MS +duplicitous +duplicity/SM +durability/MS +durable/PS +durableness/M +durably +durance/SM +duration/MS +durational +duress/SM +during +durst +durum/MS +dusk/GDMS +duskiness/MS +dusky/RPT +dust/MRDGZS +dustbin/MS +dustcart/M +dustcover +duster/M +dustily +dustiness/MS +dusting/M +dustless +dustman/M +dustmen +dustpan/SM +dusty/RPT +dutch/MS +duteous/Y +dutiable +dutiful/UPY +dutifulness/S +duty/SM +duvet/SM +duxes +dwarf/MTGSPRD +dwarfish +dwarfism/MS +dweeb/S +dwell/IGS +dweller/SM +dwelling/MS +dwelt/I +dwindle/GSD +dyad/MS +dyadic +dybbuk/SM +dybbukim +dye/JDRSMZG +dyed/A +dyeing/M +dyer/M +dyes/A +dyestuff/SM +dying/UA +dyke's +dynamic/S +dynamical/Y +dynamics/M +dynamism/SM +dynamite/RSDZMG +dynamiter/M +dynamized +dynamo/MS +dynastic +dynasty/MS +dyne/M +dysentery/SM +dysfunction/MS +dysfunctional +dyslectic/S +dyslexia/MS +dyslexic/S +dyslexically +dyspepsia/MS +dyspeptic/S +dysprosium/MS +dystopia/M +dystrophy/M +dz +débutante/SM +décolletage/S +décolleté +démodé +dérailleur/MS +déshabillé's +détente +e'en +e'er +e's +e/FMDS +ea +each +eager/TSPRYM +eagerness/MS +eagle/SDGM +eaglet/SM +ear/GSMDYH +earache/SM +eardrum/SM +earful/MS +earing/M +earl/MS +earldom/MS +earliness/SM +earlobe/S +early/PRST +earmark/DGSJ +earmuff/SM +earn/GRDZTSJ +earned/U +earner/M +earnest/PYS +earnestness/MS +earning/M +earphone/MS +earpieces +earplug/MS +earring/MS +earshot/MS +earsplitting +earth/MDNYG +earthbound +earthed/U +earthenware/MS +earthiness/SM +earthliness/M +earthling/MS +earthly/TPR +earthmen +earthmover/M +earthmoving +earthquake/SDGM +earths/U +earthshaking +earthward/S +earthwork/MS +earthworm/MS +earthy/PTR +earwax/MS +earwig/MS +earwigged +earwigging +ease's/EU +ease/LDRSMG +eased/E +easel/MS +easement/MS +easer/M +eases/UE +easies +easily/U +easiness/MSU +easing/M +east/GSMR +eastbound +easter/Y +easterly/S +eastern/ZR +easterner/M +easternmost +easting/M +eastward/S +easy/PUTR +easygoing/P +easygoingness/M +eat/SJZGNRB +eatable/U +eatables +eaten/U +eater/M +eatery/MS +eating/M +eave/SM +eavesdrop/S +eavesdropped +eavesdropper/MS +eavesdropping +ebb/DSG +ebony/SM +ebullience/SM +ebullient/Y +ebullition/SM +eccentric/MS +eccentrically +eccentricity/SM +eccl +ecclesiastic/MS +ecclesiastical/Y +echelon/SGDM +echinoderm/SM +echo/DMG +echoed/A +echoes/A +echoic +echolocation/SM +eclectic/S +eclectically +eclecticism/MS +eclipse/MGSD +ecliptic/MS +eclogue/MS +ecocide/SM +ecol +ecologic +ecological/Y +ecologist/MS +ecology/MS +econ +econometric/S +econometricians +econometrics/M +economic/S +economical/YU +economics/M +economist/MS +economization +economize/GZSRD +economizer/M +economizing/U +economy/MS +ecosystem/MS +ecru/SM +ecstasy/MS +ecstatic/S +ecstatically +ectoplasm/M +ecumenic/MS +ecumenical/Y +ecumenicism/SM +ecumenicist/MS +ecumenics/M +ecumenism/SM +ecumenist/MS +eczema/MS +ed/ASC +eddy/SDMG +edelweiss/MS +edema/SM +edematous +eden +edge/DRSMZGJ +edgeless +edger/M +edgewise +edgily +edginess/MS +edging/M +edgy/TRP +edibility/MS +edible/SP +edibleness/SM +edict/SM +edification/M +edifice/SM +edifier/M +edify/ZNXGRSD +edifying/U +edit/SADG +editable +edited/IU +edition/SM +editor/MS +editorial/YS +editorialist/M +editorialize/DRSG +editorializer/M +editorship/MS +eds +educ/DBG +educability/SM +educable/S +educate/XASDGN +educated/YP +education/AM +educational/Y +educationalists +educationists +educative +educator/MS +educe/S +eduction/M +edutainment/S +eek/S +eel/MS +eelgrass/M +eerie/RT +eerily +eeriness/MS +efface/SRDLG +effaceable/I +effacement/MS +effacer/M +effect/SMDGV +effective/YIP +effectiveness/ISM +effectives +effector/MS +effectual/IYP +effectualness/MI +effectuate/SDGN +effectuation/M +effeminacy/MS +effeminate/SY +effendi/MS +efferent/SY +effervesce/GSD +effervescence/SM +effervescent/Y +effete/YP +effeteness/SM +efficacious/IPY +efficaciousness/MI +efficacy/IMS +efficiency/MIS +efficient/ISY +effigy/SM +effloresce +efflorescence/SM +efflorescent +effluence/SM +effluent/MS +effluvia +effluvium/M +efflux/M +effluxion +effort/MS +effortless/PY +effortlessness/SM +effrontery/MS +effulgence/SM +effulgent +effuse/XSDVGN +effusion/M +effusive/YP +effusiveness/MS +egad +egalitarian/I +egalitarianism/MS +egalitarians +egg/GMDRS +eggbeater/SM +eggcup/MS +egger/M +egghead/SDM +eggheaded/P +eggnog/SM +eggplant/MS +eggshell/SM +egis's +eglantine/MS +ego/SM +egocentric/S +egocentrically +egocentricity/SM +egoism/SM +egoist/SM +egoistic +egoistical/Y +egomania/MS +egomaniac/MS +egotism/SM +egotist/MS +egotistic +egotistical/Y +egregious/PY +egregiousness/MS +egress/SDMG +egret/SM +eh +eider/SM +eiderdown/SM +eidetic +eigenfunction/MS +eigenstate/S +eigenvalue/SM +eigenvector/MS +eight/SM +eighteen/MHS +eighteenths +eightfold +eighth/MS +eighths +eightieths +eightpence +eighty/SHM +einsteinium/MS +eisteddfod/M +either +ejaculate/SDXNG +ejaculation/M +ejaculatory +eject/VGSD +ejecta +ejection/SM +ejector/SM +eke/DSG +eked/A +el/AS +elaborate/SDYPVNGX +elaborateness/SM +elaboration/M +elaborators +eland/SM +elans +elapse/SDG +elastic/S +elastically/I +elasticated +elasticity/SM +elasticize/GDS +elastodynamics +elastomer/M +elate/SRDXGN +elated/PY +elatedness/M +elater/M +elation/M +elbow/GDMS +elbowroom/SM +elder/SY +elderberry/MS +elderflower +elderliness/M +elderly/PS +eldest +elect/ASGD +electable/U +elected/U +election/SAM +electioneer/GSD +elective/SPY +electiveness/M +elector/SM +electoral/Y +electorate/SM +electress/M +electric/S +electrical/PY +electricalness/M +electrician/SM +electricity/SM +electrification/M +electrifier/M +electrify/ZXGNDRS +electro/M +electrocardiogram/MS +electrocardiograph/M +electrocardiographs +electrocardiography/MS +electrochemical/Y +electrocute/GNXSD +electrocution/M +electrode/SM +electrodynamic/YS +electrodynamics/M +electroencephalogram/SM +electroencephalograph/M +electroencephalographic +electroencephalographs +electroencephalography/MS +electrologist/MS +electroluminescent +electrolysis/M +electrolyte/SM +electrolytic +electrolytically +electrolyze/SDG +electromagnet/SM +electromagnetic +electromagnetically +electromagnetism/SM +electromechanical +electromechanics +electromotive +electromyograph +electromyographic +electromyographically +electromyography/M +electron/MS +electronegative +electronic/S +electronically +electronics/M +electrophoresis/M +electrophorus/M +electroplate/DSG +electroscope/MS +electroscopic +electroshock/GDMS +electrostatic/S +electrostatics/M +electrotherapist/M +electrotype/GSDZM +electroweak +eleemosynary +elegance/ISM +elegant/YI +elegiac/S +elegiacal +elegy/SM +elem +element/MS +elemental/YS +elementarily +elementariness/M +elementary/P +elephant/SM +elephantiases +elephantiasis/M +elephantine +elev/NX +elevate/XDSNG +elevated/S +elevation/M +elevator/SM +eleven/HM +elevens/S +elevenths +elf/M +elfin/S +elfish +elicit/GSD +elicitation/MS +elide/GSD +eligibility/ISM +eligible/SI +eliminate/XSDYVGN +elimination/M +eliminator/SM +elision/SM +elite/MPS +elitism/SM +elitist/SM +elixir/MS +elk/MS +ell/MS +ellipse/MS +ellipsis/M +ellipsoid/MS +ellipsoidal +ellipsometer/MS +ellipsometry +elliptic +elliptical/YS +ellipticity/M +elm/MRS +elocution/SM +elocutionary +elocutionist/MS +elodea/S +elongate/NGXSD +elongation/M +elope/SRDLG +elopement/MS +eloper/M +eloquence/SM +eloquent/IY +els +else/M +elsewhere +eluate/SM +elucidate/SDVNGX +elucidation/M +elude/GSD +elusive/YP +elusiveness/SM +elute/DGN +elution/M +elven +elver/SM +elves/M +elvish +elysian +em/M +emaciate/NGXDS +emaciation/M +emacs/M +email/SMDG +emanate/XSDVNG +emanation/M +emancipate/DSXGN +emancipation/M +emancipator/MS +emasculate/GNDSX +emasculation/M +embalm/ZGRDS +embalmer/M +embank/GLDS +embankment/MS +embarcadero +embargo/GMD +embargoes +embark/ADESG +embarkation/EMS +embarrass/SDLG +embarrassed/U +embarrassedly +embarrassing/Y +embarrassment/MS +embassy/MS +embattle/DSG +embed/S +embeddable +embedded +embedder +embedding/MS +embellish/LGRSD +embellished/U +embellisher/M +embellishment/MS +ember/MS +embezzle/LZGDRS +embezzlement/MS +embezzler/M +embitter/LGDS +embitterment/SM +emblazon/DLGS +emblazonment/SM +emblem/GSMD +emblematic +embodier/M +embodiment/ESM +embody/ESDGA +embolden/DSG +embolism/SM +embosom +emboss/ZGRSD +embosser/M +embouchure/SM +embower/GSD +embrace/RSDVG +embraceable +embracer/M +embracing/Y +embrasure/MS +embrittle +embrocation/SM +embroider/SGZDR +embroiderer/M +embroidery/MS +embroil/SLDG +embroilment/MS +embryo/SM +embryologist/SM +embryology/MS +embryonic +emcee/SDM +emceeing +emend/SRDGB +emendation/MS +emerald/SM +emerge/ADSG +emergence/MAS +emergency/SM +emergent/S +emerita +emeritae +emeriti +emeritus +emery/MGSD +emetic/S +emf/S +emigrant/MS +emigrate/SDXNG +emigration/M +eminence/MS +eminent/Y +emir/SM +emirate/SM +emissary/SM +emission/AMS +emissivity/MS +emit/S +emittance/M +emitted +emitter/SM +emitting +emollient/S +emolument/SM +emote/SDVGNX +emotion/M +emotional/UY +emotionalism/MS +emotionality/M +emotionalize/GDS +emotionless +emotive/Y +empaneled +empaneling +empath +empathetic +empathetical/Y +empathic +empathize/SDG +empathy/MS +emperor/MS +emphases +emphasis/M +emphasize/ZGCRSDA +emphatic/U +emphatically/U +emphysema/SM +emphysematous +empire/MS +empiric/SM +empirical/Y +empiricism/SM +empiricist/SM +emplace/L +emplacement/MS +employ/LAGDS +employability/UM +employable/US +employed/U +employee/SM +employer/SM +employment/UMAS +emporium/MS +empower/GLSD +empowerment/MS +empress/MS +emptier/M +emptily +emptiness/SM +empty/GRSDPT +empyrean/SM +ems/C +emu/SM +emulate/SDVGNX +emulation/M +emulative/Y +emulator/MS +emulsification/M +emulsifier/M +emulsify/NZSRDXG +emulsion/SM +en/BM +enable/SRDZG +enabler/M +enact/SGALD +enactment/ASM +enamel/ZGJMDRS +enameler/M +enamelware/SM +enamor/DSG +enc +encamp/LSDG +encampment/MS +encapsulate/SDGNX +encapsulation/M +encase/GSDL +encasement/SM +encephalitic +encephalitides +encephalitis/M +encephalographic +encephalopathy/M +enchain/SGD +enchant/ESLDG +enchanter/MS +enchanting/Y +enchantment/MSE +enchantress/MS +enchilada/SM +encipher/SRDG +encipherer/M +encircle/GLDS +encirclement/SM +encl +enclave/MGDS +enclose/GDS +enclosed/U +enclosure/SM +encode/ZJGSRD +encoder/M +encomium/SM +encompass/GDS +encore/GSD +encounter/GSD +encourage/SRDGL +encouragement/SM +encourager/M +encouraging/Y +encroach/LGRSD +encroacher/M +encroachment/MS +encrust/DSG +encrustation/MS +encrypt/DGS +encrypted/U +encryption/SM +encumber/SEDG +encumbered/U +encumbrance/SRM +encumbrancer/M +ency +encyclical/SM +encyclopaedia's +encyclopedia/SM +encyclopedic +encyst/GSLD +encystment/MS +end/ZGVMDRSJ +endanger/DGSL +endangerment/SM +endear/GSLD +endearing/Y +endearment/MS +endeavor/GZSMRD +endeavored/U +endeavorer/M +endemic/S +endemically +endemicity +ender/M +endgame/M +ending/M +endive/SM +endless/PY +endlessness/MS +endmost +endnote/MS +endocrine/S +endocrinologist/SM +endocrinology/SM +endogamous +endogamy/M +endogenous/Y +endomorphism/SM +endorse/DRSZGL +endorsement/MS +endorser/M +endoscope/MS +endoscopic +endoscopy/SM +endosperm/M +endothelial +endothermic +endow/GSDL +endowment/SM +endpoint/MS +endue/SDG +endungeoned +endurable/U +endurably/U +endurance/SM +endure/BSDG +enduring/YP +enduringness/M +endways +enema/SM +enemy/SM +energetic/S +energetically +energetics/M +energize/ZGDRS +energized/U +energizer/M +energy/MS +enervate/XNGVDS +enervation/M +enfeeble/GLDS +enfeeblement/SM +enfilade/MGDS +enfold/SGD +enforce/LDRSZG +enforceability/M +enforceable/U +enforced/Y +enforcement/SM +enforcer/M +enforcible/U +enfranchise/ELDRSG +enfranchisement/EMS +enfranchiser/M +engage/ADSGE +engagement/SEM +engaging/Y +engender/DGS +engine/MGSD +engineer/GSMDJ +engineering/MY +england/ZR +engorge/LGDS +engorgement/MS +engram/MS +engrave/ZGDRSJ +engraver/M +engraving/M +engross/GLDRS +engrossed/Y +engrosser/M +engrossing/Y +engrossment/SM +engulf/GDSL +engulfment/SM +enhance/LZGDRS +enhanceable +enhancement/MS +enhancer/M +enharmonic +enigma/MS +enigmatic +enigmatically +enjambement's +enjambment/MS +enjoin/GSD +enjoinder +enjoy/GBDSL +enjoyability +enjoyable/P +enjoyableness/M +enjoyably +enjoyment/SM +enlarge/LDRSZG +enlargeable +enlargement/MS +enlarger/M +enlighten/GDSL +enlightened/U +enlightening/U +enlightenment/SM +enlist/SAGDL +enlistee/MS +enlister/M +enlistment/SAM +enliven/LDGS +enlivenment/SM +enmesh/DSLG +enmeshment/SM +enmity/MS +ennoble/LDRSG +ennoblement/SM +ennobler/M +ennui/SM +enormity/SM +enormous/YP +enormousness/MS +enough +enoughs +enplane/DSG +enqueue/DS +enquirer/S +enquiringly +enrage/SDG +enrapture/GSD +enrich/LDSRG +enricher/M +enrichment/SM +enrobed +enroll/LGSD +enrollee/SM +enrollment/SM +ens +ensconce/DSG +ensemble/MS +enshrine/DSLG +enshrinement/SM +enshroud/DGS +ensign/SM +ensilage/DSMG +enslave/ZGLDSR +enslavement/MS +enslaver/M +ensnare/GLDS +ensnarement/SM +ensue/SDG +ensure/SRDZG +ensurer/M +entail/SDRLG +entailer/M +entailment/MS +entangle/EGDRSL +entanglement/ESM +entangler/EM +entente/MS +enter/ASDG +entered/U +enterer/M +enteritides +enteritis/SM +enterprise/GMSR +enterpriser/M +enterprising/Y +entertain/SGZRDL +entertainer/M +entertaining/Y +entertainment/SM +enthalpy/SM +enthrall/GDSL +enthrallment/SM +enthrone/GDSL +enthronement/MS +enthuse/DSG +enthusiasm/SM +enthusiast/MS +enthusiastic/U +enthusiastically/U +entice/SRDJLZG +enticement/SM +enticing/Y +entire/SY +entirety/SM +entitle/GLDS +entitlement/MS +entity/SM +entomb/GDSL +entombment/MS +entomological +entomologist/S +entomology/MS +entourage/SM +entr'acte/S +entrails +entrain/GSLDR +entrainer/M +entrance/MGDSL +entrancement/MS +entranceway/M +entrancing/Y +entrant/MS +entrap/SL +entrapment/SM +entrapped +entrapping +entreat/SGD +entreating/Y +entreaty/SM +entrench/LSDG +entrenchment/MS +entrepreneur/MS +entrepreneurial +entrepreneurship/M +entropic +entropy/MS +entrust/DSG +entry/ASM +entryway/SM +entrée/S +entwine/DSG +enumerable +enumerate/AN +enumerated/U +enumerates +enumerating +enumeration's/A +enumeration/SM +enumerative +enumerator/SM +enunciable +enunciate/XGNSD +enunciated/U +enunciation/M +enureses +enuresis/M +envelop/ZGLSDR +envelope/MS +enveloper/M +envelopment/MS +envenom/SDG +enviable/U +enviableness/M +enviably +envied/U +envier/M +envious/PY +enviousness/SM +environ/LGSD +environment/MS +environmental/Y +environmentalism/SM +environmentalist/SM +envisage/DSG +envision/GSD +envoy/SM +envy/SRDMG +envying/Y +enzymatic +enzymatically +enzyme/SM +enzymology/M +eohippus/M +eolian +eon/SM +epaulet/SM +ephedrine/MS +ephemera/MS +ephemeral/SY +ephemerids +ephemeris/M +epic/SM +epically +epicenter/SM +epicure/SM +epicurean/S +epicycle/MS +epicyclic +epicyclical/Y +epicycloid/M +epidemic/MS +epidemically +epidemiological/Y +epidemiologist/MS +epidemiology/MS +epidermal +epidermic +epidermis/MS +epidural +epigenetic +epiglottis/SM +epigram/MS +epigrammatic +epigraph/RM +epigrapher/M +epigraphs +epigraphy/MS +epilepsy/SM +epileptic/S +epilogue/SDMG +epinephrine/SM +epiphany/SM +epiphenomena +episcopacy/MS +episcopal/Y +episcopalian +episcopate/MS +episode/SM +episodic +episodically +epistemic +epistemological/Y +epistemology/M +epistle/MRS +epistolary/S +epistolatory +epitaph/GMD +epitaphs +epitaxial/Y +epitaxy/M +epithelial +epithelium/MS +epithet/MS +epitome/MS +epitomize/SRDZG +epitomized/U +epitomizer/M +epoch/M +epochal/Y +epochs +eponymous +epoxy/GSD +epsilon/SM +equability/MS +equable/P +equableness/M +equably +equal/USDY +equaling +equality/ISM +equalization/MS +equalize/DRSGJZ +equalized/U +equalizer/M +equalizes/U +equanimity/MS +equate/NGXBSD +equation/M +equator/SM +equatorial/S +equerry/MS +equestrian/S +equestrianism/SM +equestrienne/SM +equiangular +equidistant/Y +equilateral/S +equilibrate/GNSD +equilibration/M +equilibrium/MSE +equine/S +equinoctial/S +equinox/MS +equip/AS +equipage/SM +equipartition/M +equipment/SM +equipoise/GMSD +equipotent +equipped/AU +equipping/A +equiproportional +equiproportionality +equiproportionate +equitable/I +equitableness/M +equitably/I +equitation/SM +equity/IMS +equiv +equivalence/DSMG +equivalent/SY +equivocal/UY +equivocalness/MS +equivocate/NGSDX +equivocation/M +equivocator/SM +era/MS +eradicable/I +eradicate/SDXVGN +eradication/M +eradicator/SM +eras/SRDBGZ +erase/N +eraser/M +erasion/M +erasure/MS +erbium/SM +ere +erect/GPSRDY +erectile +erection/SM +erectness/MS +erector/SM +erelong +eremite/MS +erg/SM +ergo +ergodic +ergodicity/M +ergonomic/U +ergonomically +ergonomics/M +ergophobia +ergosterol/SM +ergot/SM +eris +ermine/MSD +erode/SDG +erodible +erogenous +erosible +erosion/SM +erosional +erosive/P +erosiveness/M +erotic/S +erotica/M +erotically +eroticism/MS +err/DGS +errancy/MS +errand/MS +errant/YS +errantry/M +errata/SM +erratic/S +erratically +erratum/MS +erring/UY +erroneous/YP +erroneousness/M +error/SM +ersatz/S +erst +erstwhile +eruct/DGS +eructation/MS +erudite/NYX +erudition/M +erupt/DSVG +eruption/SM +eruptive/SY +erysipelas/SM +erythrocyte/SM +es +escadrille/M +escalate/CDSXGN +escalation/MC +escalator/SM +escallop/SGDM +escapable/I +escapade/SM +escape/LGSRDB +escapee/MS +escapement/MS +escaper/M +escapism/SM +escapist/S +escapology +escarole/MS +escarpment/MS +eschatology/M +eschew/SGD +escort/SGMD +escritoire/SM +escrow/DMGS +escudo/MS +escutcheon/SM +esophageal +esophagi +esophagus/M +esoteric +esoterica +esoterically +esp +espadrille/MS +espalier/SMDG +especial/Y +espionage/SM +esplanade/SM +espousal/MS +espouse/SRDG +espouser/M +espresso/SM +esprit/SM +espy/GSD +esquire/GMSD +essay/SZMGRD +essayer/M +essayist/SM +essence/MS +essential/USI +essentialist/M +essentially +essentialness/M +est/RZ +establish/LAEGSD +established/U +establisher/M +establishment/EMAS +estate/GSDM +esteem/EGDS +ester/M +esthete's +esthetic's +esthetically +esthetics's +estimable/I +estimableness/M +estimate/XDSNGV +estimating/A +estimation/M +estimator/SM +estoppal +estrange/DRSLG +estrangement/SM +estranger/M +estrogen/SM +estrous +estrus/SM +estuarine +estuary/SM +et +eta/SM +etc +etcetera/SM +etch/GZJSRD +etcher/M +etching/M +eternal/PSY +eternalness/SM +eternity/SM +ethane/SM +ethanol/MS +ether/SM +ethereal/PY +etherealness/M +etherized +ethic/MS +ethical/PYS +ethically/U +ethicalness/M +ethicist/S +ethnic/S +ethnically +ethnicity/MS +ethnocentric +ethnocentrism/MS +ethnographers +ethnographic +ethnography/M +ethnological +ethnologist/SM +ethnology/SM +ethnomethodology +ethological +ethologist/MS +ethology/SM +ethos/SM +ethyl/SM +ethylene/MS +etiologic +etiological +etiology/SM +etiquette/SM +etymological/Y +etymologist/SM +etymology/MS +eucalypti +eucalyptus/SM +euchre/MGSD +euclidean +eugenic/S +eugenically +eugenicist/SM +eugenics/M +eulogist/MS +eulogistic +eulogize/GRSDZ +eulogized/U +eulogizer/M +eulogy/MS +eunuch/M +eunuchs +euphemism/MS +euphemist/M +euphemistic +euphemistically +euphonious/Y +euphonium/M +euphony/SM +euphoria/SM +euphoric +euphorically +eureka/S +europium/MS +eutectic +euthanasia/SM +euthenics/M +evacuate/DSXNGV +evacuation/M +evacuee/MS +evade/SRDBGZ +evader/M +evaluable +evaluate/ADSGNX +evaluated/U +evaluation/MA +evaluational +evaluative +evaluator/MS +evanescence/MS +evanescent +evangelic +evangelical/YS +evangelicalism/SM +evangelism/SM +evangelist/MS +evangelistic +evangelize/GDS +evaporate/VNGSDX +evaporation/M +evaporative/Y +evaporator/MS +evasion/SM +evasive/PY +evasiveness/SM +eve's/A +eve/RSM +even/PUYRT +evened +evener/M +evenhanded/YP +evening/SM +evenness/MSU +evens +evensong/MS +event/SGM +eventful/YU +eventfulness/SM +eventide/SM +eventual/Y +eventuality/MS +eventuate/GSD +ever/T +everglade/MS +evergreen/S +everlasting/PYS +everlastingness/M +everliving +evermore +every +everybody/M +everyday/P +everydayness/M +everyman +everyone/MS +everyplace +everything +everywhere +eves/A +evict/DGS +eviction/SM +evidence/MGSD +evident/YS +evidential/Y +evil/YRPTS +evildoer/SM +evildoing/MS +evilness/MS +evince/SDG +eviscerate/GNXDS +evisceration/M +evocable +evocate/NVX +evocation/M +evocative/YP +evocativeness/M +evoke/SDG +evolute/NMXS +evolution/M +evolutionarily +evolutionary +evolutionist/MS +evolve/SDG +ewe/MZRS +ewer/M +ex/S +exacerbate/NGXDS +exacerbation/M +exact/TGSPRDY +exacter/M +exacting/YP +exactingness/M +exaction/SM +exactitude/ISM +exactly/I +exactness/MSI +exaggerate/DSXNGV +exaggerated/YP +exaggeration/M +exaggerative/Y +exaggerator/MS +exalt/ZRDGS +exaltation/SM +exalted/Y +exalter/M +exam/MNS +examen/M +examination's +examination/AS +examine/BGZDRS +examined/AU +examinees +examiner/M +examines/A +examining/A +example/DSGM +exampled/U +exasperate/DSXGN +exasperated/Y +exasperating/Y +exasperation/M +excavate/NGDSX +excavation/M +excavator/SM +exceed/SGDR +exceeder/M +exceeding/Y +excel/S +excelled +excellence/SM +excellency/MS +excellent/Y +excelling +excelsior/S +except/DSGV +exception/BMS +exceptionable/U +exceptional/YU +exceptionalness/M +excerpt/GMDRS +excerpter/M +excess/GVDSM +excessive/PY +excessiveness/M +exchange/GDRSZ +exchangeable +exchanger/M +exchequer/SM +excise/XMSDNGB +excision/M +excitability/MS +excitable/P +excitableness/M +excitably +excitation/SM +excitatory +excite/RSDLBZG +excited/Y +excitement/MS +exciter/M +exciting/U +excitingly +exciton/M +exclaim/SZDRG +exclaimer/M +exclamation/MS +exclamatory +exclude/DRSG +excluder/M +exclusion/SZMR +exclusionary +exclusioner/M +exclusive/SPY +exclusiveness/SM +exclusivity/MS +excommunicate/XVNGSD +excommunication/M +excoriate/GNXSD +excoriation/M +excrement/SM +excremental +excrescence/MS +excrescent +excreta +excrete/NGDRSX +excreter/M +excretion/M +excretory/S +excruciate/NGDS +excruciating/Y +excruciation/M +exculpate/XSDGN +exculpation/M +exculpatory +excursion/MS +excursionist/SM +excursive/PY +excursiveness/SM +excursus/MS +excusable/IP +excusableness/IM +excusably/I +excuse/BGRSD +excused/U +excuser/M +exec/MS +execrable/P +execrableness/M +execrably +execrate/DSXNGV +execration/M +executable/MS +execute/NGVZBXDRS +executer/M +execution/ZMR +executional +executioner/M +executive/SM +executor/SM +executrices +executrix/M +exegeses +exegesis/M +exegete/M +exegetic/S +exegetical +exemplar/MS +exemplariness/M +exemplary/P +exemplification/M +exemplifier/M +exemplify/ZXNSRDG +exempt/SDG +exemption/MS +exercise/ZDRSGB +exerciser/M +exert/SGD +exertion/MS +exeunt +exhalation/SM +exhale/GSD +exhaust/VGRDS +exhausted/Y +exhauster/M +exhaustible/I +exhausting/Y +exhaustion/SM +exhaustive/YP +exhaustiveness/MS +exhibit/VGSD +exhibition/ZMRS +exhibitioner/M +exhibitionism/MS +exhibitionist/MS +exhibitor/SM +exhilarate/XSDVNG +exhilarating/Y +exhilaration/M +exhort/DRSG +exhortation/SM +exhorter/M +exhumation/SM +exhume/GRSD +exhumer/M +exigence/S +exigency/SM +exigent/SY +exiguity/SM +exiguous +exile/SDGM +exist/SDG +existence/MS +existent/I +existential/Y +existentialism/MS +existentialist/MS +existentialistic +existents +exit/MDSG +exobiology/MS +exocrine +exodus/SM +exogamous +exogamy/M +exogenous/Y +exonerate/SDVGNX +exoneration/M +exorbitance/MS +exorbitant/Y +exorcise/SDG +exorcism/SM +exorcist/SM +exorcizer/M +exoskeleton/MS +exosphere/SM +exothermic +exothermically +exotic/PS +exotica +exotically +exoticism/SM +exoticness/M +exp +expand/DRSGZB +expandability/M +expanded/U +expander/M +expanse/DSXGNVM +expansible +expansion/M +expansionary +expansionism/MS +expansionist/MS +expansive/YP +expansiveness/S +expatiate/XSDNG +expatiation/M +expatriate/SDNGX +expatriation/M +expect/SBGD +expectancy/MS +expectant/YS +expectation/MS +expectational +expected/UPY +expecting/Y +expectorant/S +expectorate/NGXDS +expectoration/M +expedience/IS +expediency/IMS +expedient/YI +expedients +expedite/ZDRSNGX +expediter/M +expedition/M +expeditionary +expeditious/YP +expeditiousness/MS +expeditor's +expel/S +expellable +expelled +expelling +expend/SDRGB +expendable/S +expended/U +expender/M +expenditure/SM +expense/DSGVM +expensive/IYP +expensiveness/SMI +experience/ISDM +experienced/U +experiencing +experiential/Y +experiment/GSMDRZ +experimental/Y +experimentalism/M +experimentalist/SM +experimentation/SM +experimenter/M +expert's +expert/PISY +experted +experting +expertise/SM +expertize/GD +expertness/IM +expertnesses +expiable/I +expiate/XGNDS +expiation/M +expiatory +expiration/MS +expire/SDG +expired/U +expiry/MS +explain/ADSG +explainable/UI +explained/U +explainer/SM +explanation/MS +explanatory +expletive/SM +explicable/I +explicate/VGNSDX +explication/M +explicative/Y +explicit/PSY +explicitness/SM +explode/DSRGZ +exploded/U +exploder/M +exploit/ZGVSMDRB +exploitation/MS +exploitative +exploited/U +exploiter/M +exploration/MS +exploratory +explore/DSRBGZ +explored/U +explorer/M +explosion/MS +explosive/YPS +explosiveness/SM +expo/MS +exponent/MS +exponential/SY +exponentiate/XSDNG +exponentiation/M +export's +export/AGSD +exportability +exportable +exportation/SM +exporter/MS +expos/RSDZG +expose +exposed/U +exposer/M +exposit/D +exposition/SM +expositor/MS +expository +expostulate/DSXNG +expostulation/M +exposure/SM +expound/ZGSDR +expounder/M +express/GVDRSY +expressed/U +expresser/M +expressibility/I +expressible/I +expressibly/I +expression/MS +expressionism/SM +expressionist/S +expressionistic +expressionless/YP +expressive/IYP +expressiveness's/I +expressiveness/MS +expressway/SM +expropriate/XDSGN +expropriation/M +expropriator/SM +expulsion/MS +expunge/GDSR +expunger/M +expurgate/SDGNX +expurgated/U +expurgation/M +exquisite/YPS +exquisiteness/SM +ext +extant +extemporaneous/YP +extemporaneousness/MS +extempore/S +extemporization/SM +extemporize/ZGSRD +extemporizer/M +extend/SGZDR +extendability/M +extended/U +extendedly +extendedness/M +extender/M +extendibility/M +extendibles +extensibility/M +extensible/I +extension/SM +extensional/Y +extensive/PY +extensiveness/SM +extensor/MS +extent/SM +extenuate/XSDGN +extenuation/M +exterior/MYS +exterminate/XNGDS +extermination/M +exterminator/SM +extern/M +external/YS +externalities +externalization/SM +externalize/GDS +extinct/DGVS +extinction/MS +extinguish/BZGDRS +extinguishable/I +extinguisher/M +extirpate/XSDVNG +extirpation/M +extol/S +extolled +extoller/M +extolling +extort/DRSGV +extorter/M +extortion/ZSRM +extortionate/Y +extortioner/M +extortionist/SM +extra/S +extracellular/Y +extract/GVSBD +extraction/SM +extractive/Y +extractor/SM +extracurricular/S +extradite/XNGSDB +extradition/M +extragalactic +extralegal/Y +extramarital +extramural +extraneous/YP +extraneousness/M +extraordinarily +extraordinariness/M +extraordinary/PS +extrapolate/XVGNSD +extrapolation/M +extrasensory +extraterrestrial/S +extraterritorial +extraterritoriality/MS +extravagance/MS +extravagant/Y +extravaganza/SM +extravehicular +extravert's +extrema +extremal +extreme/DSRYTP +extremeness/MS +extremism/SM +extremist/MS +extremity/SM +extricable/I +extricate/XSDNG +extrication/M +extrinsic +extrinsically +extroversion/SM +extrovert/GMDS +extrude/GDSR +extruder/M +extrusion/MS +extrusive +exuberance/MS +exuberant/Y +exudate/XNM +exudation/M +exude/GSD +exult/DGS +exultant/Y +exultation/SM +exulting/Y +exurb/MS +exurban +exurbanite/SM +exurbia/MS +eye/GDRSMZ +eyeball/GSMD +eyebrow/MS +eyed/P +eyedropper/MS +eyeful/MS +eyeglass/MS +eyelash/MS +eyeless +eyelet/GSMD +eyelid/SM +eyeliner/MS +eyeopener/MS +eyeopening +eyepiece/SM +eyer/M +eyeshadow +eyesight/MS +eyesore/SM +eyestrain/MS +eyeteeth +eyetooth/M +eyewash/MS +eyewitness/SM +eyrie's +f's/KA +f/IRAC +fa/M +fable/GMSRD +fabler/M +fabric/MS +fabricate/SDXNG +fabrication/M +fabricator/MS +fabulists +fabulous/YP +fabulousness/M +facade/GMSD +face's +face/AGCSD +facecloth +facecloths +faceless/P +faceplate/M +facer/CM +facet/SGMD +facetious/YP +facetiousness/MS +facial/YS +facile/YP +facileness/M +facilitate/VNGXSD +facilitation/M +facilitator/SM +facilitatory +facility/MS +facing/MS +facsimile/MSD +facsimileing +fact/MS +faction/SM +factional +factionalism/SM +factious/PY +factiousness/M +factitious +facto +factoid/S +factor/SDMJG +factorial/MS +factoring's +factoring/A +factorisable +factorization/SM +factorize/GSD +factory/MS +factotum/MS +factual/PY +factuality/M +factualness/M +faculty/MS +fad/ZGSMDR +faddish +faddist/SM +fade/S +faded/U +fadedly +fadeout +fader/M +fading's +fading/U +faerie/MS +faery's +fag/MS +fagged +fagging +faggoting's +fagot/MDSJG +fagoting/M +fail/JSGD +failing's +failing/UY +faille/MS +failsafe +failure/SM +fain/GTSRD +faint/YRDSGPT +fainter/M +fainthearted +faintness/MS +fair/TURYP +faired +fairgoer/S +fairground/MS +fairing/MS +fairish +fairless +fairness's +fairness/US +fairs +fairway/MS +fairy/MS +fairyland/MS +fairytale +faith's +faith/U +faithed +faithful/UYP +faithfulness/MSU +faithfuls +faithing +faithless/YP +faithlessness/SM +faiths +fajitas +fake/ZGDRS +faker/M +fakir/SM +falafel +falcon/ZSRM +falconer/M +falconry/MS +fall/SGZMRN +fallacious/PY +fallaciousness/M +fallacy/MS +faller/M +fallibility/MSI +fallible/I +fallibleness/MS +fallibly/I +falloff/S +fallopian +fallout/MS +fallow/PSGD +fallowness/M +false/PTYR +falsehood/SM +falseness/SM +falsetto/SM +falsie/MS +falsifiability/M +falsifiable/U +falsification/M +falsifier/M +falsify/ZRSDNXG +falsity/MS +falter/RDSGJ +falterer/M +faltering/UY +fame/DSMG +famed/C +fames/C +familial +familiar/YPS +familiarity/MUS +familiarization/MS +familiarize/ZGRSD +familiarized/U +familiarizer/M +familiarizing/Y +familiarly/U +familiarness/M +family/MS +famine/SM +faming/C +famish/GSD +famous/PY +famously/I +famousness/M +fan/SM +fanatic/SM +fanatical/YP +fanaticalness/M +fanaticism/MS +fancied +fancier/SM +fanciest +fanciful/YP +fancifulness/MS +fancily +fanciness/SM +fancy/IS +fancying +fancywork/SM +fandango/SM +fanfare/SM +fanfold/M +fang/DMS +fangled +fanlight/SM +fanned +fanning +fanny/SM +fanout +fantail/SM +fantasia/SM +fantasist/M +fantasize/SRDG +fantastic/S +fantastical/Y +fantasy/GMSD +fanzine/S +far/GDR +farad/SM +faraway +farce/SDGM +farcical/Y +fare/MS +farer/M +farewell/DGMS +farfetchedness/M +farina/MS +farinaceous +farm/MRDGZSJ +farmer/M +farmhand/S +farmhouse/SM +farming/M +farmland/SM +farmstead/SM +farmworker/S +farmyard/MS +faro/MS +farrago/M +farragoes +farrier/SM +farrow/DMGS +farseeing +farsighted/YP +farsightedness/SM +fart/MDGS! +farther +farthermost +farthest +farthing/SM +fas +fascia/SM +fascicle/DSM +fasciculate/DNX +fasciculation/M +fascinate/SDNGX +fascinating/Y +fascination/M +fascism/MS +fascist/SM +fascistic +fashion's +fashion/ADSG +fashionable/PS +fashionableness/M +fashionably/U +fashioner/SM +fast/GTXSPRND +fastback/MS +fastball/S +fasten/AGUDS +fastener/MS +fastening/SM +fastidious/PY +fastidiousness/MS +fastness/MS +fat/PSGMDY +fatal/SY +fatalism/MS +fatalist/MS +fatalistic +fatalistically +fatality/MS +fatback/SM +fate/MS +fateful/YP +fatefulness/MS +fathead/SMD +fatheaded/P +father/DYMGS +fathered/U +fatherhood/MS +fatherland/SM +fatherless +fatherliness/M +fatherly/P +fathom/MDSBG +fathomable/U +fathomless +fatigue/MGSD +fatigued/U +fatiguing/Y +fatness/SM +fatso/M +fatted +fatten/JZGSRD +fattener/M +fatter +fattest/M +fattiness/SM +fatting +fatty/RSPT +fatuity/MS +fatuous/YP +fatuousness/SM +fatwa/SM +faucet/SM +fault/CGSMD +faultfinder/MS +faultfinding/MS +faultily +faultiness/MS +faultless/PY +faultlessness/SM +faulty/RTP +faun/MS +fauna/MS +fauvism/S +favor/ESMRDGZ +favorable/UMPS +favorableness/MU +favorably/U +favored's/U +favored/YPSM +favoredness/M +favorer/EM +favoring/MYS +favorings/U +favorite/SMU +favoritism/MS +favors/A +fawn/GZRDMS +fawner/M +fawning/Y +fax/GMDS +fay/MDRGS +faze/DSG +faïence/S +fealty/MS +fear/RDMSG +fearful/YP +fearfuller +fearfullest +fearfulness/MS +fearless/PY +fearlessness/MS +fearsome/PY +fearsomeness/M +feasibility/SM +feasible/UI +feasibleness/M +feasibly/U +feast/GSMRD +feaster/M +feat/MYRGTS +feater/C +feather/ZMDRGS +featherbed +featherbedding/SM +featherbrain/MD +feathered/U +feathering/M +featherless +featherlight +feathertop +featherweight/SM +feathery/TR +feats/C +feature/MGSD +featureless +febrile +fecal +feces +feckless/PY +fecklessness/M +fecund/I +fecundability +fecundate/XSDGN +fecundation/M +fecundity/SM +fed/U +federal/YS +federalism/SM +federalist/MS +federalization/MS +federalize/GSD +federate/FSDXVNG +federated/U +federation/FM +federative/Y +fedora/SM +feds +fee/MDS +feeble/TPR +feebleness/SM +feebly +feed/GRZJS +feedback/SM +feedbag/MS +feeder/M +feeding/M +feedlot/SM +feedstock +feedstuffs +feeing +feel/GZJRS +feeler/M +feeling/MYP +feelingly/U +feelingness/M +feet/M +feign/RDGS +feigned/U +feigner/M +feint/MDSG +feisty/RT +feldspar/MS +felicitate/XGNSD +felicitation/M +felicitous/IY +felicitousness/M +felicity/IMS +feline/SY +fell/PSGZTRD +fella/S +fellatio/SM +felled/A +feller/M +felling/A +fellness/M +fellow/SGDYM +fellowman +fellowmen +fellowship/SM +fellowshipped +fellowshipping +felon/MS +felonious/PY +feloniousness/M +felony/MS +felt/GSD +felting/M +fem/S +female/MPS +femaleness/SM +feminine/PYS +feminineness/M +femininity/MS +feminism/MS +feminist/MS +femme/MS +femoral +femur/MS +fen/MS +fence/SRDJGMZ +fenced/U +fencepost/M +fencer/M +fencing/M +fend/RDSCZG +fender/CM +fenestration/CSM +fenland/M +fennel/SM +fer/FLC +feral +ferment/FSCM +fermentation/MS +fermented +fermenter +fermenting +fermion/MS +fermium/MS +fern/MS +fernery/M +ferny/TR +ferocious/YP +ferociousness/MS +ferocity/MS +ferret/SMRDG +ferreter/M +ferric +ferris +ferrite/M +ferro +ferroelectric +ferromagnet/M +ferromagnetic +ferrous +ferrule/MGSD +ferry/SDMG +ferryboat/MS +ferryman/M +ferrymen +fertile/YP +fertileness/M +fertility/IMS +fertilization/ASM +fertilize/SRDZG +fertilized/U +fertilizer/M +fertilizes/A +ferule/SDGM +fervency/MS +fervent/Y +fervid/YP +fervidness/M +fervor/MS +fess's +fess/KGFSD +fest/RVZ +festal/S +fester/GD +festival/SM +festive/PY +festiveness/SM +festivity/SM +festoon/SMDG +feta/MS +fetal +fetch/RSDGZ +fetcher/M +fetching/Y +feted +fetich's +fetid/YP +fetidness/SM +feting +fetish/MS +fetishism/SM +fetishist/SM +fetishistic +fetlock/MS +fetter's +fetter/UGSD +fettle/GSD +fettling/M +fettuccine/S +fetus/SM +feud/MDSG +feudal/Y +feudalism/MS +feudalistic +feudatory/M +fever/SDMG +feverish/PY +feverishness/SM +few/PTRS +fewness/MS +fey/RT +fez/M +fezzes +ff +fiancé/MS +fiancée/S +fiasco/M +fiascoes +fiat/MS +fib/SZMR +fibbed +fibber/MS +fibbing +fiber/DM +fiberboard/MS +fiberfill/S +fiberglass/DSMG +fibril/MS +fibrillate/XGNDS +fibrillation/M +fibrin/MS +fibroblast/MS +fibroid/S +fibroses +fibrosis/M +fibrous/YP +fibrousness/M +fibula/M +fibulae +fibular +fices +fiche/SM +fichu/SM +fickle/RTP +fickleness/MS +ficos +fiction/SM +fictional/Y +fictionalization/MS +fictionalize/DSG +fictitious/PY +fictitiousness/M +fictive/Y +ficus +fiddle/GMZJRSD +fiddler/M +fiddlestick/SM +fiddly +fide/F +fidelity/IMS +fidget/DSG +fidgety +fiducial/Y +fiduciary/MS +fie/S +fief/MS +fiefdom/S +field/ZISMR +fielded +fielder/IM +fielding +fieldstone/M +fieldwork/ZMRS +fieldworker/M +fiend/MS +fiendish/YP +fiendishness/M +fierce/RPTY +fierceness/SM +fierily +fieriness/MS +fiery/PTR +fies/C +fiesta/MS +fife/DRSMZG +fifer/M +fifteen/HRMS +fifteenths +fifth/Y +fifths +fiftieths +fifty/HSM +fig/MLS +figged +figging +fight/ZSJRG +fightback +fighter/MIS +fighting/IS +figment/MS +figural +figuration/FSM +figurative/YP +figurativeness/M +figure's +figure/GFESD +figurehead/SM +figurer/SM +figurine/SM +figuring/S +filament/MS +filamentary +filamentous +filbert/MS +filch/SDG +file/KDRSGMZ +filed/AC +filename/SM +filer/KMCS +files/AC +filet's +filial/UY +filibuster/MDRSZG +filibusterer/M +filigree/MSD +filigreeing +filing/AC +filings +fill/BAJGSD +filled/U +filler/MS +fillet/MDSG +filleting/M +filling/M +fillip/MDGS +filly/SM +film/SGMD +filmdom/M +filminess/SM +filming/M +filmmaker/S +filmstrip/SM +filmy/RTP +filter/RDMSZGB +filtered/U +filterer/M +filth/M +filthily +filthiness/SM +filths +filthy/TRSDGP +filtrate/SDXMNG +filtrated/I +filtrates/I +filtrating/I +filtration/IMS +fin/TGMDRS +finagle/RSDZG +finagler/M +final/SY +finale/MS +finalist/MS +finality/MS +finalization/SM +finalize/GSD +finance/MGSDJ +financed/A +finances/A +financial/Y +financier/DMGS +financing/A +finch/MS +find/BRJSGZ +findable/U +finder/M +finding/M +fine's +fine/FGSCRDA +finely +fineness/MS +finery/MAS +finespun +finesse/SDMG +finger/SGRDMJ +fingerboard/SM +fingerer/M +fingering/M +fingerless +fingerling/M +fingernail/MS +fingerprint/SGDM +fingertip/MS +finial/SM +finical +finickiness/S +finicky/RPT +fining/M +finis/SM +finish/JZGRSD +finished/UA +finisher/M +finishes/A +finite/ISPY +finitely/C +finiteness/MIC +fink/GDMS +finned +finner +finning +finny/RT +fiord's +fir/ZGJMDRHS +fire/MS +firearm/SM +fireball/SM +fireboat/M +firebomb/MDSG +firebox/MS +firebrand/MS +firebreak/SM +firebrick/SM +firebug/SM +firecracker/SM +fired/U +firedamp/SM +firefight/JRGZS +firefly/MS +fireguard/M +firehouse/MS +firelight/GZSM +fireman/M +firemen +fireplace/MS +fireplug/MS +firepower/SM +fireproof/SGD +firer/M +firesafe +fireside/SM +firestorm/SM +firetrap/SM +firetruck/S +firewall/S +firewater/SM +firewood/MS +firework/MS +firing/M +firkin/M +firm's +firm/ISFDG +firmament/MS +firmer +firmest +firmly/I +firmness/MS +firmware/MS +firring +first/SY +firstborn/S +firsthand +firth/M +firths +fiscal/YS +fish/JGZMSRD +fishbowl/MS +fishcake/S +fisher/M +fisherman/M +fishermen/M +fishery/MS +fishhook/MS +fishily +fishiness/MS +fishing/M +fishmeal +fishmonger/MS +fishnet/SM +fishpond/SM +fishtail/DMGS +fishtanks +fishwife/M +fishwives +fishy/TPR +fissile +fission/BSDMG +fissionable/S +fissure/MGSD +fist/MDGS +fistfight/SM +fistful/MS +fisticuff/SM +fistula/SM +fistulous +fit's/K +fit/UYPS +fitful/PY +fitfulness/SM +fitments +fitness/USM +fits/AK +fitted/UA +fitter/SM +fittest +fitting/AU +fittingly +fittingness/M +fittings +five/MRS +fivefold +fiver/M +fix/USDG +fixable +fixate/VNGXSD +fixatifs +fixation/M +fixative/S +fixed/YP +fixedness/M +fixer/SM +fixes/I +fixing/SM +fixity/MS +fixture/SM +fizz/SRDG +fizzer/M +fizzle/GSD +fizzy/RT +fjord/SM +fl/GJD +flab/MS +flabbergast/GSD +flabbergasting/Y +flabbily +flabbiness/SM +flabby/TPR +flaccid/Y +flaccidity/MS +flack/SGDM +flag/MS +flagella/M +flagellate/DSNGX +flagellation/M +flagellum/M +flagged +flagging/SMY +flaggingly/U +flagman/M +flagmen +flagon/SM +flagpole/SM +flagrance/MS +flagrancy/SM +flagrant/Y +flagship/MS +flagstaff/MS +flagstone/SM +flail/SGMD +flair/SM +flak/RDMGS +flake/SM +flaker/M +flakiness/MS +flaky/PRT +flam/MRNDJGZ +flambeing +flambes +flamboyance/MS +flamboyancy/MS +flamboyant/YS +flambé/D +flame's +flame/SIGDR +flamen/M +flamenco/SM +flameproof/DGS +flamer/IM +flamethrower/SM +flaming/Y +flamingo/SM +flammability/ISM +flammable/SI +flan/MS +flange/GMSD +flank/SGZRDM +flanker/M +flannel/DMGS +flannelet/MS +flannelette's +flap/MS +flapjack/SM +flapped +flapper/SM +flapping +flaps/M +flare/SDG +flareup/S +flaring/Y +flash/JMRSDGZ +flashback/SM +flashbulb/SM +flashcard/S +flashcube/MS +flasher/M +flashgun/S +flashily +flashiness/SM +flashing/M +flashlight/MS +flashy/TPR +flask/SM +flat/MYPS +flatbed/S +flatboat/MS +flatcar/MS +flatfeet +flatfish/SM +flatfoot/SGDM +flathead/M +flatiron/SM +flatland/RS +flatmate/M +flatness/MS +flatted +flatten/SDRG +flattener/M +flatter/DRSZG +flatterer/M +flattering/YU +flattery/SM +flattest/M +flatting +flattish +flattop/MS +flatulence/SM +flatulent/Y +flatus/SM +flatware/MS +flatworm/SM +flaunt/SDG +flaunting/Y +flautist/SM +flavor/SJDRMZG +flavored/U +flavorer/M +flavorful +flavoring/M +flavorless +flavorsome +flaw/GDMS +flawless/PY +flawlessness/MS +flax/MSN +flaxseed/M +flay/RDGZS +flayer/M +flea/SM +fleabag/MS +fleabites +fleawort/M +fleck/GRDMS +fledge/GSD +fledged/U +fledgling/SM +flee/RS +fleece/RSDGMZ +fleecer/M +fleeciness/SM +fleecy/RTP +fleeing +fleet/MYRDGTPS +fleeting/YP +fleetingly/M +fleetingness/SM +fleetness/MS +flesh/JMYRSDG +flesher/M +fleshiness/M +fleshless +fleshly/TR +fleshpot/SM +fleshy/TPR +fletch/DRSGJ +fletcher/M +fletching/M +flew/S +flews/M +flex/MSDAG +flexed/I +flexibility/MSI +flexible/I +flexibly/I +flexitime's +flextime/S +flexural +flexure/M +flibbertigibbet/MS +flick/GZSRD +flicker/GD +flickering/Y +flickery +flier/M +flight/GMDS +flightiness/SM +flightless +flightpath +flighty/RTP +flimflam/MS +flimflammed +flimflamming +flimsily +flimsiness/MS +flimsy/PTRS +flinch/GDRS +flincher/M +flinching/U +fling/RMG +flinger/M +flint/MDSG +flintiness/M +flintless +flintlock/MS +flinty/TRP +flip/S +flipflop +flippable +flippancy/MS +flippant/Y +flipped +flipper/SM +flippest +flipping +flirt/GRDS +flirtation/SM +flirtatious/PY +flirtatiousness/MS +flit/S +flitted +flitting +float/SRDGJZ +floater/M +floaty +flocculate/GNDS +flocculation/M +flock/SJDMG +floe/MS +flog/S +flogged +flogger/SM +flogging/SM +flood/SMRDG +floodgate/MS +floodlight/DGMS +floodlit +floodplain/S +floodwater/SM +floor/SJRDMG +floorboard/MS +floorer/M +flooring/M +floorspace +floorwalker/SM +floozy/SM +flop/MS +flophouse/SM +flopped +flopper/M +floppily +floppiness/SM +flopping +floppy/TMRSP +flora/SM +floral/SY +florescence/MIS +florescent/I +floret/MS +florid/YP +floridness/SM +florin/MS +florist/MS +floss/GSDM +flossy/RST +flotation/SM +flotilla/SM +flotsam/SM +flounce/GDS +flouncing/M +flouncy/RT +flounder/SDG +flour/SGDM +flourish/GSRD +flourisher/M +flourishing/Y +floury/TR +flout/GZSRD +flouter/M +flow/ISG +flowchart/SG +flowed +flower's +flower/CSGD +flowerbed/SM +flowerer/M +floweriness/SM +flowerless +flowerpot/MS +flowery/TRP +flowing/Y +flown +flowstone +flt +flu/MS +flub/S +flubbed +flubbing +fluctuate/XSDNG +fluctuation/M +flue/SM +fluency/MS +fluent/SF +fluently +fluff/SGDM +fluffiness/SM +fluffy/PRT +fluid/MYSP +fluidity/SM +fluidized +fluidness/M +fluke/SDGM +fluky/RT +flume/SDGM +flummox/DSG +flung +flunk/SRDG +flunkey's +flunky/MS +fluoresce/GSRD +fluorescence/MS +fluorescent/S +fluoridate/XDSGN +fluoridation/M +fluoride/SM +fluorimetric +fluorinated +fluorine/SM +fluorite/MS +fluorocarbon/MS +fluoroscope/MGDS +fluoroscopic +flurry/GMDS +flush/TRSDPBG +flushness/M +fluster/DSG +flute/SRDGMJ +fluter/M +fluting/M +flutist/MS +flutter/DRSG +flutterer/M +fluttery +flux/IMS +fluxed/A +fluxes/A +fluxing +fly/JGBDRSTZ +flyaway +flyblown +flyby/M +flybys +flycatcher/MS +flyer's +flyleaf/M +flyleaves +flyover/MS +flypaper/MS +flysheet/S +flyspeck/MDGS +flyswatter/S +flyway/MS +flyweight/MS +flywheel/MS +foal/MDSG +foam/MRDSG +foaminess/MS +foamy/RPT +fob/SM +fobbed +fobbing +focal/F +focally +foci/M +focus/SRDMBG +focused/AU +focuser/M +focuses/A +fodder/GDMS +foe/SM +foetid +fog/SM +fogbound +fogged/C +foggily +fogginess/MS +fogging/C +foggy/RPT +foghorn/SM +fogs/C +fogy/SM +fogyish +foible/MS +foil/GSD +foist/GDS +fol/Y +fold/RDJSGZ +foldable/U +foldaway/S +folded/AU +folder/M +foldout/MS +folds/UA +foliage/MSD +foliate/CSDXGN +foliation/CM +folio/SDMG +folk/MS +folklike +folklore/MS +folkloric +folklorist/SM +folksiness/MS +folksinger/S +folksinging/S +folksong/S +folksy/TPR +folktale/S +folkway/S +foll +follicle/SM +follicular +follow/JSZBGRD +follower/M +followup's +folly/SM +foment/RDSG +fomentation/SM +fomenter/M +fond/PMYRDGTS +fondant/SM +fondle/GSRD +fondler/M +fondness/MS +fondue/MS +font/MS +fontanel/MS +fontanelle's +food/MS +foodie/S +foodstuff/MS +fool/MDGS +foolery/MS +foolhardily +foolhardiness/SM +foolhardy/PTR +foolish/PRYT +foolishness/SM +foolproof +foolscap/MS +foot/SMRDGZJ +footage/SM +football/SRDMGZ +footbridge/SM +footer/M +footfall/SM +foothill/SM +foothold/MS +footing/M +footless +footlights +footling +footlocker/SM +footloose +footman/M +footmarks +footmen +footnote/MSDG +footpad/SM +footpath/M +footpaths +footplate/M +footprint/MS +footrace/S +footrest/MS +footsie/SM +footsore +footstep/SM +footstool/SM +footwear/M +footwork/SM +fop/MS +fopped +foppery/MS +fopping +foppish/YP +foppishness/SM +for/HT +forage/GSRDMZ +forager/M +foray/SGMRD +forayer/M +forbade +forbear/MRSG +forbearance/SM +forbearer/M +forbid/S +forbidden +forbidding/YPS +forbiddingness/M +forbore +forborne +force/SRDGM +forced/Y +forcefield/MS +forceful/PY +forcefulness/MS +forceps/M +forcer/M +forcible/P +forcibleness/M +forcibly +ford/SMDBG +fordable/U +fore/S +forearm/GSDM +forebear/MS +forebode/GJDS +foreboding/PYM +forebodingness/M +forecast/SZGR +forecaster/M +forecastle/MS +foreclose/GSD +foreclosure/MS +forecourt/SM +foredoom/SDG +forefather/SM +forefeet +forefinger/MS +forefoot/M +forefront/SM +foregoer/M +foregoing/S +foregone +foregos +foreground/MGDS +forehand/S +forehead/MS +foreign/PRYZS +foreigner/M +foreignness/SM +foreknew +foreknow/GS +foreknowledge/MS +foreknown +foreleg/MS +forelimb/MS +forelock/MDSG +foreman/M +foremast/SM +foremen +foremost +forename/DSM +forenoon/SM +forensic/S +forensically +forensics/M +foreordain/DSG +forepart/MS +forepaws +forepeople +foreperson/S +foreplay/MS +forequarter/SM +forerunner/MS +foresail/SM +foresaw +foresee/ZSRB +foreseeable/U +foreseeing +foreseen/U +foreseer/M +foreshadow/SGD +foreshore/M +foreshorten/DSG +foresight/SMD +foresighted/PY +foresightedness/SM +foreskin/SM +forest's +forest/CSAGD +forestall/LGSRD +forestaller/M +forestallment/M +forestation/MCS +forestations/A +forester/SM +forestland/S +forestry/MS +foretaste/MGSD +foretell/RGS +foreteller/M +forethought/MS +foretold +forever/PS +forevermore +forewarn/GSJRD +forewarner/M +forewent +forewoman/M +forewomen +foreword/SM +forfeit/ZGDRMS +forfeiter/M +forfeiture/MS +forfend/GSD +forgather/GSD +forgave +forge/JVGMZSRD +forged/A +forger/M +forgery/MS +forges/A +forget/SV +forgetful/PY +forgetfulness/SM +forgettable/U +forgettably/U +forgetting +forging/M +forgivable/U +forgivably/U +forgive/SRPBZG +forgiven +forgiveness/SM +forgiver/M +forgiving/UP +forgivingly +forgivingness/M +forgo/RSGZ +forgoer/M +forgoes +forgone +forgot +forgotten/U +fork/GSRDM +forkful/S +forklift/DMSG +forlorn/PTRY +forlornness/M +form's +form/CGSAFDI +formability/AM +formal/IY +formaldehyde/SM +formalin/M +formalism/SM +formalist/SM +formalistic +formality/SMI +formalization/SM +formalize/ZGSRD +formalized/U +formalizer/M +formalizes/I +formalness/M +formals +formant/MIS +format's +format/AVS +formate/MXGNSD +formation/AFSCIM +formative/SYP +formatively/I +formativeness/IM +formatted/UA +formatter's +formatter/A +formatters +formatting/A +formed/U +former/FSAI +formerly +formfitting +formic +formidable/P +formidableness/M +formidably +formless/PY +formlessness/MS +formula/SM +formulaic +formulate/AGNSDX +formulated/U +formulation/AM +formulator/SM +fornicate/GNXSD +fornication/M +fornicator/SM +forsake/SG +forsaken +forsook +forsooth +forswear/SG +forswore +forsworn +forsythia/MS +fort/SM +forte/MS +forthcome/JG +forthcoming/U +forthright/PYS +forthrightness/SM +forthwith +fortieths +fortification/MS +fortified/U +fortifier/SM +fortify/ADSG +fortiori +fortissimo/S +fortitude/SM +fortnight/MYS +fortnightly/S +fortress/GMSD +fortuitous/YP +fortuitousness/SM +fortuity/MS +fortunate/YUS +fortunateness/M +fortune/MGSD +fortuneteller/SM +fortunetelling/SM +forty/SRMH +forum/MS +forward/PTZSGDRY +forwarder/M +forwarding/M +forwardness/MS +forwent +fossil/MS +fossiliferous +fossilization/MS +fossilize/GSD +fossilized/U +foster/SRDG +fosterer/M +fought +foul/SYRDGTP +foulard/SM +foulmouth/D +foulness/MS +fouls/M +found/RDGZS +foundation/SM +foundational +founded/UF +founder's/F +founder/MDG +founding/F +foundling/MS +foundry/MS +founds/KF +fount/MS +fountain/SMDG +fountainhead/SM +four/SHM +fourfold +fourpence/M +fourpenny +fourposter/SM +fourscore/S +foursome/SM +foursquare +fourteen/SMRH +fourteener/M +fourteenths +fourth/Y +fourths +fovea/M +fowl/SGMRD +fowler/M +fowling/M +fox/MDSG +foxfire/SM +foxglove/SM +foxhole/SM +foxhound/SM +foxily +foxiness/MS +foxing/M +foxtail/M +foxtrot/MS +foxtrotted +foxtrotting +foxy/TRP +foyer/SM +fps +fr +fracas/SM +fractal/SM +fraction/ISMA +fractional/Y +fractionate/DNG +fractionation/M +fractioned +fractioning +fractious/PY +fractiousness/SM +fracture/MGDS +fragile/Y +fragility/MS +fragment/SDMG +fragmentarily +fragmentariness/M +fragmentary/P +fragmentation/MS +fragrance/SM +fragrant/Y +frail/STPYR +frailness/MS +frailty/MS +frame/SRDJGMZ +framed/U +framer/M +framework/SM +framing/M +franc/SM +franchise's +franchise/ESDG +franchisee/S +franchiser/SM +francium/MS +francophone/M +frangibility/SM +frangible +frank/SGTYRDP +franker/M +frankfurter/MS +frankincense/MS +franklin/M +frankness/MS +frantic/PY +frantically +franticness/M +frappeed +frappeing +frappes +frappé +frat/MS +fraternal/Y +fraternity/MSF +fraternization/SM +fraternize/GZRSD +fraternizer/M +fraternizing/U +fratricidal +fratricide/MS +fraud's +fraud/CS +fraudsters +fraudulence/S +fraudulent/YP +fraught/SGD +fray's +fray/CSDG +frazzle/GDS +freak/SGDM +freakish/YP +freakishness/SM +freaky/RT +freckle/GMDS +freckly/RT +free/YTDRSP +freebase/GDS +freebie/MS +freeboot/ZR +freebooter/M +freeborn +freedman/M +freedmen +freedom/MS +freehand/D +freehanded/Y +freehold/ZSRM +freeholder/M +freeing/S +freelance/SRDGZM +freeload/SRDGZ +freeloader/M +freeman/M +freemasonry/M +freemen +freeness/M +freestanding +freestone/SM +freestyle/SM +freethinker/MS +freethinking/S +freeway/MS +freewheel/SRDMGZ +freewheeler/M +freewheeling/P +freewill +freezable +freeze/UGSA +freezer/SM +freezing/S +freight/ZGMDRS +freighter/M +frenetic/S +frenetically +frenzied/Y +frenzy/MDSG +freon/S +freq +frequency/ISM +frequent/IY +frequented/U +frequenter/MS +frequentest +frequenting +frequentness/M +frequents +fresco/DMG +frescoes +fresh/AZSRNDG +freshen/SZGDR +freshener/M +fresher/MA +freshest +freshet/SM +freshly +freshman/M +freshmen +freshness/MS +freshwater/SM +fret/S +fretboard +fretful/PY +fretfulness/MS +fretsaw/S +fretted +fretting +fretwork/MS +friable/P +friableness/M +friar/YMS +friary/MS +fricassee/MSD +fricasseeing +frication/M +fricative/MS +friction/MS +frictional/Y +frictionless/Y +fridge/SM +fried/A +friedcake/SM +friend/SGMYD +friendless/P +friendlessness/M +friendlies +friendlily +friendliness/USM +friendly/PUTR +friendship/MS +frier's +fries/M +frieze/SDGM +frig/S +frigate/SM +frigged +frigging/S +fright/GXMDNS +frighten/DG +frightening/Y +frightful/PY +frightfulness/MS +frigid/YP +frigidity/MS +frigidness/SM +frill/MDGS +frilly/RST +fringe's +fringe/IGSD +frippery/SM +frisk/RDGS +frisker/M +friskily +friskiness/SM +frisky/RTP +frisson/M +fritter/RDSG +fritterer/M +fritz/SM +frivolity/MS +frivolous/PY +frivolousness/SM +frizz/GYSD +frizzle/DSG +frizzly/RT +frizzy/RT +fro/HS +frock's +frock/SUDGC +frocking/M +frog/MS +frogged +frogging +frogman/M +frogmarched +frogmen +frolic/SM +frolicked +frolicker/SM +frolicking +frolicsome +from +frond/SM +front's +front/GSFRD +frontage/MS +frontal/SY +frontier/SM +frontiersman/M +frontiersmen +frontispiece/SM +frontrunner's +frontward/S +frosh/M +frost's +frost/CDSG +frostbit/G +frostbite/MS +frostbiting/M +frostbitten +frosted/U +frosteds +frostily +frostiness/SM +frosting/MS +frosty/PTR +froth/GMD +frothiness/SM +froths +frothy/TRP +froufrou/MS +froward/P +frowardness/MS +frown/RDSG +frowner/M +frowning/Y +frowzily +frowziness/SM +frowzy/RPT +froze/UA +frozen/YP +frozenness/M +fructify/GSD +fructose/MS +frugal/Y +frugality/SM +fruit/GMRDS +fruitcake/SM +fruiter/RM +fruiterer/M +fruitful/UYP +fruitfuller +fruitfullest +fruitfulness/MS +fruitiness/MS +fruition/SM +fruitless/YP +fruitlessness/MS +fruity/RPT +frump/MS +frumpish +frumpy/TR +frustrate/RSDXNG +frustrater/M +frustrating/Y +frustration/M +frustum/SM +fry/NGDS +fryer/MS +ft/C +fuchsia/MS +fuck/GZJRDMS! +fucker/M! +fuddle/GSD +fudge/GMSD +fuel's +fuel/ASDG +fueler/SM +fugal +fugitive/SYMP +fugitiveness/M +fugue/GMSD +fuhrer/S +fulcrum/SM +fulfill/GLSRD +fulfilled/U +fulfiller/M +fulfillment/MS +full/RDPSGZT +fullback/SMG +fuller/DMG +fullish +fullness/MS +fullstops +fullword/SM +fully +fulminate/XSDGN +fulmination/M +fulness's +fulsome/PY +fulsomeness/SM +fumble/GZRSD +fumbler/M +fumbling/Y +fume/DSG +fumigant/MS +fumigate/NGSDX +fumigation/M +fumigator/SM +fuming/Y +fumy/TR +fun/MS +function/GSMD +functional/YS +functionalism/M +functionalist/SM +functionality/S +functionary/MS +functor/SM +fund/ASMRDZG +fundamental/SY +fundamentalism/SM +fundamentalist/SM +funded/U +fundholders +fundholding +funding/S +funeral/MS +funerary +funereal/Y +funfair/M +fungal/S +fungi/M +fungible/M +fungicidal +fungicide/SM +fungoid/S +fungous +fungus/M +funicular/SM +funk/GSDM +funkiness/S +funky/RTP +funned +funnel/SGMD +funner +funnest +funnily/U +funniness/SM +funning +funny/RSPT +fur/PMS +furbelow/MDSG +furbish/GDRSA +furbisher/M +furious/RYP +furiousness/M +furl/UDGS +furlong/MS +furlough/DGM +furloughs +furn +furnace/GMSD +furnish/GASD +furnished/U +furnisher/MS +furnishing/SM +furniture/SM +furor/MS +furore/MS +furred +furrier/M +furriness/SM +furring/SM +furrow/DMGS +furry/RTZP +further/TGDRS +furtherance/MS +furtherer/M +furthermore +furthermost +furthest +furtive/PY +furtiveness/SM +fury/SM +furze/SM +fuse's/A +fuse/FSDAGCI +fusebox/S +fusee/SM +fuselage/SM +fusibility/SM +fusible/I +fusiform +fusilier/MS +fusillade/SDMG +fusion/KMFSI +fuss/SRDMG +fussbudget/MS +fusser/M +fussily +fussiness/MS +fusspot/SM +fussy/PTR +fustian/MS +fustiness/MS +fusty/RPT +fut +futile/PY +futileness/M +futility/MS +futon/S +future/SM +futurism/SM +futurist/S +futuristic/S +futurity/MS +futurologist/S +futurology/MS +futz/GSD +fuze's +fuzz/SDMG +fuzzily +fuzziness/SM +fuzzy/PRT +fwd +fwy +fête/MS +g's +g/VBX +gab/S +gabardine/SM +gabbed +gabbiness/S +gabbing +gabble/SDG +gabby/TRP +gaberdine's +gabfest/MS +gable/GMSRD +gad/S +gadabout/MS +gadded +gadder/MS +gadding +gadfly/MS +gadget/SM +gadgetry/MS +gadolinium/MS +gaff/SGZRDM +gaffe/MS +gaffer/M +gag/DRSG +gaga +gage/SM +gager/M +gagged +gagging +gaggle/SDG +gagwriter/S +gaiety/MS +gaily +gain/ADGS +gainer/SM +gainful/YP +gainfulness/M +gaining/S +gainly/U +gainsaid +gainsay/RSZG +gainsayer/M +gait/GSZMRD +gaiter/M +gal's +gal/AS +gala/SM +galactic +galaxy/MS +gale's +gale/AS +galen +galena/MS +galenite/M +gall/SGMD +gallant/UY +gallanted +gallanting +gallantry/MS +gallants +gallbladder/MS +galleon/SM +galleria/S +gallery/MSDG +galley/MS +gallimaufry/MS +galling/Y +gallium/SM +gallivant/GDS +gallon/SM +gallonage/M +gallop/GSRDZ +galloper/M +gallows/M +gallstone/MS +galoot/MS +galore/S +galosh/GMSD +galumph/GD +galumphs +galvanic +galvanism/MS +galvanization/SM +galvanize/SDG +galvanometer/SM +galvanometric +gambit/MS +gamble/GZRSD +gambler/M +gambol/SGD +game/PJDRSMYTZG +gamecock/SM +gamekeeper/MS +gameness/MS +gamesmanship/SM +gamesmen +gamest/RZ +gamester/M +gamete/MS +gametic +gamin/MS +gamine/SM +gaminess/MS +gaming/M +gamma/MS +gammon/DMSG +gamut/MS +gamy/TRP +gander/DMGS +gang/GRDMS +gangbusters +ganger/M +gangland/SM +ganglia/M +gangling +ganglion/M +ganglionic +gangplank/SM +gangrene/SDMG +gangrenous +gangster/SM +gangway/MS +gannet/SM +gantlet/GMDS +gantry/MS +gaol/MRDGZS +gaoler/M +gap/SJMDRG +gape/S +gaper/M +gaping/Y +gapped +gapping +gar/SLM +garage/GMSD +garb/DMGS +garbage/SDMG +garbageman/M +garbanzo/MS +garble/RSDG +garbler/M +garden/ZGRDMS +gardener/M +gardenia/SM +gardening/M +garfish/MS +gargantuan +gargle/SDG +gargoyle/DSM +garish/YP +garishness/MS +garland/SMDG +garlic/SM +garlicked +garlicking +garlicky +garment/MDGS +garner/SGD +garnet/SM +garnish/DSLG +garnishee/SDM +garnisheeing +garnishment/MS +garote's +garotte's +garred +garret/SM +garring +garrison/SGMD +garrote/SRDMZG +garroter/M +garrotte's +garrulity/SM +garrulous/PY +garrulousness/MS +garter/SGDM +garçon/SM +gas's +gas/FC +gasbag/MS +gaseous/YP +gaseousness/M +gases/C +gash/GTMSRD +gasification/M +gasifier/M +gasify/SRDGXZN +gasket/SM +gaslight/DMS +gasohol/S +gasoline/MS +gasometer/M +gasp/GZSRD +gasper/M +gasping/Y +gassed/C +gasser/MS +gassiness/M +gassing/SM +gassy/PTR +gastric +gastritides +gastritis/MS +gastroenteritides +gastroenteritis/M +gastrointestinal +gastronome/SM +gastronomic +gastronomical/Y +gastronomy/MS +gastropod/SM +gasworks/M +gate/MGDS +gateau/MS +gateaux +gatecrash/GZSRD +gatehouse/MS +gatekeeper/SM +gatepost/SM +gateway/MS +gather/JRDZGS +gathered/IA +gatherer/M +gathering/M +gathers/A +gator/MS +gauche/TYPR +gaucheness/SM +gaucherie/SM +gaucho/SM +gaudily +gaudiness/MS +gaudy/PRST +gauge/SM +gaugeable +gauger/M +gaunt/PYRDSGT +gauntlet/GSDM +gauntness/MS +gauss's +gauss/C +gausses +gauze/SDGM +gauziness/MS +gauzy/TRP +gave +gavel/GMDS +gavotte/MSDG +gawk/SGRDM +gawkily +gawkiness/MS +gawky/RSPT +gay/RTPS +gayety's +gayness/SM +gaze/DRSZG +gazebo/SM +gazelle/MS +gazer/M +gazette/MGSD +gazetteer/SGDM +gazillion/S +gazpacho/MS +gear/DMJSG +gearbox/SM +gearing/M +gearshift/MS +gearstick +gearwheel/SM +gecko/MS +gee/TDS +geegaw's +geeing +geek/SM +geeky/RT +geese/M +geest/M +geezer/MS +geisha/M +gel/MBS +gelatin/SM +gelatinous/PY +gelatinousness/M +gelcap +geld/JSGD +gelding/M +gelid +gelignite/MS +gelled +gelling +gem/MS +gemlike +gemmed +gemming +gemological +gemologist/MS +gemology/MS +gemstone/SM +gen +gendarme/MS +gender/DMGS +genderless +gene/MS +genealogical/Y +genealogist/SM +genealogy/MS +genera/M +general/MSPY +generalissimo/SM +generalist/MS +generality/MS +generalizable/SM +generalization/MS +generalize/GZBSRD +generalized/U +generalizer/M +generalness/M +generalship/SM +generate/CXAVNGSD +generation/MCA +generational +generative/AY +generator/SM +generators/A +generic/PS +generically +generosity/MS +generous/PY +generously/U +generousness/SM +genes/S +genesis/M +genetic/S +genetically +geneticist/MS +genetics/M +genial/PY +geniality/FMS +genially/F +genialness/M +genie/SM +genies/K +genii/M +genital/YF +genitalia +genitals +genitive/SM +genitourinary +genius/SM +genocidal +genocide/SM +genome/SM +genotype/MS +genre/MS +gent/AMS +genteel/PRYT +genteelness/MS +gentian/SM +gentile/S +gentility/MS +gentle/PRSDGT +gentlefolk/S +gentleman/YM +gentlemanliness/M +gentlemanly/U +gentlemen +gentleness/SM +gentlewoman/M +gentlewomen/M +gently +gentrification/M +gentrify/NSDGX +gentry/MS +genuflect/GDS +genuflection/MS +genuine/PY +genuineness/SM +genus +geocentric +geocentrically +geocentricism +geochemical/Y +geochemistry/MS +geochronology/M +geode/SM +geodesic/S +geodesy/MS +geodetic/S +geog +geographer/MS +geographic +geographical/Y +geography/MS +geologic +geological/Y +geologist/MS +geology/MS +geom +geomagnetic +geomagnetically +geomagnetism/SM +geometer/MS +geometric/S +geometrical/Y +geometrician/M +geometry/MS +geomorphological +geomorphology/M +geophysical/Y +geophysicist/MS +geophysics/M +geopolitic/S +geopolitical/Y +geopolitics/M +geostationary +geosynchronous +geosyncline/SM +geothermal +geothermic +geranium/SM +gerbil/MS +geriatric/S +geriatrics/M +germ/MNS +germane +germanium/SM +germanized +germen/M +germicidal +germicide/MS +germinal/Y +germinate/XVGNSD +germinated/U +germination/M +germinative/Y +gerontocracy/M +gerontological +gerontologist/SM +gerontology/SM +gerrymander/SGD +gerund/SVM +gerundive/M +gestalt/M +gestapo/S +gestate/SDGNX +gestation/M +gestational +gesticulate/XSDVGN +gesticulation/M +gesticulative/Y +gestural +gesture/SDMG +gesundheit +get/S +getaway/SM +getter/SDM +getting +getup/MS +gewgaw/MS +geyser/GDMS +ghastliness/MS +ghastly/TPR +ghat/MS +gherkin/SM +ghetto/DGMS +ghettoize/SDG +ghost/SMYDG +ghostlike +ghostliness/MS +ghostly/TRP +ghostwrite/RSGZ +ghostwritten +ghostwrote +ghoul/SM +ghoulish/PY +ghoulishness/SM +giant/SM +giantess/MS +giantkiller +gibber/DGS +gibberish/MS +gibbet/MDSG +gibbon/MS +gibbous/YP +gibbousness/M +gibe/GDRS +giber/M +giblet/MS +giddap +giddily +giddiness/SM +giddy/GPRSDT +gift/SGMD +gifted/PY +giftedness/M +gig/MS +gigabyte/S +gigacycle/MS +gigahertz/M +gigantic/P +gigantically +giganticness/M +gigavolt +gigawatt/M +gigged +gigging +giggle/RSDGZ +giggler/M +giggling/Y +giggly/TR +gigolo/MS +gila +gilbert/M +gild/JSGZRD +gilder/M +gilding/M +gill/SGMRD +gilt/S +gimbaled +gimbals +gimcrack/S +gimcrackery/SM +gimlet/MDSG +gimme/S +gimmick/GDMS +gimmickry/MS +gimmicky +gimp/GSMD +gimpy/RT +gin/MS +ginger/SGDYM +gingerbread/SM +gingerliness/M +gingerly/P +gingersnap/SM +gingery +gingham/SM +gingivitis/SM +ginkgo/M +ginkgoes +ginmill +ginned +ginning +ginseng/SM +giraffe/MS +gird/RDSGZ +girded/U +girder/M +girdle/GMRSD +girdler/M +girl/MS +girlfriend/MS +girlhood/SM +girlie/M +girlish/YP +girlishness/SM +giro/M +girt/GDS +girth/MDG +girths +gist/MS +git/M +give/HZGRS +giveaway/SM +giveback/S +given/SP +giver/M +giving/Y +gizmo's +gizzard/SM +glacial/Y +glaciate/XNGDS +glaciation/M +glacier/SM +glaciological +glaciologist/M +glaciology/M +glacé/DGS +glad/YSP +gladded +gladden/GDS +gladder +gladdest +gladding +gladdy +glade/SM +gladiator/SM +gladiatorial +gladiola/MS +gladioli +gladiolus/M +gladly/RT +gladness/MS +gladsome/RT +glamor/DMGS +glamorization/MS +glamorize/SRDZG +glamorizer/M +glamorous/PY +glamorousness/M +glance/GJSD +glancing/Y +gland/ZSM +glanders/M +glandes +glandular/Y +glans/M +glare/SDG +glaring/YP +glaringness/M +glasnost/S +glass/GSDM +glassblower/S +glassblowing/MS +glassful/MS +glasshouse/SM +glassily +glassiness/SM +glassless +glassware/SM +glasswort/M +glassy/PRST +glaucoma/SM +glaucous +glaze/SRDGZJ +glazed/U +glazer/M +glazier/SM +glazing/M +gleam/MDGS +glean/RDGZJS +gleaner/M +gleaning/M +glee/DSM +gleed/M +gleeful/YP +gleefulness/MS +gleeing +glen/SM +glib/YP +glibber +glibbest +glibness/MS +glide/JGZSRD +glider/M +glim/M +glimmer/DSJG +glimmering/M +glimpse/DRSZMG +glimpser/M +glint/DSG +glissandi +glissando/M +glisten/DSG +glister/DGS +glitch/MS +glitter/GDSJ +glittering/Y +glittery +glitz/GSD +glitzy/TR +gloaming/MS +gloat/SRDG +gloater/M +gloating/Y +glob/GDMS +global/SY +globalism/S +globalist/S +globe/SM +globetrotter/MS +globular/PY +globularity/M +globularness/M +globule/MS +globulin/MS +glockenspiel/SM +glommed +gloom/GSMD +gloomily +gloominess/MS +gloomy/RTP +glop/MS +glopped +glopping +gloppy/TR +glorification/M +glorifier/M +glorify/XZRSDNG +glorious/IYP +gloriousness/IM +glory/SDMG +gloss/GSDM +glossary/MS +glossily +glossiness/SM +glossolalia/SM +glossy/RSPT +glottal +glottalization/M +glottis/MS +glove/SRDGMZ +gloveless +glover/M +glow/GZRDMS +glower/GD +glowing/Y +glowworm/SM +glucose/SM +glue/DRSMZG +glued/U +gluer/M +gluey +gluier +gluiest +glum/SYP +glummer +glummest +glumness/MS +gluon/M +glut/SMNX +glutamate/M +gluten/M +glutenous +glutinous/PY +glutinousness/M +glutted +glutting +glutton/MS +gluttonous/Y +gluttony/SM +glyceride/M +glycerin/SM +glycerinate/MD +glycerine's +glycerol/SM +glycerolized/C +glycine/M +glycogen/SM +glycol/MS +glyph/M +glyphs +gm +gnarl/SMDG +gnash/SDG +gnat/MS +gnaw/GRDSJ +gnawer/M +gnawing/M +gneiss/SM +gnome/SM +gnomelike +gnomic +gnomish +gnomonic +gnostic/K +gnosticism +gnu/MS +go/MRHZGJ +goad/MDSG +goal/MDSG +goalie/SM +goalkeeper/MS +goalkeeping/M +goalless +goalmouth/M +goalpost/S +goalscorer +goalscoring +goaltender/SM +goat/MS +goatee/SM +goatherd/MS +goatskin/SM +gob/SM +gobbed +gobbet/MS +gobbing +gobble/SRDGZ +gobbledegook's +gobbledygook/S +gobbler/M +goblet/MS +goblin/SM +god/SMY +godchild/M +godchildren +goddammit +goddamn/GS +goddaughter/SM +godded +goddess/MS +godding +godfather/GSDM +godforsaken +godhead/S +godhood/SM +godless/P +godlessness/MS +godlike/P +godlikeness/M +godliness/UMS +godly/UTPR +godmother/MS +godparent/SM +godsend/MS +godson/MS +goer/MG +goes +gofer/SM +goggle/SRDGZ +goggler/M +going/M +goiter/SM +gold/MRNGTS +goldbrick/GZRDMS +goldbricker/M +golden/TRYP +goldenness/M +goldenrod/SM +goldenseal/M +goldfinch/MS +goldfish/SM +goldmine/S +goldsmith/M +goldsmiths +golf/RDMGZS +golfer/M +golly/S +gonad/SM +gonadal +gondola/SM +gondolier/MS +gone/RZN +goner/M +gong/SGDM +gonion/M +gonna +gonorrhea/MS +gonorrheal +goo/MS +goober/MS +good/SYP +goodbye/MS +goodhearted +goodie's +goodish +goodly/TR +goodness/MS +goodnight +goodwill/MS +goody/SM +gooey +goof/SDMG +goofiness/MS +goofy/RPT +gooier +gooiest +gook/SM +goon/SM +goop/SM +goos/SDG +goose/M +gooseberry/MS +goosebumps +gopher/SM +gore/DSMG +gorge/GMSRD +gorged/E +gorgeous/YP +gorgeousness/SM +gorger/EM +gorges/E +gorging/E +gorgon/S +gorilla/MS +gorily +goriness/MS +goring/M +gormandize/SRDGZ +gormandizer/M +gormless +gorp/S +gorse/SM +gory/PRT +gos +gosh/S +goshawk/MS +gosling/M +gospel/MRSZ +gospeler/M +gossamer/SM +gossip/ZGMRDS +gossipy +got/IU +gotcha/SM +goto +gotta +gotten/U +gouge/GZSRD +gouger/M +goulash/SM +gourd/MS +gourde/SM +gourmand/MS +gourmet/MS +gout/SM +gouty/RT +gov/S +govern/LBGSD +governable/U +governance/SM +governed/U +governess/SM +government/MS +governmental/Y +governor/MS +governorship/SM +govt +gown/GSDM +gr +grab/S +grabbed +grabber/SM +grabbing/S +grace/ESDMG +graceful/EYPU +gracefuller +gracefullest +gracefulness/ESM +graceless/PY +gracelessness/MS +gracious/UY +graciousness/SM +grackle/SM +grad/MRDGZJS +gradate/DSNGX +gradation/MCS +grade's +grade/ACSDG +graded/U +gradely +grader/MC +gradient/RMS +gradual/SYP +gradualism/MS +gradualist/MS +gradualness/MS +graduand/SM +graduate/MNGDSX +graduation/M +graffiti +graffito/M +graft/MRDSGZ +grafter/M +grafting/M +graham/SM +grail/S +grain's +grain/IGSD +grainer/M +graininess/MS +graining/M +grainy/RTP +gram/KSM +grammar/MS +grammarian/SM +grammatic/K +grammatical/UY +grammaticality/M +grammaticalness/M +gramme/SM +gramophone/SM +grampus/SM +granary/MS +grand/TPSYR +grandam/SM +grandaunt/MS +grandchild/M +grandchildren +granddad/SM +granddaddy/MS +granddaughter/MS +grandee/SM +grandeur/MS +grandfather/MYDSG +grandiloquence/SM +grandiloquent/Y +grandiose/YP +grandiosity/MS +grandkid/SM +grandma/MS +grandmaster/MS +grandmother/MYS +grandnephew/MS +grandness/MS +grandniece/SM +grandpa/MS +grandparent/MS +grandson/MS +grandstand/SRDMG +grandstander/M +granduncle/MS +grange/MSR +granite/MS +granitic +granny/MS +granola/S +grant/SGZMRD +grantee/MS +granter/M +grantor's +grantsmanship/S +granular/Y +granularity/SM +granulate/SDXVGN +granulation/M +granule/SM +granulocytic +grape/SDGM +grapefruit/SM +grapeshot/M +grapevine/MS +graph/GMD +grapheme/M +graphic/PS +graphical/Y +graphicness/M +graphics/M +graphite/SM +graphologist/SM +graphology/MS +graphs +grapnel/SM +grapple/DRSG +grappler/M +grappling/M +grasp/SRDBG +grasper/M +grasping/PY +graspingness/M +grass/GZSDM +grasshopper/SM +grassland/MS +grassroots +grassy/RT +grate/SRDJGZ +grateful/YPU +gratefuller +gratefullest +gratefulness/USM +grater/M +grates/I +graticule/M +gratification/M +gratified/U +gratify/NDSXG +gratifying/Y +grating/YM +gratis +gratitude/IMS +gratuitous/PY +gratuitousness/MS +gratuity/SM +gravamen/SM +grave/SRDPGMZTY +gravedigger/SM +gravel/SGMYD +graven +graveness/MS +graver/M +graveside/S +gravestone/SM +graveyard/MS +gravid/PY +gravidness/M +gravimeter/SM +gravimetric +gravitas +gravitate/XVGNSD +gravitation/M +gravitational/Y +graviton/SM +gravity/MS +gravy/SM +gray/PYRDGTS +graybeard/MS +grayish +grayness/S +graze/GZSRD +grazer/M +grazing/M +grease/GMZSRD +greasepaint/MS +greaseproof +greaser/M +greasily +greasiness/SM +greasy/PRT +great/SPTYRN +greatcoat/DMS +greaten/DG +greathearted +greatness/MS +grebe/MS +greed's +greed/C +greedily +greediness/SM +greeds +greedy/RTP +green/SYRDMPGT +greenback/MS +greenbelt/S +greenery/MS +greenfield +greenfly/M +greengage/SM +greengrocer/SM +greengrocery/M +greenhorn/SM +greenhouse/SM +greening/M +greenish/P +greenmail/GDS +greenness/MS +greenroom/SM +greensward/SM +greenwood/MS +greet/SRDJGZ +greeter/M +greeting/M +greets/A +gregarious/PY +gregariousness/MS +gremlin/SM +grenade/MS +grenadier/SM +grenadine/SM +grew/A +greybeard/M +greyhound/MS +greyness/M +grid/SGM +gridded +griddle/DSGM +griddlecake/SM +gridiron/GSMD +gridlock/DSG +grids/A +grief/MS +grievance/SM +grieve/SRDGZ +griever/M +grieving/Y +grievous/PY +grievousness/SM +griffin/SM +griffon's +grill/RDGS +grille/SM +griller/M +grillwork/M +grim/PGYD +grimace/DRSGM +grimacer/M +grime/MS +griminess/MS +grimmer +grimmest +grimness/MS +grimy/TPR +grin/S +grind/ASG +grinder/MS +grinding/SY +grindstone/SM +gringo/SM +grinned +grinner/M +grinning/Y +grip/SGZMRD +gripe/S +griper/M +grippe/GMZSRD +gripper/M +gripping/Y +grisliness/SM +grisly/RPT +grist/MYS +gristle/SM +gristliness/M +gristly/TRP +gristmill/MS +grit/MS +gritted +gritter/MS +grittiness/SM +gritting +gritty/PRT +grizzle/DSG +grizzling/M +grizzly/TRS +groan/GZSRDM +groaner/M +groat/SM +grocer/MS +grocery/MS +grog/MS +groggily +grogginess/SM +groggy/RPT +groin/MGSD +grok/S +grokked +grokking +grommet/GMDS +groofs +groom/GZSMRD +groomer/M +groomsman/M +groomsmen +groove/SRDGM +groover/M +groovy/TR +grope/SRDJGZ +groper/M +grosbeak/SM +grosgrain/MS +gross/GTYSRDP +grossness/MS +grotesque/PSY +grotesqueness/MS +grotto/M +grottoes +grouch/GDS +grouchily +grouchiness/MS +grouchy/RPT +ground/JGZMDRS +groundbreaking/S +grounded/U +grounder/M +groundhog/SM +groundless/YP +groundlessness/M +groundnut/MS +groundsheet/M +groundskeepers +groundsman/M +groundswell/S +groundwater/S +groundwork/SM +group/ZJSMRDG +grouped/A +grouper/M +groupie/MS +grouping/M +groups/A +grouse/GMZSRD +grouser/M +grout/GSMRD +grouter/M +grove/SRMZ +grovel/SDRGZ +groveler/M +grovelike +groveling/Y +grow/GZYRHS +grower/M +growing/I +growingly +growl/RDGZS +growler/M +growling/Y +growly/RP +grown/IA +grownup/MS +grows/A +growth/IMA +growths/IA +grub/MS +grubbed +grubber/SM +grubbily +grubbiness/SM +grubbing +grubby/RTP +grubstake/MSDG +grudge/GMSRDJ +grudger/M +grudging/Y +gruel/MDGJS +grueling/Y +gruesome/RYTP +gruesomeness/SM +gruff/PSGTYRD +gruffness/MS +grumble/GZJDSR +grumbler/M +grumbling/Y +grump/MDGS +grumpily +grumpiness/MS +grumpy/TPR +grunge/S +grungy/RT +grunion/SM +grunt/SGRD +grunter/M +gryphon's +gs/A +gt +guacamole/MS +guanine/MS +guano/MS +guarani/SM +guarantee/RSDZM +guaranteeing +guarantor/SM +guaranty/MSDG +guard/RDSGZ +guarded/UYP +guardedness/UM +guarder/M +guardhouse/SM +guardian/SM +guardianship/MS +guardrail/SM +guardroom/SM +guardsman/M +guardsmen +guava/SM +gubernatorial +gudgeon/M +guernsey/S +guerrilla/MS +guess/BGZRSD +guessable/U +guessed/U +guesser/M +guesstimate/DSMG +guesswork/MS +guest/SGMD +guff/SM +guffaw/GSDM +guidance/MS +guide/GZSRD +guidebook/SM +guided/U +guideline/SM +guidepost/MS +guider/M +guild/SZMR +guilder/M +guildhall/SM +guile/SDGM +guileful +guileless/YP +guilelessness/MS +guillemot/MS +guillotine/SDGM +guilt/SM +guiltily +guiltiness/MS +guiltless/YP +guiltlessness/M +guilty/PTR +guinea/SM +guise's +guise/SDEG +guitar/SM +guitarist/SM +gulag/S +gulch/MS +gulden/MS +gulf/DMGS +gull/MDSG +gullet/MS +gulley's +gullibility/MS +gullible +gully/SDMG +gulp/RDGZS +gum/MS +gumbo/MS +gumboil/MS +gumboots +gumdrop/SM +gummed +gumminess/M +gumming/C +gummy/RTP +gumption/SM +gumshoe/SDM +gumshoeing +gumtree/MS +gun/MS +gunboat/MS +gunfight/SRMGZ +gunfighter/M +gunfire/SM +gunflint/M +gunfought +gunk/SM +gunky/RT +gunman/M +gunmen +gunmetal/MS +gunned +gunnel's +gunner/SM +gunnery/MS +gunning/M +gunny/SM +gunnysack/SM +gunpoint/MS +gunpowder/SM +gunrunner/MS +gunrunning/MS +gunship/S +gunshot/SM +gunsling/GZR +gunslinger/M +gunsmith/M +gunsmiths +gunwale/MS +guppy/SM +gurgle/SDG +gurney/S +guru/MS +gush/SRDGZ +gusher/M +gushy/TR +gusset/MDSG +gussy/GSD +gust/MDGS +gustatory +gusted/E +gustily +gustiness/M +gusting/E +gusto/M +gustoes +gusts/E +gusty/RPT +gut/SM +gutless/P +gutlessness/S +guts/R +gutser/M +gutsiness/M +gutsy/PTR +gutted +gutter/GSDM +guttering/M +guttersnipe/M +gutting +guttural/SPY +gutturalness/M +gutty/RSMT +guy/MDRZGS +guzzle/GZRSD +guzzler/M +gym/MS +gymkhana/SM +gymnasia's +gymnasium/SM +gymnast/SM +gymnastic/S +gymnastically +gymnastics/M +gymnosperm/SM +gynecologic +gynecological/MS +gynecologist/SM +gynecology/MS +gyp/S +gypped +gypper/S +gypping +gypsite +gypster/S +gypsum/MS +gypsy/SDMG +gyrate/XNGSD +gyration/M +gyrator/MS +gyrfalcon/SM +gyro/MS +gyrocompass/M +gyroscope/SM +gyroscopic +gyve/GDS +h'm +h's +h/EMS +ha/H +habeas +haberdasher/SM +haberdashery/SM +habiliment/SM +habit's +habit/IBDGS +habitability/MS +habitable/P +habitableness/M +habitant/ISM +habitat/MS +habitation/MI +habitations +habitual/SYP +habitualness/SM +habituate/SDNGX +habituation/M +habitué/MS +hacienda/MS +hack/GZSDRBJ +hacker/M +hackle/RSDMG +hackler/M +hackney/SMDG +hacksaw/SDMG +hackwork/S +had/GD +haddock/MS +hades +hadj's +hadji's +hadn't +hadron/MS +hadst +haemoglobin's +haemophilia's +haemorrhage's +hafnium/MS +haft/GSMD +hag/SMN +haggard/SYP +haggardness/MS +hagged +hagging +haggis/SM +haggish +haggle/RSDZG +haggler/M +hagiographer/SM +hagiography/MS +hahnium/S +haiku/M +hail/SGMDR +hailer/M +hailstone/SM +hailstorm/SM +hair/SDM +hairball/SM +hairbreadth/M +hairbreadths +hairbrush/SM +haircare +haircloth/M +haircloths +haircut/MS +haircutting +hairdo/SM +hairdresser/SM +hairdressing/SM +hairdryer/S +hairiness/MS +hairless/P +hairlessness/M +hairlike +hairline/SM +hairnet/MS +hairpiece/MS +hairpin/MS +hairsbreadth +hairsbreadths +hairsplitter/SM +hairsplitting/MS +hairspray +hairspring/SM +hairstyle/SMG +hairstylist/S +hairy/PTR +hajj/M +hajjes +hajji/MS +hake/MS +halal/S +halalled +halalling +halberd/SM +halcyon/S +hale/ISRDG +haler/IM +halest +half/PM +halfback/SM +halfbreed +halfhearted/PY +halfheartedness/MS +halfpence/S +halfpenny/MS +halfpennyworth +halftime/S +halftone/MS +halfway +halfword/MS +halibut/SM +halide/SM +halite/MS +halitoses +halitosis/M +hall/SMR +hallelujah +hallelujahs +halliard's +hallmark/SGMD +hallo/GDS +halloo's +hallow/UD +hallowing +hallows +hallucinate/VNGSDX +hallucination/M +hallucinatory +hallucinogen/SM +hallucinogenic/S +hallway/SM +halo/SDMG +halocarbon +halogen/SM +halogenated +halon +halt/GZJSMDR +halter/GDM +halting/Y +halve/GZDS +halves/M +halyard/MS +ham/SM +hamburg/SZRM +hamburger/M +hamlet/MS +hammed +hammer/ZGSRDM +hammerer/M +hammerhead/SM +hammering/M +hammerless +hammerlock/MS +hammertoe/SM +hamming +hammock/MS +hammy/RT +hamper/GSD +hampered/U +hamster/MS +hamstring/MGS +hamstrung +hand's +hand/UDSG +handbag/MS +handbagged +handbagging +handball/SM +handbarrow/MS +handbasin +handbill/MS +handbook/SM +handbrake/M +handcar/SM +handcart/MS +handclasp/MS +handcraft/GMDS +handcuff/GSD +handcuffs/M +handed/PY +handedness/M +hander/S +handful/SM +handgun/SM +handhold/M +handicap/SM +handicapped +handicapper/SM +handicapping +handicraft/SMR +handicraftsman/M +handicraftsmen +handily/U +handiness/SM +handiwork/MS +handkerchief/MS +handle/MZGRSD +handleable +handlebar/SM +handler/M +handless +handling/M +handmade +handmaid/NMSX +handmaiden/M +handout/SM +handover +handpick/GDS +handrail/SM +handsaw/SM +handset/SM +handshake/GMSR +handshaker/M +handshaking/M +handsome/RPTY +handsomely/U +handsomeness/MS +handspike/SM +handspring/SM +handstand/MS +handwork/SM +handwoven +handwrite/GSJ +handwriting/M +handwritten +handy/URT +handyman/M +handymen +hang/GDRZBSJ +hangar/SGDM +hangdog/S +hanged/A +hanger/M +hanging/M +hangman/M +hangmen +hangnail/MS +hangout/MS +hangover/SM +hangs/A +hangup/S +hank/GZDRMS +hanker/GRDJ +hankerer/M +hankering/M +hankie/SM +hanky's +hansom/MS +hap/SMY +haphazard/SPY +haphazardness/SM +hapless/YP +haplessness/MS +haploid/S +happed +happen/JDGS +happening/M +happenstance/SM +happily/U +happiness/UMS +happing +happy/UTPR +harangue/GDRS +haranguer/M +harass/LSRDZG +harasser/M +harassment/SM +harbinger/DMSG +harbor/ZGRDMS +harborer/M +hard/YNRPJGXTS +hardback/SM +hardball/SM +hardboard/SM +hardboiled +hardbound +hardcore/MS +hardcover/SM +harden/ZGRD +hardened/U +hardener/M +hardening/M +hardhat/S +hardheaded/YP +hardheadedness/SM +hardhearted/YP +hardheartedness/SM +hardihood/MS +hardily +hardiness/SM +hardliner/S +hardness/MS +hardscrabble +hardshell +hardship/MS +hardstand/S +hardtack/MS +hardtop/MS +hardware/SM +hardwire/DSG +hardwood/MS +hardworking +hardy/PTRS +hare/MGDS +harebell/MS +harebrained +harelip/MS +harelipped +harem/SM +hark/GDS +harlequin/MS +harlot/SM +harlotry/MS +harm/MDRGS +harmed/U +harmer/M +harmful/PY +harmfulness/MS +harmless/YP +harmlessness/SM +harmonic/S +harmonica/MS +harmonically +harmonics/M +harmonious/IPY +harmoniousness's/I +harmoniousness/MS +harmonium/MS +harmonization's +harmonization/A +harmonizations +harmonize/ZGSRD +harmonized/U +harmonizer/M +harmonizes/UA +harmony/EMS +harness/DRSMG +harnessed/U +harnesser/M +harnesses/U +harp/MDRJGZS +harper/M +harping/M +harpist/SM +harpoon/SZGDRM +harpooner/M +harpsichord/SM +harpsichordist/MS +harpy/SM +harridan/SM +harrier/M +harrow/RDMGS +harrower/M +harrumph/SDG +harry/RSDGZ +harsh/TRNYP +harshen/GD +harshness/SM +hart/MS +harvest/MDRZGS +harvested/U +harvester/M +harvestman/M +has +hash's +hash/AGSD +hasher/M +hashing/M +hashish/MS +hasn't +hasp/GMDS +hassle/MGRSD +hassock/MS +hast/GXJDN +haste/MS +hasten/GRD +hastener/M +hastily +hastiness/MS +hasty/RPT +hat/MDRSZG +hatch/RSDJG +hatchback/SM +hatcheck/S +hatched/U +hatcher/M +hatchery/MS +hatchet/MDSG +hatching/M +hatchway/MS +hate/S +hateful/YP +hatefulness/MS +hater/M +hatless +hatred/SM +hatstands +hatted +hatter/SM +hatting +hauberk/SM +haughtily +haughtiness/SM +haughty/TPR +haul/SDRGZ +haulage/MS +hauler/M +haunch/GMSD +haunt/JRDSZG +haunter/M +haunting/Y +hauteur/MS +have/ZGSR +haven't +haven/DMGS +haver/G +haversack/SM +havoc/SM +havocked +havocking +haw/MDSG +hawk/GZSDRM +hawker/M +hawking/M +hawkish/P +hawkishness/S +haws/RZ +hawser/M +hawthorn/MS +hay/GSMDR +haycock/SM +hayfield/MS +hayloft/MS +haymow/MS +hayrick/MS +hayride/MS +hayseed/MS +haystack/SM +haywain +haywire/MS +hazard/MDGS +hazardous/PY +hazardousness/M +haze/DSRJMZG +hazel/MS +hazelnut/SM +hazer/M +hazily +haziness/MS +hazing/M +hazy/PTR +hdqrs +he'd +he'll +he/VMZ +head/SJGZMDR +headache/MS +headband/SM +headboard/MS +headcount +headdress/MS +header/M +headfirst +headgear/SM +headhunt/ZGSRDMJ +headhunter/M +headhunting/M +headily +headiness/S +heading/M +headlamp/S +headland/MS +headless/P +headlessness/M +headlight/MS +headline/DRSZMG +headliner/M +headlock/MS +headlong +headman/M +headmaster/MS +headmastership/M +headmen +headmistress/MS +headphone/SM +headpiece/SM +headpin/MS +headquarter/GDS +headrest/MS +headroom/SM +headscarf/M +headset/SM +headship/SM +headshrinker/MS +headsman/M +headsmen +headstall/SM +headstand/MS +headstock/M +headstone/MS +headstrong +headwaiter/SM +headwall/S +headwater/S +headway/MS +headwind/SM +headword/MS +heady/PTR +heal/DRHSGZ +healed/U +healer/M +health/M +healthful/U +healthfully +healthfulness/SM +healthily/U +healthiness/MSU +healths +healthy/URPT +heap/SMDG +hear/ZTSRHJG +heard/UA +hearer/M +hearing/AM +hearken/SGD +hears/SDAG +hearsay/SM +hearse/M +heart/SMDNXG +heartache/SM +heartbeat/MS +heartbreak/GMS +heartbreaking/Y +heartbroke +heartbroken +heartburn/SGM +heartburning/M +hearted/Y +hearten/EGDS +heartening/EY +heartfelt +hearth/M +hearthrug +hearths +hearthstone/MS +heartily +heartiness/SM +heartland/SM +heartless/YP +heartlessness/SM +heartrending/Y +heartsick/P +heartsickness/MS +heartstrings +heartthrob/MS +heartwarming +heartwood/SM +hearty/TRSP +heat/SMDRGZBJ +heated/UA +heatedly +heater/M +heath/MRNZX +heathen/M +heathendom/SM +heathenish/Y +heathenism/MS +heather/M +heathery +heathland +heaths +heatproof +heats/A +heatstroke/MS +heatwave +heave/DSRGZ +heaven/SYM +heavenliness/M +heavenly/PTR +heavenward/S +heaver/M +heaves/M +heavily +heaviness/MS +heavy/TPRS +heavyhearted +heavyset +heavyweight/SM +hebephrenic +hecatomb/M +heck/S +heckle/RSDZG +heckler/M +hectare/MS +hectic/S +hectically +hectogram/MS +hectometer/SM +hector/SGD +hedge/DSRGMZ +hedgehog/MS +hedgehop/S +hedgehopped +hedgehopping +hedger/M +hedgerow/SM +hedging/Y +hedonism/SM +hedonist/MS +hedonistic +heed/SMGD +heeded/U +heedful/PY +heedfulness/M +heeding/U +heedless/YP +heedlessness/SM +heehaw/DGS +heel/SGZMDR +heeler/M +heeling/M +heelless +heft/GSD +heftily +heftiness/SM +hefty/TRP +hegemonic +hegemony/MS +hegira/S +heifer/MS +height/SMNX +heighten/GD +heinous/PY +heinousness/SM +heir/SDMG +heiress/MS +heirloom/MS +heist/GSMRD +heister/M +held +helical/Y +helices/M +helicon/M +helicopter/GSMD +heliocentric +heliography/M +heliosphere +heliotrope/SM +heliport/MS +helium/MS +helix/M +hell/GSMDR +hellbender/M +hellbent +hellcat/SM +hellebore/SM +heller/M +hellfire/M +hellhole/SM +hellion/SM +hellish/PY +hellishness/SM +hello/GMS +helluva +helm's +helm/U +helmed +helmet/GSMD +helming +helms +helmsman/M +helmsmen +helot/S +help/GZSJDR +helper/M +helpful/UY +helpfulness/MS +helping/M +helpless/YP +helplessness/SM +helpline/S +helpmate/SM +helpmeet's +helve/GMDS +hem/MS +hematite/MS +hematologic +hematological +hematologist/SM +hematology/MS +heme/MS +hemisphere/MSD +hemispheric +hemispherical +hemline/SM +hemlock/MS +hemmed +hemmer/SM +hemming +hemoglobin/MS +hemolytic +hemophilia/SM +hemophiliac/SM +hemorrhage/GMDS +hemorrhagic +hemorrhoid/MS +hemostat/SM +hemp/MNS +hemstitch/DSMG +hen/MS +hence/S +henceforth +henceforward +henchman/M +henchmen +henge/M +henna/MDSG +henning +henpeck/GSD +henry/M +hep/S +heparin/MS +hepatic/S +hepatitides +hepatitis/M +hepper +heppest +heptagon/SM +heptagonal +heptane/M +heptathlon/S +her +herald/MDSG +heralded/U +heraldic +heraldry/MS +herb/MS +herbaceous +herbage/MS +herbal/S +herbalism +herbalist/MS +herbicidal +herbicide/MS +herbivore/SM +herbivorous/Y +herculean +herd/MDRGZS +herder/M +herdsman/M +herdsmen +here's +here/IS +hereabout/S +hereafter/S +hereby +hereditary +heredity/MS +herein +hereinafter +hereof +hereon +heres/M +heresy/SM +heretic/SM +heretical +hereto +heretofore +hereunder +hereunto +hereupon +herewith +heritable +heritage/MS +heritor/IM +hermaphrodite/SM +hermaphroditic +hermeneutic/S +hermeneutics/M +hermetic/S +hermetical/Y +hermit/MS +hermitage/SM +hermitian +hernia/MS +hernial +herniate/NGXDS +hero/M +heroes +heroic/U +heroically +heroics +heroin/MS +heroine/SM +heroism/SM +heron/SM +herpes/M +herpetologist/SM +herpetology/MS +herring/SM +herringbone/SDGM +herself +hertz/M +hes +hesitance/S +hesitancy/SM +hesitant/U +hesitantly +hesitate/XDRSNG +hesitater/M +hesitating/UY +hesitation/M +heterodox +heterodoxy/MS +heterodyne +heterogamous +heterogamy/M +heterogeneity/SM +heterogeneous/PY +heterogeneousness/M +heterosexual/YMS +heterosexuality/SM +heterostructure +heterozygous +heuristic/SM +heuristically +hew/DRZGS +hewer/M +hex/DSRG +hexachloride/M +hexadecimal/YS +hexafluoride/M +hexagon/SM +hexagonal/Y +hexagram/SM +hexameter/SM +hexer/M +hey +heyday/MS +hf +hgt +hgwy +hi/D +hiatus/SM +hibachi/MS +hibernate/XGNSD +hibernation/M +hibernator/SM +hibiscus/MS +hiccup/MDGS +hick/SM +hickey/SM +hickory/MS +hid/ZDRGJ +hidden/U +hide/S +hideaway/SM +hidebound +hideous/YP +hideousness/SM +hideout/MS +hider/M +hiding/M +hie/S +hieing +hierarchal +hierarchic +hierarchical/Y +hierarchy/SM +hieratic +hieroglyph +hieroglyphic/S +hieroglyphics/M +hieroglyphs +hifalutin +high/PYRT +highball/GSDM +highborn +highboy/MS +highbrow/SM +highchair/SM +highfalutin +highhanded/PY +highhandedness/SM +highish +highland/ZSRM +highlight/GZRDMS +highness/MS +highpoint +highroad/MS +highs +hight +hightail/DGS +highway/MS +highwayman/M +highwaymen +hijack/JZRDGS +hijacker/M +hike/ZGDSR +hiker/M +hilarious/YP +hilariousness/MS +hilarity/MS +hill/GSMDR +hillbilly/MS +hiller/M +hilliness/SM +hillman +hillmen +hillock/SM +hillside/SM +hilltop/MS +hillwalking +hilly/TRP +hilt/MDGS +him/S +himself +hind/RSZ +hinder/GRD +hindered/U +hinderer/M +hindmost +hindquarter/SM +hindrance/SM +hindsight/SM +hinge's +hinge/UDSG +hinger +hint/GZMDRS +hinter/M +hinterland/MS +hip/PSM +hipbone/SM +hipness/S +hipped +hipper +hippest +hippie/MTRS +hipping/M +hippo/MS +hippodrome/MS +hippopotamus/SM +hippy's +hipster/MS +hiragana +hire/AGSD +hireling/SM +hirer/SM +hiring/S +hirsute/P +hirsuteness/MS +his +hiss/DSRMJG +hisser/M +hissing/M +hist/SDG +histamine/SM +histidine/SM +histochemic +histochemical +histochemistry/M +histogram/MS +histological +histologist/MS +histology/SM +historian/MS +historic +historical/PY +historicalness/M +historicism/M +historicist/M +historicity/MS +historiographer/SM +historiography/MS +history/MS +histrionic/S +histrionically +histrionics/M +hit/MS +hitch/UGSD +hitcher/MS +hitchhike/RSDGZ +hither +hitherto +hitless +hittable +hitter/SM +hitting +hive/MGDS +ho/DRYZ +hoar/M +hoard/RDJZSGM +hoarder/M +hoarding/M +hoarfrost/SM +hoariness/MS +hoarse/RTYP +hoarseness/SM +hoary/TPR +hoax/GZMDSR +hoaxer/M +hob/SM +hobbed +hobbing +hobbit +hobble/ZSRDG +hobbler/M +hobby/SM +hobbyhorse/SM +hobbyist/SM +hobgoblin/MS +hobnail/GDMS +hobnob/S +hobnobbed +hobnobbing +hobo/SDMG +hoc +hock/GDRMS +hocker/M +hockey/SM +hockshop/SM +hod/SM +hodge/MS +hodgepodge/SM +hoe/SM +hoecake/SM +hoedown/MS +hoeing +hoer/M +hog/SM +hogan/SM +hogback/MS +hogged +hogger +hogging +hoggish/Y +hogshead/SM +hogtie/SD +hogtying +hogwash/SM +hoist/GRDS +hoister/M +hoke/DSG +hokey/PRT +hokier +hokiest +hokum/MS +hold/NRBSJGZ +holdall/MS +holder/M +holding's +holding/IS +holdout/SM +holdover/SM +holdup/MS +hole/MGDS +holey +holiday/GRDMS +holidaymaker/S +holier/U +holiness/MSU +holistic +holistically +hollandaise +holler/GDS +hollow/RDYTGSP +hollowness/MS +hollowware/M +holly/SM +hollyhock/MS +holmium/MS +holocaust/MS +hologram/SM +holograph/GMD +holographic +holographs +holography/MS +holster/MDSG +holy/SRTP +holystone/MS +homage/MGSRD +homager/M +hombre/SM +homburg/SM +home/DSRMYZG +homebody/MS +homebound +homeboy/S +homebuilder/S +homebuilding +homebuilt +homecoming/MS +homegrown +homeland/SM +homeless/P +homelessness/SM +homelike +homeliness/SM +homely/RPT +homemade +homemake/JRZG +homemaker/M +homemaking/M +homeomorph/M +homeomorphic +homeomorphism/MS +homeopath +homeopathic +homeopaths +homeopathy/MS +homeostases +homeostasis/M +homeostatic +homeowner/S +homeownership +homepage +homer/GDM +homerists +homeroom/MS +homeschooling/S +homesick/P +homesickness/MS +homespun/S +homestead/GZSRDM +homesteader/M +homestretch/SM +hometown/SM +homeward +homework/ZSMR +homeworker/M +homey/PS +homeyness/MS +homicidal/Y +homicide/SM +homier +homiest +homiletic/S +homily/SM +hominess's +homing/M +hominid/MS +hominy/SM +homo/SM +homogamy/M +homogenate/MS +homogeneity/ISM +homogeneous/PY +homogenization/MS +homogenize/DRSGZ +homogenizer/M +homograph/M +homographs +homological +homologous +homologue/M +homology/MS +homomorphic +homomorphism/SM +homonym/SM +homophobia/S +homophobic +homophone/MS +homopolymers +homosexual/YMS +homosexuality/SM +homotopy +homozygous/Y +hon/MDRSZTG +honcho/DSG +hone/SM +honest/RYT +honestly/E +honesty/ESM +honey/GSMD +honeybee/SM +honeycomb/SDMG +honeydew/SM +honeylocust +honeymoon/RDMGZS +honeymooner/M +honeysuckle/MS +hong/M +honk/GZSDRM +honker/M +honky/SM +honor's +honor/ERDBZGS +honorable/PSM +honorableness/SM +honorables/U +honorablies/U +honorably/UE +honorarily +honorarium/SM +honorary/S +honored/U +honoree/S +honorer/EM +honorific/S +honors/A +hooch/MS +hood/MDSG +hooded/P +hoodedness/M +hoodlum/SM +hoodoo/DMGS +hoodwink/SRDG +hoodwinker/M +hooey/SM +hoof/DRMSG +hoofer/M +hoofmark/S +hook/GZDRMS +hookah/M +hookahs +hooked/P +hookedness/M +hooker/M +hookey's +hooks/U +hookup/SM +hookworm/MS +hooky/SRMT +hooligan/SM +hooliganism/SM +hoop/MDRSG +hooper/M +hoopla/SM +hooray/SMDG +hoosegow/MS +hoot/MDRSGZ +hootch's +hootenanny/SM +hooter/M +hooves/M +hop/SMDRG +hope/SM +hoped/U +hopeful/SPY +hopefulness/MS +hopeless/YP +hopelessness/SM +hoper/M +hopped +hopper/MS +hopping/M +hoppled +hopples +hopscotch/MDSG +horde/DSGM +horehound/MS +horizon/MS +horizontal/YS +hormonal/Y +hormone/MS +horn/GDRMS +hornbeam/M +hornblende/MS +horned/P +hornedness/M +hornet/MS +horniness/M +hornless +hornlike +hornpipe/MS +horny/TRP +horologic +horological +horologist/MS +horology/MS +horoscope/MS +horrendous/Y +horrible/SP +horribleness/SM +horribly +horrid/PY +horridness/M +horrific +horrifically +horrify/DSG +horrifying/Y +horror/MS +hors/DSGX +horse's +horse/UGDS +horseback/MS +horsedom +horseflesh/M +horsefly/MS +horsehair/SM +horsehide/SM +horselaugh/M +horselaughs +horseless +horselike +horsely +horseman/M +horsemanship/MS +horsemen +horseplay/SMR +horseplayer/M +horsepower/SM +horseradish/SM +horseshoe/MRSD +horseshoeing +horseshoer/M +horsetail/SM +horsewhip/SM +horsewhipped +horsewhipping +horsewoman/M +horsewomen +horsey +horsier +horsiest +horsing/M +hortatory +horticultural +horticulture/SM +horticulturist/SM +hos/GDS +hosanna/SDG +hose/M +hosepipe +hosier/MS +hosiery/SM +hosp +hospice/MS +hospitable/I +hospitably/I +hospital/MS +hospitality's/I +hospitality/MS +hospitalization/MS +hospitalize/GSD +host/MYDGS +hostage/MS +hostel/SZGMRD +hosteler/M +hostelry/MS +hostess/MDSG +hostile/YS +hostility/SM +hostler/MS +hot/PSY +hotbed/MS +hotblooded +hotbox/MS +hotcake/S +hotchpotch/M +hotel/MS +hotelier/MS +hotelman/M +hotfoot/DGS +hothead/DMS +hotheaded/PY +hotheadedness/SM +hothouse/MGDS +hotness/MS +hotplate/SM +hotpot/M +hotrod +hotshot/S +hotted +hotter +hottest +hotting +hough/M +hound/MRDSG +hounder/M +hounding/M +hour/YMS +hourglass/MS +houri/MS +hourly/S +house's +house/ASDG +houseboat/SM +housebound +houseboy/SM +housebreak/JSRZG +housebreaker/M +housebreaking/M +housebroke +housebroken +housebuilding +houseclean/JDSG +housecleaning/M +housecoat/MS +housefly/MS +houseful/SM +household/ZRMS +householder/M +househusband/S +housekeep/JRGZ +housekeeper/M +housekeeping/M +houselights +housemaid/MS +houseman/M +housemen +housemother/MS +housemoving +houseparent/SM +houseplant/S +houser +housetop/MS +housewares +housewarming/MS +housewife/YM +housewifeliness/M +housewifely/P +housewives +housework/ZSMR +houseworker/M +housing/MS +hove/ZR +hovel/GSMD +hover/GRD +hovercraft/M +hoverer/M +how/SM +howbeit +howdah/M +howdahs +howdy/GSD +however +howitzer/MS +howl/GZSMDR +howler/M +howsoever +hoy/M +hoyden/DMGS +hoydenish +hp +hr +hrs +ht +huarache/SM +hub/MS +hubba +hubbub/SM +hubby/SM +hubcap/SM +hubris/SM +huckleberry/SM +huckster/SGMD +huddle/RSDMG +huddler/M +hue/MDS +huff/SGDM +huffily +huffiness/SM +huffy/TRP +hug/RTS +huge/YP +hugeness/MS +hugged +hugger +hugging/S +huh +huhs +hula/MDSG +hulk/GDMS +hull/MDRGZS +hullabaloo/SM +huller/M +hulling/M +hullo/GSDM +hum/S +human/IPY +humane/IY +humaneness/SM +humaner +humanest +humanism/SM +humanist/SM +humanistic +humanitarian/S +humanitarianism/SM +humanity/ISM +humanization/CSM +humanize/RSDZG +humanized/C +humanizer/M +humanizes/IAC +humanizing/C +humankind/M +humanness/IM +humannesses +humanoid/S +humans +humble/TZGPRSDJ +humbleness/SM +humbly +humbug/MS +humbugged +humbugging +humdinger/MS +humdrum/S +humeral/S +humeri +humerus/M +humid/Y +humidification/MC +humidifier/CM +humidify/RSDCXGNZ +humidistat/M +humidity/MS +humidor/MS +humiliate/SDXNG +humiliating/Y +humiliation/M +humility/MS +hummed +hummer/SM +humming +hummingbird/SM +hummock/MDSG +hummocky +hummus/S +humongous +humor/RDMZGS +humored/U +humorist/MS +humorless/PY +humorlessness/MS +humorous/YP +humorousness/MS +hump/GSMD +humpback/SMD +humph/DG +humphs +humus/SM +hunch/GMSD +hunchback/DSM +hundred/SHRM +hundredfold/S +hundredths +hundredweight/SM +hung/A +hunger/SDMG +hungover +hungrily +hungriness/SM +hungry/RTP +hunk/ZRMS +hunker/DG +hunky/RST +hunt/GZJDRS +hunter/M +hunting/M +huntress/MS +huntsman/M +huntsmen +hurdle/JMZGRSD +hurdler/M +hurl/DRGZJS +hurler/M +hurling/M +hurray/SDG +hurricane/MS +hurried/UY +hurriedness/M +hurry/RSDG +hurt/U +hurter/M +hurtful/PY +hurtfulness/MS +hurting/Y +hurtle/SDG +hurts +husband/GSDRYM +husbander/M +husbandman/M +husbandmen +husbandry/SM +hush/DSG +husk/SGZDRM +husker/M +huskily +huskiness/MS +husking/M +husky/RSPT +hussar/MS +hussy/SM +hustings/M +hustle/RSDZG +hustler/M +hut/MS +hutch/MSDG +hutted +hutting +huzzah/GD +huzzahs +hwy +hyacinth/M +hyacinths +hyaena's +hybrid/MS +hybridism/SM +hybridization/S +hybridize/GSD +hydra/MS +hydrangea/SM +hydrant/SM +hydrate's +hydrate/CSDNGX +hydration/MC +hydraulic/S +hydraulically +hydraulicked +hydraulicking +hydraulics/M +hydrazine/M +hydride/MS +hydro/SM +hydrocarbon/SM +hydrocephali +hydrocephalus/MS +hydrochemistry +hydrochloric +hydrochloride/M +hydrodynamic/S +hydrodynamical +hydrodynamics/M +hydroelectric +hydroelectrically +hydroelectricity/SM +hydrofluoric +hydrofoil/MS +hydrogen/MS +hydrogenate's +hydrogenate/CDSGN +hydrogenation/MC +hydrogenations +hydrogenous +hydrological/Y +hydrologist/MS +hydrology/SM +hydrolysis/M +hydrolyze/GSD +hydrolyzed/U +hydromagnetic +hydromechanics/M +hydrometer/SM +hydrometry/MS +hydrophilic +hydrophobia/SM +hydrophobic +hydrophone/SM +hydroplane/DSGM +hydroponic/S +hydroponics/M +hydrosphere/MS +hydrostatic/S +hydrostatics/M +hydrotherapy/SM +hydrothermal/Y +hydrous +hydroxide/MS +hydroxy +hydroxyl/SM +hydroxylate/N +hydroxyzine/M +hyena/MS +hygiene/MS +hygienic/S +hygienically +hygienics/M +hygienist/MS +hygrometer/SM +hygroscopic +hying +hymen/MS +hymeneal/S +hymn/GSDM +hymnal/SM +hymnbook/S +hype/MZGDSR +hyperactive/S +hyperactivity/SM +hyperbola/MS +hyperbole/MS +hyperbolic +hyperbolically +hyperboloid/SM +hyperboloidal +hypercellularity +hypercritical/Y +hypercube/MS +hyperemia/M +hyperemic +hyperfine +hypergamous/Y +hypergamy/M +hyperglycemia/MS +hyperinflation +hypermarket/SM +hypermedia/S +hyperplane/SM +hyperplasia/M +hypersensitive/P +hypersensitiveness/MS +hypersensitivity/MS +hypersonic +hyperspace/M +hypersphere/M +hypertension/MS +hypertensive/S +hypertext/SM +hyperthyroid +hyperthyroidism/MS +hypertrophy/MSDG +hypervelocity +hyperventilate/XSDGN +hyperventilation/M +hyphen/DMGS +hyphenate/NGXSD +hyphenated/U +hyphenation/M +hypnoses +hypnosis/M +hypnotherapy/SM +hypnotic/S +hypnotically +hypnotism/MS +hypnotist/SM +hypnotize/SDG +hypo/DMSG +hypoactive +hypoallergenic +hypocellularity +hypochondria/MS +hypochondriac/SM +hypocrisy/SM +hypocrite/MS +hypocritical/Y +hypodermic/S +hypoglycemia/SM +hypoglycemic/S +hypophyseal +hypophysectomized +hypotenuse/MS +hypothalami +hypothalamic +hypothalamically +hypothalamus/M +hypothermia/SM +hypotheses +hypothesis/M +hypothesize/ZGRSD +hypothesizer/M +hypothetic +hypothetical/Y +hypothyroid +hypothyroidism/SM +hypoxia/M +hyssop/MS +hysterectomy/MS +hysteresis/M +hysteria/SM +hysteric/SM +hysterical/YU +i +i's +iamb/MS +iambi +iambic/S +iambus/SM +ibex/MS +ibid +ibidem +ibis/SM +ibuprofen/S +ice's +ice/GDSC +iceberg/SM +iceboat/MS +icebound +icebox/MS +icebreaker/SM +icecap/SM +iceman/M +icemen +icepack +icepick/S +ichneumon/M +ichthyologist/MS +ichthyology/MS +icicle/SM +icily +iciness/SM +icing/MS +icky/RT +icon/MS +iconic +iconoclasm/MS +iconoclast/MS +iconoclastic +iconography/MS +icosahedra +icosahedral +icosahedron/M +ictus/SM +icy/RPT +id/MY +idea/SM +ideal/MYS +idealism/MS +idealist/MS +idealistic +idealistically +idealization/MS +idealize/GDRSZ +idealized/U +idealizer/M +idealogical +ideate/SN +ideation/M +idem +idempotent/S +identical/YP +identicalness/M +identifiability +identifiable/U +identifiably +identification/M +identified/U +identifier/M +identify/XZNSRDG +identity/SM +ideogram/MS +ideograph/M +ideographic +ideographs +ideological/Y +ideologist/SM +ideologue/S +ideology/SM +ides +idiocy/MS +idiolect/M +idiom/MS +idiomatic/P +idiomatically +idiopathic +idiosyncrasy/SM +idiosyncratic +idiosyncratically +idiot/MS +idiotic +idiotically +idle/PZTGDSR +idleness/MS +idler/M +idol/MS +idolater/MS +idolatress/S +idolatrous +idolatry/SM +idolization/SM +idolize/ZGDRS +idolized/U +idolizer/M +ids +idyll/MS +idyllic +idyllically +if +iffiness/S +iffy/TPR +ifs +igloo/MS +igneous +ignitable +ignite/ASDG +igniter/M +ignition/MS +ignoble/P +ignobleness/M +ignobly +ignominious/Y +ignominy/MS +ignoramus/SM +ignorance/MS +ignorant/SPY +ignorantness/M +ignore/SRDGB +ignorer/M +iguana/MS +ii +iii +ikon's +ilea +ileitides +ileitis/M +ileum/M +ilia +iliac +ilium/M +ilk/MS +ill/PS +illegal/YS +illegality/MS +illegibility/MS +illegible +illegibly +illegitimacy/SM +illegitimate/SDGY +illiberal/Y +illiberality/SM +illicit/YP +illicitness/MS +illimitable/P +illimitableness/M +illiquid +illiteracy/MS +illiterate/PSY +illiterateness/M +illness/MS +illogic/M +illogical/PY +illogicality/SM +illogicalness/M +illume/DG +illuminate/XSDVNG +illuminating/U +illuminatingly +illumination/M +illumine/BGSD +illus/V +illusion's +illusion/ES +illusionary +illusionist/MS +illusive/PY +illusiveness/M +illusoriness/M +illusory/P +illustrate/VGNSDX +illustrated/U +illustration/M +illustrative/Y +illustrator/SM +illustrious/PY +illustriousness/SM +illy +image/DSGM +imagery/MS +imaginable/U +imaginableness +imaginably/U +imaginariness/M +imaginary/PS +imagination/MS +imaginative/UY +imaginativeness/M +imagine/RSDJBG +imagined/U +imaginer/M +imago/M +imagoes +imam/MS +imbalance/SDM +imbecile/YMS +imbecilic +imbecility/MS +imbibe/ZRSDG +imbiber/M +imbrication/SM +imbroglio/MS +imbruing +imbue/GDS +imitable/I +imitate/SDVNGX +imitation/M +imitative/YP +imitativeness/MS +imitator/SM +immaculate/YP +immaculateness/SM +immanence/S +immanency/MS +immanent/Y +immaterial/PY +immateriality/MS +immaterialness/MS +immature/SPY +immatureness/M +immaturity/MS +immeasurable/P +immeasurableness/M +immeasurably +immediacy/MS +immediate/YP +immediateness/SM +immemorial/Y +immense/PRTY +immenseness/M +immensity/MS +immerse/RSDXNG +immersible +immersion/M +immigrant/SM +immigrate/NGSDX +immigration/M +imminence/SM +imminent/YP +imminentness/M +immobile +immobility/MS +immobilization/MS +immobilize/DSRG +immoderate/NYP +immoderateness/M +immoderation/M +immodest/Y +immodesty/SM +immolate/SDNGX +immolation/M +immoral/Y +immorality/MS +immortal/SY +immortality/SM +immortalize/GDS +immortalized/U +immovability/SM +immovable/PS +immovableness/M +immovably +immune/S +immunity/SM +immunization/MS +immunize/GSD +immunoassay/M +immunodeficiency/S +immunodeficient +immunologic +immunological/Y +immunologist/SM +immunology/MS +immure/GSD +immutability/MS +immutable/P +immutableness/M +immutably +imp/SGMDRY +impact/VGMRDS +impaction/SM +impactor/SM +impair/LGRDS +impaired/U +impairer/M +impairment/SM +impala/MS +impale/GLRSD +impalement/SM +impaler/M +impalpable +impalpably +impanel/DGS +impart/GDS +impartation/M +impartial/Y +impartiality/SM +impassable/P +impassableness/M +impassably +impasse/SXBMVN +impassibility/SM +impassible +impassibly +impassion/DG +impassioned/U +impassive/YP +impassiveness/MS +impassivity/MS +impasto/SM +impatience/SM +impatiens/M +impatient/Y +impeach/DRSZGLB +impeachable/U +impeacher/M +impeachment/MS +impeccability/SM +impeccable/S +impeccably +impecunious/PY +impecuniousness/MS +imped/GRD +impedance/MS +impede/S +impeded/U +impeder/M +impediment/SM +impedimenta +impel/S +impelled +impeller/MS +impelling +impend/DGS +impenetrability/MS +impenetrable/P +impenetrableness/M +impenetrably +impenitence/MS +impenitent/YS +imperative/PSY +imperativeness/M +imperceivable +imperceptibility/MS +imperceptible +imperceptibly +imperceptive +imperf +imperfect/YSVP +imperfectability +imperfection/MS +imperfectness/SM +imperial/YS +imperialism/MS +imperialist/SM +imperialistic +imperialistically +imperil/GSLD +imperilment/SM +imperious/YP +imperiousness/MS +imperishable/SP +imperishableness/M +imperishably +impermanence/MS +impermanent/Y +impermeability/SM +impermeable/P +impermeableness/M +impermeably +impermissible +impersonal/Y +impersonality/M +impersonalized +impersonate/XGNDS +impersonation/M +impersonator/SM +impertinence/SM +impertinent/YS +imperturbability/SM +imperturbable +imperturbably +impervious/PY +imperviousness/M +impetigo/MS +impetuosity/MS +impetuous/YP +impetuousness/MS +impetus/MS +impiety/MS +imping/GD +impinge/LS +impingement/MS +impious/PY +impiousness/SM +impish/YP +impishness/MS +implacability/SM +implacable/P +implacableness/M +implacably +implant/BGSDR +implantation/SM +implanter/M +implausibility/MS +implausible +implausibly +implement/SMRDGZB +implementability +implementable/U +implementation's +implementation/A +implementations +implemented/AU +implementer/M +implementing/A +implementor/MS +implicant/SM +implicate/VGSD +implication/M +implicative/PY +implicit/YP +implicitness/SM +implied/Y +implode/GSD +implore/GSD +imploring/Y +implosion/SM +implosive/S +imply/GNSDX +impolite/YP +impoliteness/MS +impolitic/PY +impoliticness/M +imponderable/PS +imponderableness/M +import/SZGBRD +importance/SM +important/Y +importation/MS +importer/M +importing/A +importunate/PYGDS +importunateness/M +importune/SRDZYG +importuner/M +importunity/SM +imposable +impose/ASDG +imposer/SM +imposing/U +imposingly +imposition/SM +impossibility/SM +impossible/PS +impossibleness/M +impossibly +impost/SGMD +imposter's +impostor/SM +imposture/SM +impotence/MS +impotency/S +impotent/SY +impound/GDS +impoundments +impoverish/LGDRS +impoverisher/M +impoverishment/SM +impracticable/P +impracticableness/M +impracticably +impractical/PY +impracticality/SM +impracticalness/M +imprecate/NGXSD +imprecation/M +imprecise/PYXN +impreciseness/MS +imprecision/M +impregnability/MS +impregnable/P +impregnableness/M +impregnably +impregnate/DSXNG +impregnation/M +impresario/SM +impress/DRSGVL +impressed/U +impresser/M +impressibility/MS +impressible +impression/BMS +impressionability/SM +impressionable/P +impressionableness/M +impressionism/SM +impressionist/MS +impressionistic +impressive/YP +impressiveness/MS +impressment/M +imprimatur/SM +imprint/SZDRGM +imprinter/M +imprinting/M +imprison/GLDS +imprisonment/MS +improbability/MS +improbable/P +improbableness/M +improbably +impromptu/S +improper/PY +improperness/M +impropitious +impropriety/SM +improve/SRDGBL +improved/U +improvement/MS +improver/M +improvidence/SM +improvident/Y +improvisation/MS +improvisational +improvisatory +improvise/RSDZG +improviser/M +imprudence/SM +imprudent/Y +impudence/MS +impudent/Y +impugn/SRDZGB +impugner/M +impulse/XMVGNSD +impulsion/M +impulsive/YP +impulsiveness/MS +impunity/SM +impure/RPTY +impureness/M +impurity/MS +imputation/SM +impute/SDBG +in/AS +inaction +inactive +inadequate/S +inadvertence/MS +inadvertent/Y +inalienability/MS +inalienably +inalterable/P +inalterableness/M +inamorata/MS +inane/SRPYT +inanimate/P +inanimateness/S +inanity/MS +inappeasable +inappropriate/P +inarticulate/P +inasmuch +inaugural/S +inaugurate/XSDNG +inauguration/M +inauthenticity +inbound/G +inbred/S +inbreed/JG +inc/T +incalculableness/M +incalculably +incandescence/SM +incandescent/YS +incant +incantation/SM +incantatory +incapable/S +incapacitate/GNSD +incapacitation/M +incarcerate/XGNDS +incarceration/M +incarnadine/GDS +incarnate/AGSDNX +incarnation/AM +incendiary/S +incense/MGDS +incentive/ESM +incentively +incept/DGVS +inception/MS +inceptive/Y +inceptor/M +incessant/Y +incest/SM +incestuous/PY +incestuousness/MS +inch/GMDS +inchoate/DSG +inchworm/MS +incidence/MS +incident/SM +incidental/YS +incinerate/XNGSD +incineration/M +incinerator/SM +incipience/SM +incipiency/M +incipient/Y +incise/SDVGNX +incision/M +incisive/YP +incisiveness/MS +incisor/MS +incite/RZL +incitement/MS +inciter/M +incl +inclination/ESM +incline/EGSD +incliner/M +inclining/M +include/GDS +inclusion/MS +inclusive/PY +inclusiveness/MS +incognito/S +incoherency/M +income/M +incommode/DG +incommunicado +incomparable +incompetent/MS +incomplete/P +inconceivability/MS +inconceivable/P +inconceivableness/M +incondensable +incongruousness/S +inconsiderable/P +inconsiderableness/M +inconsistence +inconsolable/P +inconsolableness/M +inconsolably +incontestability/SM +incontestably +incontrovertibly +inconvenience/DG +inconvertibility +inconvertible +incorporable +incorporate/GASDXN +incorporated/UE +incorrect/P +incorrigibility/MS +incorrigible/SP +incorrigibleness/M +incorrigibly +incorruptible/S +incorruptibly +increase/JB +increaser/M +increasing/Y +incredible/P +incredibleness/M +increment/DMGS +incremental/Y +incrementation +incriminate/XNGSD +incrimination/M +incriminatory +incrustation/SM +incubate/XNGVDS +incubation/M +incubator/MS +incubus/MS +inculcate/SDGNX +inculcation/M +inculpate/SDG +incumbency/MS +incumbent/S +incunabula +incunabulum +incurable/S +incurious +incursion/SM +ind +indebted/P +indebtedness/SM +indefatigable/P +indefatigableness/M +indefatigably +indefeasible +indefeasibly +indefinable/PS +indefinableness/M +indefinite/S +indelible +indelibly +indemnification/M +indemnify/NXSDG +indemnity/SM +indent/R +indentation/SM +indented/U +indenter/M +indention/SM +indenture/DG +indescribable/PS +indescribableness/M +indescribably +indestructible/P +indestructibleness/M +indestructibly +indeterminably +indeterminacy/MS +indeterminism +index/MRDZGB +indexation/S +indexer/M +indicant/MS +indicate/DSNGVX +indication/M +indicative/SY +indicator/MS +indices's +indict/SGLBDR +indicter/M +indictment/SM +indifference +indigence/MS +indigenous/YP +indigenousness/M +indigent/SY +indigestible/S +indignant/Y +indignation/MS +indigo/SM +indirect/PG +indiscreet/P +indiscriminate/PY +indiscriminateness/M +indispensability/MS +indispensable/SP +indispensableness/M +indispensably +indisputable/P +indisputableness/M +indissoluble/P +indissolubleness/M +indissolubly +indistinguishable/P +indistinguishableness/M +indite/SDG +indium/SM +individual/YMS +individualism/MS +individualist/MS +individualistic +individualistically +individuality/MS +individualization/SM +individualize/DRSGZ +individualized/U +individualizer/M +individualizes/U +individualizing/Y +individuate/DSXGN +individuation/M +indivisible/SP +indivisibleness/M +indivisibly +indoctrinate/GNXSD +indoctrination/M +indoctrinator/SM +indolence/SM +indolent/Y +indomitable/P +indomitableness/M +indomitably +indoor +indubitable/P +indubitableness/M +indubitably +induce/ZGLSRD +inducement/MS +inducer/M +inducible +induct/GV +inductance/MS +inductee/SM +induction/SM +inductive/PY +inductiveness/M +inductor/MS +indulge/GDRS +indulgence/SDGM +indulgent/Y +indulger/M +industrial/SY +industrialism/MS +industrialist/MS +industrialization/MS +industrialize/SDG +industrialized/U +industrious/YP +industriousness/SM +industry/SM +inebriate/NGSDX +inebriation/M +inedible +ineducable +ineffability/MS +ineffable/P +ineffableness/M +ineffably +inelastic +ineligibly +ineluctable +ineluctably +inept/YP +ineptitude/SM +ineptness/MS +inequivalent +inerrant +inert/SPY +inertia/SM +inertial/Y +inertness/MS +inescapably +inestimably +inevitability/MS +inevitable/P +inevitableness/M +inevitably +inexact/P +inexhaustible/P +inexhaustibleness/M +inexhaustibly +inexorability/M +inexorable/P +inexorableness/M +inexorably +inexpedience/M +inexplicable/P +inexplicableness/M +inexplicably +inexplicit +inexpressibility/M +inexpressible/PS +inexpressibleness/M +inextricably +inf/ZT +infamous +infamy/SM +infancy/M +infant/MS +infanticide/MS +infantile +infantry/SM +infantryman/M +infantrymen +infarct/SM +infarction/SM +infatuate/XNGSD +infatuation/M +infauna +infect/ESGDA +infected/U +infecter +infection/EASM +infectious/PY +infectiousness/MS +infective +infer/B +inference/GMSR +inferential/Y +inferior/SMY +inferiority/MS +infernal/Y +inferno/MS +inferred +inferring +infertile +infest/GSDR +infestation/MS +infester/M +infidel/SM +infighting/M +infill/MG +infiltrate/V +infiltrator/MS +infinite/V +infinitesimal/SY +infinitival +infinitive/YMS +infinitude/MS +infinitum +infinity/SM +infirmary/SM +infirmity/SM +infix/M +inflammable/P +inflammableness/M +inflammation/MS +inflammatory +inflatable/MS +inflate/NGBDRSX +inflater/M +inflation/ESM +inflationary +inflect/GVDS +inflection/SM +inflectional/Y +inflexible/P +inflexibleness/M +inflexion/SM +inflict/DRSGV +inflicter/M +infliction/SM +inflow/M +influence/SRDGM +influenced/U +influencer/M +influent +influential/SY +influenza/MS +info/SM +infomercial/S +informatics +information/ES +informational +informative/UY +informativeness/S +informatory +informed/U +informer/M +infotainment/S +infra +infrared/SM +infrasonic +infrastructural +infrastructure/MS +infrequence/S +infringe/LR +infringement/SM +infringer/M +infuriate/GNYSD +infuriating/Y +infuriation/M +infuse/RZ +infuser/M +infusible/P +infusibleness/M +ingenious/YP +ingeniousness/MS +ingenuity/SM +ingenuous/EY +ingenuousness/MS +ingest/DGVS +ingestible +ingestion/SM +inglenook/MS +ingoing +ingot/SMDG +ingrained/Y +ingrate/M +ingratiate/DSGNX +ingratiating/Y +ingratiation/M +ingredient/SM +ingress/MS +ingression/M +ingrown/P +inguinal +ingénue/S +inhabit/R +inhabitable/U +inhabitance +inhabited/U +inhabiter/M +inhalant/S +inhalation/SM +inhalator/SM +inhale/Z +inhere/DG +inherent/Y +inherit/BDSG +inheritable/P +inheritableness/M +inheritance/EMS +inherited/E +inheriting/E +inheritor/S +inheritress/MS +inheritrix/MS +inherits/E +inhibit/DVGS +inhibited/U +inhibiter's +inhibition/MS +inhibitor/MS +inhibitory +inhomogeneous +inhospitable/P +inhospitableness/M +inhospitality +inimical/Y +inimitable/P +inimitableness/M +inimitably +inion +iniquitous/PY +iniquitousness/M +iniquity/MS +initial/GSPRDY +initialer/M +initialization's +initialization/A +initializations +initialize/ASDG +initialized/U +initializer/S +initiate/UD +initiates +initiating +initiation/SM +initiative/SM +initiator/MS +initiatory +inject/GVSDB +injectable/U +injection/MS +injector/SM +injunctive +injure/SRDZG +injured/U +injurer/M +injurious/YP +injuriousness/M +ink/ZDRJ +inkblot/SM +inker/M +inkiness/MS +inkling/SM +inkstand/SM +inkwell/SM +inky/TP +inland +inlander/M +inlay/RG +inletting +inly/G +inmost +inn/ZGDRSJ +innards +innate/YP +innateness/SM +inner/Y +innermost/S +innersole/S +innerspring +innervate/GNSDX +innervation/M +inning/M +innkeeper/MS +innocence/SM +innocent/SYRT +innocuous/PY +innocuousness/MS +innovate/SDVNGX +innovation/M +innovative/P +innovator/MS +innovatory +innuendo/MDGS +innumerability/M +innumerable/P +innumerableness/M +innumerably +innumerate +inoculate/ASDG +inoculation/MS +inoculative +inoffensive/P +inopportune/P +inopportuneness/M +inordinate/PY +inordinateness/M +inorganic +inpatient +input/MRDG +inquire/ZR +inquirer/M +inquiring/Y +inquiry/MS +inquisition/MS +inquisitional +inquisitive/YP +inquisitiveness/MS +inquisitor/MS +inquisitorial/Y +inrush/M +ins +insalubrious +insanitary +insatiability/MS +insatiable/P +insatiableness/M +insatiably +inscribe/Z +inscription/SM +inscrutability/SM +inscrutable/P +inscrutableness/SM +inscrutably +inseam +insecticidal +insecticide/MS +insectivore/SM +insectivorous +insecure/P +insecureness/M +inseminate/NGXSD +insemination/M +insensate/P +insensateness/M +insensible/P +insentient +inseparable/S +insert/ADSG +inserter/M +insertion/AMS +insetting +inshore +inside/Z +insider/M +insidious/YP +insidiousness/MS +insightful/Y +insigne's +insignia/SM +insignificant +insinuate/VNGXSD +insinuating/Y +insinuation/M +insinuator/SM +insipid/Y +insipidity/MS +insist/SGD +insistence/SM +insistent/Y +insisting/Y +insociable +insofar +insole/M +insolence/SM +insolent/YS +insoluble/P +insolubleness/M +insolubly +insomnia/MS +insomniac/S +insomuch +insouciance/SM +insouciant/Y +inspect/AGSD +inspection/SM +inspective +inspector/SM +inspectorate/MS +inspiration/MS +inspirational/Y +inspire/R +inspired/U +inspirer/M +inspiring/U +inspirit/DG +inst/B +install/ADRSG +installable +installation/SM +installer/MS +installment/MS +instance/GD +instant/SRYMP +instantaneous/PY +instantaneousness/M +instantiate/SDXNG +instantiated/U +instantiation/M +instate/AGSD +instead +instigate/XSDVGN +instigation/M +instigator/SM +instillation/SM +instinct/VMS +instinctive/Y +instinctual +institute/ZXVGNSRD +instituter/M +institutes/M +institution/AM +institutional/Y +institutionalism/M +institutionalist/M +institutionalization/SM +institutionalize/GDS +institutor's +instr +instruct/DSVG +instructed/U +instruction/MS +instructional +instructive/PY +instructiveness/M +instructor/MS +instrument/GMDS +instrumental/SY +instrumentalist/MS +instrumentality/SM +instrumentation/SM +insubordinate +insubstantial +insufferable +insufferably +insular/YS +insularity/MS +insulate/DSXNG +insulated/U +insulation/M +insulator/MS +insulin/MS +insult/DRSG +insulter/M +insulting/Y +insuperable +insuperably +insupportable/P +insupportableness/M +insurance's/A +insurance/MS +insure/BZGS +insured/S +insurer/M +insurgence/SM +insurgency/MS +insurgent/MS +insurmountably +insurrection/SM +insurrectionist/SM +int/ZR +intact/P +intactness/M +intaglio/GMDS +intake/M +intangible/M +integer/MS +integrability/M +integrable +integral/SYM +integrand/MS +integrate/AGNXEDS +integration/EMA +integrative/E +integrator/MS +integrity/SM +integument/SM +intellect/MVS +intellective/Y +intellectual/YPS +intellectualism/MS +intellectuality/M +intellectualize/GSD +intellectualness/M +intelligence/MSR +intelligencer/M +intelligent/UY +intelligentsia/MS +intelligibilities +intelligibility/UM +intelligible/PU +intelligibleness/MU +intelligibly/U +intemperate/P +intendant/MS +intended/SYP +intendedness/M +intender/M +intensification/M +intensifier/M +intensify/GXNZRSD +intensional/Y +intensive/PSY +intensiveness/MS +intent/YP +intention/SDM +intentional/UY +intentionality/M +intentness/SM +inter/ESTL +interact/VGDS +interaction/MS +interactive/PY +interactivity +interaxial +interbank +interbred +interbreed/GS +intercalate/GNVDS +intercalation/M +intercase +intercaste +intercede/SRDG +interceder/M +intercensal +intercept/DGS +interception/MS +interceptor/MS +intercession/MS +intercessor/SM +intercessory +interchange/DSRGJ +interchangeability/M +interchangeable/P +interchangeableness/M +interchangeably +interchanger/M +intercity +interclass +intercohort +intercollegiate +intercom/SM +intercommunicate/SDXNG +intercommunication/M +interconnect/GDS +interconnected/P +interconnectedness/M +interconnection/SM +interconnectivity +intercontinental +interconversion/M +intercorrelated +intercourse/SM +interdenominational +interdepartmental/Y +interdependence/MS +interdependency/SM +interdependent/Y +interdict/MDVGS +interdiction/MS +interdisciplinary +interest/GEMDS +interested/UYE +interesting/YP +interestingly/U +interestingness/M +interface/SRDGM +interfacing/M +interfaith +interfere/SRDG +interference/MS +interferer/M +interfering/Y +interferometer/SM +interferometric +interferometry/M +interferon/MS +interfile/GSD +intergalactic +intergeneration/M +intergenerational +interglacial +intergovernmental +intergroup +interim/S +interindex +interindustry +interior/SMY +interj +interject/GDS +interjection/MS +interjectional +interlace/GSD +interlard/SGD +interlayer/G +interleave/SDG +interleukin/S +interlibrary +interline/JGSD +interlinear/S +interlingua/M +interlingual +interlining/M +interlink/GDS +interlisp/M +interlobular +interlock/RDSG +interlocker/M +interlocutor/MS +interlocutory +interlope/GZSRD +interloper/M +interlude/MSDG +intermarriage/MS +intermarry/GDS +intermediary/MS +intermediate/YMNGSDP +intermediateness/M +intermediation/M +interment/SME +intermeshed +intermetrics +intermezzi +intermezzo/SM +interminably +intermingle/DSG +intermission/MS +intermittent/Y +intermix/GSRD +intermodule +intermolecular/Y +intern/L +internal/SY +internalization/SM +internalize/GDS +international/YS +internationalism/SM +internationalist/SM +internationality/M +internationalization/MS +internationalize/DSG +interne's +internecine +internee/SM +internetwork +internist/SM +internment/SM +internship/MS +internuclear +interocular +interoffice +interoperability +interpenetrates +interpersonal/Y +interplanetary +interplay/GSMD +interpol +interpolate/XGNVBDS +interpolation/M +interpose/GSRD +interposer/M +interposition/MS +interpret/AGSD +interpretable/U +interpretation/MSA +interpretative/Y +interpreted/U +interpreter/SM +interpretive/Y +interpretor/S +interprocess +interprocessor +interquartile +interracial +interred/E +interregional +interregnum/MS +interrelate/GNDSX +interrelated/PY +interrelatedness/M +interrelation/M +interrelationship/SM +interring/E +interrogate/DSXGNV +interrogation/M +interrogative/SY +interrogator/SM +interrogatory/S +interrupt/VGZRDS +interrupted/U +interrupter/M +interruptibility +interruptible +interruption/MS +interscholastic +intersect/GDS +intersection/MS +intersession/MS +interspecies +intersperse/GNDSX +interspersion/M +interstage +interstate/S +interstellar +interstice/SM +interstitial/SY +intersurvey +intertask +intertwine/GSD +interurban/S +interval/MS +intervene/GSRD +intervener/M +intervenor/M +intervention/MS +interventionism/MS +interventionist/S +interview/AMD +interviewed/U +interviewee/SM +interviewer/SM +interviewing +interviews +intervocalic +interweave/GS +interwove +interwoven +intestacy/SM +intestinal/Y +intestine/SM +inti +intifada +intimacy/SM +intimal +intimate/XYNGPDRS +intimateness/M +intimater/M +intimation/M +intimidate/SDXNG +intimidating/Y +intimidation/M +into +intolerable/P +intolerableness/M +intolerant/PS +intonate/NX +intonation/M +intoxicant/MS +intoxicate/DSGNX +intoxicated/Y +intoxication/M +intra +intracellular +intracity +intraclass +intracohort +intractability/M +intractable/P +intractableness/M +intradepartmental +intrafamily +intragenerational +intraindustry +intraline +intrametropolitan +intramural/Y +intramuscular/Y +intranasal +intransigence/MS +intransigent/YS +intransitive/S +intraoffice +intraprocess +intrapulmonary +intraregional +intrasectoral +intrastate +intratissue +intrauterine +intravenous/YS +intrepid/YP +intrepidity/SM +intrepidness/M +intricacy/SM +intricate/PY +intricateness/M +intrigue/DRSZG +intriguer/M +intriguing/Y +intrinsic/S +intrinsically +intro/S +introduce/ADSG +introducer/M +introduction/ASM +introductory +introit/SM +introject/SD +introspect/SGVD +introspection/MS +introspective/YP +introspectiveness/M +introversion/SM +introvert/SMDG +intrude/ZGDSR +intruder/M +intrusion/SM +intrusive/SYP +intrusiveness/MS +intubate/NGDS +intubation/M +intuit/GVDSB +intuitionist/M +intuitive/YP +intuitiveness/MS +inundate/SXNG +inundation/M +inure/GDS +invade/ZSRDG +invader/M +invalid/GSDM +invalidism/MS +invariable/P +invariant/M +invasion/SM +invasive/P +invective/PSMY +invectiveness/M +inveigh/DRG +inveigher/M +inveighs +inveigle/DRSZG +inveigler/M +invent/ADGS +invented/U +invention/ASM +inventive/YP +inventiveness/MS +inventor/MS +inventory/SDMG +inverse/YV +invert/ZSGDR +inverter/M +invertible +invest/ADSLG +investigate/XDSNGV +investigation/MA +investigator/MS +investigatory +investiture/SM +investment's/A +investment/ESA +investor/SM +inveteracy/MS +inveterate/Y +inviability +invidious/YP +invidiousness/MS +invigilate/GD +invigilator/SM +invigorate/ANGSD +invigorating/Y +invigoration/AM +invigorations +invincibility/SM +invincible/P +invincibleness/M +invincibly +inviolability/MS +inviolably +inviolate/YP +inviolateness/M +inviscid +invisible/S +invisibleness/M +invitation/MS +invitational/S +invite/SRDG +invited/U +invitee/S +inviter/M +inviting/Y +invocable +invocate +invoke/GSRDBZ +invoked/A +invoker/M +invokes/A +involuntariness/S +involuntary/P +involute/XYN +involution/M +involutorial +involve/GDSRL +involved/U +involvedly +involvement/SM +involver/M +invulnerability/M +invulnerableness/M +inward/PY +inwardness/M +ioctl +iodate/MGND +iodation/M +iodide/MS +iodinate/DNG +iodine/MS +iodize/GSD +ion's/I +ion/SMU +ionic/S +ionization's +ionization/SU +ionize/GNSRDJXZ +ionized/UC +ionizer's +ionizer/US +ionizes/U +ionizing/U +ionosphere/SM +ionospheric +iota/SM +ipecac/MS +ipso +irascibility/SM +irascible +irascibly +irate/RPYT +irateness/S +ire/MGDS +ireful +irenic/S +irides/M +iridescence/SM +iridescent/Y +iridium/MS +irids +iris/GDSM +irk/GDS +irksome/YP +irksomeness/SM +iron/DRMPSGJ +ironclad/S +ironer/M +ironic +ironical/YP +ironicalness/M +ironing/M +ironmonger/M +ironmongery/M +ironside/MS +ironstone/MS +ironware/SM +ironwood/SM +ironwork/MRS +ironworker/M +irony/SM +irradiate/XSDVNG +irradiation/M +irrational/YSP +irrationality/MS +irrationalness/M +irreclaimable +irreconcilability/MS +irreconcilable/PS +irreconcilableness/M +irreconcilably +irrecoverable/P +irrecoverableness/M +irrecoverably +irredeemable/S +irredeemably +irredentism/M +irredentist/M +irreducibility/M +irreducible +irreducibly +irreflexive +irrefutable +irrefutably +irregardless +irregular/YS +irregularity/SM +irrelevance/SM +irrelevancy/MS +irrelevant/Y +irreligious +irremediable/P +irremediableness/M +irremediably +irremovable +irreparable/P +irreparableness/M +irreparably +irreplaceable/P +irrepressible +irrepressibly +irreproachable/P +irreproachableness/M +irreproachably +irreproducibility +irreproducible +irresistibility/M +irresistible/P +irresistibleness/M +irresistibly +irresolute/PNXY +irresoluteness/SM +irresolution/M +irresolvable +irrespective/Y +irresponsibility/SM +irresponsible/PS +irresponsibleness/M +irresponsibly +irretrievable +irretrievably +irreverence/MS +irreverent/Y +irreversible +irreversibly +irrevocable/P +irrevocableness/M +irrevocably +irrigable +irrigate/DSXNG +irrigation/M +irritability/MS +irritable/P +irritableness/M +irritably +irritant/S +irritate/DSXNGV +irritated/Y +irritating/Y +irritation/M +irrupt/GVSD +irruption/SM +is +isinglass/MS +isl/GD +island/GZMRDS +islander/M +isle/MS +islet/SM +ism/MCS +isn't +isobar/MS +isobaric +isochronal/Y +isochronous/Y +isocline/M +isocyanate/M +isodine +isolate/SDXNG +isolation/M +isolationism/SM +isolationist/SM +isolationistic +isolator/MS +isomer/SM +isomeric +isomerism/SM +isometric/S +isometrically +isometrics/M +isomorph/M +isomorphic +isomorphically +isomorphism/MS +isoperimetrical +isopleth/M +isopleths +isosceles +isostatic +isotherm/MS +isothermal/Y +isotonic +isotope/SM +isotopic +isotropic +isotropically +isotropy/M +ispell/M +issuable +issuance/MS +issuant +issue/GMZDSR +issued/A +issuer/AMS +issues/A +issuing/A +isthmian/S +isthmus/SM +it'd +it'll +it/MUS +ital +italic/S +italicization/MS +italicize/GSD +italicized/U +itch/GMDS +itchiness/MS +itchy/RTP +item/MDSG +itemization/SM +itemize/GZDRS +itemized/U +itemizer/M +itemizes/A +iterate/ASDXVGN +iteration/M +iterative/YA +iterator/MS +itinerant/SY +itinerary/MS +its +itself +iv/M +ivory/SM +ivy/MDS +ix +j's +j/F +jab/SM +jabbed +jabber/JRDSZG +jabberer/M +jabbing +jabot/MS +jacaranda/MS +jack/GDRMS +jackal/SM +jackass/SM +jackboot/DMS +jackdaw/SM +jacket/GSMD +jacketed/U +jackhammer/MDGS +jackknife/MGSD +jackknives +jackpot/MS +jackrabbit/DGS +jackstraw/MS +jacquard/MS +jacuzzi +jade/MGDS +jaded/PY +jadedness/SM +jadeite/SM +jag/S +jagged/RYTP +jaggedness/SM +jaggers +jagging +jaguar/MS +jail/GZSMDR +jailbird/MS +jailbreak/SM +jailer/M +jalapeño/S +jalopy/SM +jalousie/MS +jam/SM +jamb/DMGS +jambalaya/MS +jamboree/MS +jammed/U +jamming/U +jangle/RSDGZ +jangler/M +jangly +janissary/MS +janitor/SM +janitorial +japan/SM +japanned +japanner +japanning +jape/DSMG +jar/MS +jardinière/MS +jarful/S +jargon/SGDM +jarred +jarring/SY +jasmine/MS +jasper/MS +jato/SM +jaundice/DSMG +jaundiced/U +jaunt/MDGS +jauntily +jauntiness/MS +jaunty/SRTP +javelin/SDMG +jaw/SMDG +jawbone/SDMG +jawbreaker/SM +jawline +jay/SM +jaybird/SM +jaywalk/JSRDZG +jaywalker/M +jazz/MGDS +jazziness/M +jazzmen +jazzy/PTR +jct +jealous/PY +jealousness/M +jealousy/MS +jean/MS +jeep/GZSMD +jeer/SJDRMG +jeerer/M +jeering/Y +jeez +jehad's +jejuna +jejune/PY +jejuneness/M +jejunum/M +jell/GSD +jello's +jelly/SDMG +jellybean/SM +jellyfish/MS +jellying/M +jellylike +jellyroll/S +jemmy/M +jennet/SM +jenny/SM +jeopard +jeopardize/GSD +jeopardy/MS +jeremiad/SM +jerk/GSDRJ +jerker/M +jerkily +jerkin/SM +jerkiness/SM +jerkwater/S +jerky/RSTP +jerry/M +jerrybuilt +jersey/MS +jess/M +jessamine's +jest/DRSGZM +jester/M +jesting/Y +jet/MS +jetliner/MS +jetport/SM +jetsam/MS +jetted/M +jetting/M +jettison/DSG +jetty/RSDGMT +jewel/GZMRDS +jeweler/M +jewelery/S +jewellery's +jewelry/MS +jg/M +jib/MDSG +jibbed +jibbing +jibe/S +jiff/S +jiffy/SM +jig/MS +jigged +jigger/SDMG +jigging/M +jiggle/SDG +jiggly/TR +jigsaw/GSDM +jihad/SM +jilt/DRGS +jilter/M +jimmy/GSDM +jimsonweed/S +jingle/RSDG +jingler/M +jingly/TR +jingo/M +jingoism/SM +jingoist/SM +jingoistic +jinn/MS +jinni's +jinrikisha/SM +jinx/GMDS +jitney/MS +jitter/S +jitterbug/SM +jitterbugged +jitterbugger +jitterbugging +jittery/TR +jiujitsu's +jive/MGDS +job/SM +jobbed +jobber/MS +jobbery/M +jobbing/M +jobholder/SM +jobless/P +joblessness/MS +jock/GDMS +jockey/SGMD +jockstrap/MS +jocose/YP +jocoseness/MS +jocosity/SM +jocular/Y +jocularity/SM +jocund/Y +jocundity/SM +jodhpurs +joey/M +jog/S +jogged +jogger/SM +jogging/S +joggle/SRDG +joggler/M +john/SM +johnny/SM +johnnycake/SM +join/ADGFS +joined/U +joiner/FSM +joinery/MS +joint's +joint/EGDYPS +jointed/EYP +jointedness/ME +jointer/M +jointly/F +jointures +joist/GMDS +joke/MZDSRG +joker/M +jokey +jokier +jokiest +jokily +joking/Y +jollification/MS +jollily +jolliness/SM +jollity/MS +jolly/TSRDGP +jolt/DRGZS +jolter/M +jonquil/MS +josh/DSRGZ +josher/M +joss/M +jostle/SDG +jot/S +jotted +jotter/SM +jotting/SM +joule/SM +jounce/SDG +jouncy/RT +journal/GSDM +journalese/MS +journalism/SM +journalist/SM +journalistic +journalize/DRSGZ +journalized/U +journalizer/M +journey/DRMZSGJ +journeyer/M +journeyman/M +journeymen +joust/ZSMRDG +jouster/M +jovial/Y +joviality/SM +jowl/SMD +jowly/TR +joy/MDSG +joyful/PY +joyfuller +joyfullest +joyfulness/SM +joyless/PY +joylessness/MS +joyous/YP +joyousness/MS +joyridden +joyride/SRZMGJ +joyrode +joystick/S +jubilant/Y +jubilate/XNGDS +jubilation/M +jubilee/SM +juddered +juddering +judge's +judge/AGDS +judger/M +judgeship/SM +judgment/MS +judgmental/Y +judicable +judicatory/S +judicature/MS +judicial/Y +judiciary/S +judicious/IYP +judiciousness/SMI +judo/MS +jug/MS +jugate/F +jugful/SM +jugged +juggernaut/SM +jugging +juggle/RSDGZ +juggler/M +jugglery/MS +jugular/S +juice/GMZDSR +juicer/M +juicily +juiciness/MS +juicy/TRP +jujitsu/MS +juju/M +jujube/SM +jujutsu's +juke/GS +jukebox/SM +julep/SM +julienne/GSD +jumble/GSD +jumbo/MS +jump/GZDRS +jumper/M +jumpily +jumpiness/MS +jumpsuit/S +jumpy/PTR +jun +junco/MS +junction/IMESF +juncture/SFM +jungle/SDM +junior/MS +juniority/M +juniper/SM +junk/GZDRMS +junkerdom +junket/SMDG +junketeer/SGDM +junkie/RSMT +junkyard/MS +junta/MS +juridic +juridical/Y +juried +jurisdiction/SM +jurisdictional/Y +jurisprudence/SM +jurisprudent +jurisprudential/Y +jurist/MS +juristic +juror/MS +jury/IMS +jurying +juryman/M +jurymen +jurywoman/M +jurywomen +just/UPY +justed +juster/M +justest +justice/MIS +justiciable +justifiability/M +justifiable/U +justifiably/U +justification/M +justified/UA +justifier/M +justify/GDRSXZN +justing +justness's/U +justness/MS +justs +jut/S +jute/SM +jutted +jutting +juvenile/SM +juxtapose/SDG +juxtaposition/SM +k's/IE +k/FGEIS +kHz/M +kW +kWh +kabob/SM +kaboom +kabuki/SM +kaddish/S +kaffeeklatch +kaffeeklatsch/S +kaftan's +kaiser/MS +kale/MS +kaleidescope +kaleidoscope/SM +kaleidoscopic +kaleidoscopically +kamikaze/SM +kangaroo/SGMD +kaolin/MS +kaolinite/M +kapok/SM +kappa/MS +kaput/M +karakul/MS +karaoke/S +karat/SM +karate/MS +karma/SM +karmic +kart/MS +katakana +katydid/SM +kayak/SGDM +kayo/DMSG +kazoo/SM +kc/M +kcal/M +kebab/SM +keel/GSMDR +keelhaul/SGD +keen/GTSPYDR +keener/M +keening/M +keenness/MS +keep/GZJSR +keeper/M +keeping/M +keepsake/SM +keg/MS +kegged +kegging +kelp/GZMDS +kelvin/MS +ken/MS +kenned +kennel/GSMD +kenning +keno/M +kepi/SM +kept +keratin/MS +kerbside +kerchief/MDSG +kerned +kernel/GSMD +kerning +kerosene/MS +kestrel/SM +ketch/MS +ketchup/SM +ketone/M +ketosis/M +kettle/SM +kettledrum/SM +kettleful +key/SGMD +keyboard/RDMZGS +keyboardist/S +keyclick/SM +keyhole/MS +keynote/SRDZMG +keynoter/M +keypad/MS +keypunch/ZGRSD +keypuncher/M +keyring +keystone/SM +keystroke/SDMG +keyword/SM +kg +khaki/SM +khan/MS +kibble/GMSD +kibbutz/M +kibbutzim +kibitz/GRSDZ +kibitzer/M +kibosh/GMSD +kick/GZDRS +kickback/SM +kickball/MS +kicker/M +kickoff/SM +kickstand/MS +kicky/RT +kid/MS +kidded +kidder/SM +kiddie/SD +kidding/YM +kiddish +kiddo/SM +kiddy's +kiddying +kidless +kidnap/MSJ +kidnaper's +kidnaping's +kidnapped +kidnapper/SM +kidnapping/S +kidney/MS +kidskin/SM +kielbasa/SM +kielbasi +kier/I +kill/BJGZSDR +killdeer/SM +killer/M +killing/Y +killjoy/S +kiln/GDSM +kilo/SM +kilobaud/M +kilobit/S +kilobuck +kilobyte/S +kilocycle/MS +kilogauss/M +kilogram/MS +kilohertz/M +kilohm/M +kilojoule/MS +kiloliter/MS +kilometer/SM +kiloton/SM +kilovolt/SM +kilowatt/SM +kiloword +kilt/MDRGZS +kilter/M +kimono/MS +kin/MS +kind/PSYRT +kinda +kinder/U +kindergarten/MS +kindergärtner/SM +kindhearted/YP +kindheartedness/MS +kindle/AGRSD +kindler/M +kindliness's/U +kindliness/SM +kindling/M +kindly/TUPR +kindness's +kindness/US +kindred/S +kine/SM +kinematic/S +kinematics/M +kinesics/M +kinesthesis +kinesthetic/S +kinesthetically +kinetic/S +kinetically +kinetics/M +kinfolk/S +king/SGYDM +kingbird/M +kingdom/SM +kingfisher/MS +kinglet/M +kingliness/M +kingly/TPR +kingpin/MS +kingship/SM +kink/GSDM +kinkily +kinkiness/SM +kinky/PRT +kinsfolk/S +kinship/SM +kinsman/M +kinsmen/M +kinswoman/M +kinswomen +kiosk/SM +kip/MS +kipped +kipper/DMSG +kipping +kirk/GDMS +kirsch/S +kismet/SM +kiss/DSRBJGZ +kisser/M +kit/MDRGS +kitbag's +kitchen/GDRMS +kitchener/M +kitchenette/SM +kitchenware/SM +kite/SM +kiter/M +kith/MDG +kiths +kitsch/MS +kitschy +kitted +kitten/SGDM +kittenish/YP +kittenishness/M +kitting +kittiwakes +kitty/SM +kiwi/SM +kiwifruit/S +kl +klaxon/M +kleptomania/MS +kleptomaniac/SM +kludge/RSDGMZ +kludger/M +kludgey +klutz/SM +klutziness/S +klutzy/TRP +klystron/MS +km +kn +knack/SGZRDM +knacker/M +knackwurst/MS +knapsack/MS +knave/SM +knavery/MS +knavish/Y +knead/GZRDS +kneader/M +knee/DSM +kneecap/MS +kneecapped +kneecapping +kneeing +kneel/GRS +kneeler/M +kneepad/SM +knell/SMDG +knelt +knew +knick/ZR +knickerbocker/S +knickknack/SM +knife/DSGM +knight/MDYSG +knighthood/MS +knightliness/MS +knightly/P +knish/MS +knit/AU +knits +knitted +knitter/MS +knitting/SM +knitwear/M +knives/M +knob/MS +knobbly +knobby/RT +knock/GZSJRD +knockabout/M +knockdown/S +knocker/M +knockoff/S +knockout/MS +knockwurst's +knoll/MDSG +knot/MS +knothole/SM +knotted +knottiness/M +knotting/M +knotty/TPR +know/GRBSJ +knowable/U +knower/M +knowhow +knowing/RYT +knowingly/U +knowings/U +knowledge/SM +knowledgeable/P +knowledgeableness/M +knowledgeably +known/SU +knuckle/DSMG +knuckleball/R +knuckleduster +knucklehead/MS +knurl/DSG +koala/SM +kohlrabi/M +kohlrabies +kola/SM +kook/GDMS +kookaburra/SM +kookiness/S +kooky/PRT +kopeck/MS +kosher/DGS +kowtow/SGD +kph +kraal/SMDG +kraft/M +kraut/S! +kriegspiel/M +krill/MS +krone/RM +kronor +krypton/SM +króna/M +krónur +ks +kt +kuchen/MS +kudos/M +kudzu/SM +kumquat/SM +kurtosis/M +kvetch/DSG +kw +kyle/M +l's +l/JGVXT +la/MHLG +lab/SM +label's +label/GAZRDS +labeled/U +labeler/M +labellings/A +labia/M +labial/YS +labile +labiodental +labium/M +labor/RDMJSZG +laboratory/MS +labored's/U +labored/PMY +laboredness/M +laborer/M +laboring/MY +laborings/U +laborious/PY +laboriousness/MS +laborsaving +laburnum/SM +labyrinth/M +labyrinthine +labyrinths +lac/SGMDR +lace/MS +laced/U +lacer/M +lacerate/NGVXDS +laceration/M +laces/U +lacewing/MS +lachrymal/S +lachrymose +lacing/M +lack/GRDMS +lackadaisic +lackadaisical/Y +lacker/M +lackey/SMDG +lackluster/S +laconic +laconically +lacquer/ZGDRMS +lacquerer/M +lacrosse/MS +lactate/MNGSDX +lactation/M +lactational/Y +lacteal +lactic +lactose/MS +lacuna/M +lacunae +lacy/RT +lad/XGSJMND +ladder/GDMS +laddie/MS +lade/S +laded/U +laden/U +ladened +ladening +lading/M +ladle/SDGM +lady/SM +ladybird/SM +ladybug/MS +ladyfinger/SM +ladylike/U +ladylove/MS +ladyship/SM +laetrile/S +lag/ZSR +lager/DMG +laggard/MYSP +laggardness/M +lagged +lagging/MS +lagniappe/SM +lagoon/MS +laid/AI +lain +lair/GDMS +laird/MS +laissez +laity/SM +lake/DSRMG +laker/M +lakeside +lallygag/S +lallygagged +lallygagging +lam/MDRSTG +lama/SM +lamasery/MS +lamb/SRDMG +lambada/S +lambaste/SDG +lambda/SM +lambency/MS +lambent/Y +lambkin/MS +lambskin/MS +lambswool +lame/SPY +lamebrain/SM +lamed/M +lameness/MS +lament/DGSB +lamentable/P +lamentableness/M +lamentably +lamentation/SM +lamented/U +lamina/M +laminae +laminar +laminate/XNGSD +lamination/M +lammed +lammer +lamming +lamp/SGMRD +lampblack/SM +lamplight/ZRMS +lamplighter/M +lampoon/RDMGS +lampooner/M +lamppost/SM +lamprey/MS +lampshade/MS +lanai/SM +lance/SRDGMZ +lancer/M +lancet/MS +land/SMRDJGZ +landau/MS +lander/I +landfall/SM +landfill/DSG +landforms +landhold/JGZR +landholder/M +landing/M +landlady/MS +landless +landlines +landlocked +landlord/MS +landlubber/SM +landmark/GSMD +landmass/MS +landowner/MS +landownership/M +landowning/SM +lands/I +landscape/GMZSRD +landscaper/M +landslid/G +landslide/MS +landslip +landsman/M +landsmen +landward/S +lane/SM +language/MS +languid/PY +languidness/MS +languish/SRDG +languisher/M +languishing/Y +languor/SM +languorous/Y +lank/PTYR +lankiness/SM +lankness/MS +lanky/PRT +lanolin/MS +lantern/GSDM +lanthanide/M +lanthanum/MS +lanyard/MS +lap/SM +lapboard/MS +lapdog/S +lapel/MS +lapidary/MS +lapin/MS +lapped +lappet/MS +lapping +laps/SRDG +lapse/KSDMG +lapsed/A +lapser/MA +lapses/A +lapsing/A +laptop/SM +lapwing/MS +larboard/MS +larcenist/S +larcenous +larceny/MS +larch/MS +lard/MRDSGZ +larder/M +lardy/RT +large/SRTYP +largehearted +largemouth +largeness/SM +largess/SM +largish +largo/S +lariat/MDGS +lark/GRDMS +larker/M +larkspur/MS +larva/M +larvae +larval +laryngeal/YS +larynges +laryngitides +laryngitis/M +larynx/M +las/SRZG +lasagna/S +lasagne's +lascivious/YP +lasciviousness/MS +lase +laser/M +lash/JGMSRD +lashed/U +lasher/M +lashing/M +lass/SM +lassie/SM +lassitude/MS +lasso/GRDMS +lassoer/M +last/JGSYRD +laster/M +lasting/PY +lastingness/M +lat/SDRT +latch's +latch/UGSD +latching/M +latchkey/SM +late/KA +latecomer/SM +lated/A +lately +latency/MS +lateness/MS +latent/YS +later/A +lateral/GDYS +lateralization +latest/S +latex/MS +lath/MSRDGZ +lathe/M +lather/RDMG +latherer/M +lathery +lathing/M +laths +latices/M +latish +latitude/SM +latitudinal/Y +latitudinarian/S +latitudinary +latrine/MS +latte/SR +latter/YM +lattice/SDMG +latticework/MS +latticing/M +laud/RDSBG +laudably +laudanum/MS +laudatory +lauder/M +lauds/M +laugh/BRDZGJ +laughable/P +laughableness/M +laughably +laugher/M +laughing/MY +laughingstock/SM +laughs +laughter/MS +launch/AGSD +launcher/MS +launching/S +launchpad/S +launder/SDRZJG +laundered/U +launderer/M +launderette/MS +laundress/MS +laundrette/S +laundromat/S +laundry/MS +laundryman/M +laundrymen +laundrywoman/M +laundrywomen +laura/M +laureate/DSNG +laureateship/SM +laurel/SGMD +lava/SM +lavage/MS +lavaliere/MS +lavatory/MS +lave/GDS +lavender/MDSG +lavish/SRDYPTG +lavishness/MS +law/SMDG +lawbreaker/SM +lawbreaking/MS +lawful/PUY +lawfulness/SMU +lawgiver/MS +lawgiving/M +lawless/PY +lawlessness/MS +lawmaker/MS +lawmaking/SM +lawman/M +lawmen +lawn/SM +lawnmower/S +lawrencium/SM +lawsuit/MS +lawyer/DYMGS +lax/PTSRY +laxative/PSYM +laxativeness/M +laxer/A +laxes/A +laxity/SM +laxness/SM +lay/CZGSR +layabout/MS +layaway/S +layer's/IC +layer/GJDM +layered/C +layering/M +layette/SM +layman/M +laymen +layoff/MS +layout/SM +layover/SM +laypeople +layperson/S +lays/AI +layup/MS +laywoman/M +laywomen +laze/DSG +lazily +laziness/MS +lazuli/M +lazy/PTSRDG +lazybones/M +lb +lbs +lea/MS +leach/SDG +leachate +lead/SGZXJRDN +leaded/U +leaden/PGDY +leadenness/M +leader/M +leaderless +leadership/MS +leadsman/M +leadsmen +leaf/GSDM +leafage/MS +leafhopper/M +leafiness/M +leafless +leaflet/SDMG +leafstalk/SM +leafy/PTR +league/RSDMZG +leaguer/M +leak/GSRDM +leakage/SM +leaker/M +leakiness/MS +leaky/PRT +lean/YRDGTJSP +leaner/M +leaning/M +leanness/MS +leap/RDGZS +leaper/M +leapfrog/SM +leapfrogged +leapfrogging +learn/SZGJRD +learned/UA +learnedly +learnedness/M +learner/M +learning/M +learns/UA +leas/SRDGZ +lease's +lease/ARSDG +leaseback/MS +leasehold/SRMZ +leaseholder/M +leaser/MA +leash's +leash/UGSD +leasing/M +least/S +leastwise +leather/MDSG +leatherette/S +leathern +leatherneck/SM +leathery +leave/SRDJGZ +leaven/DMJGS +leavened/U +leavening/M +leaver/M +leaves/M +leaving/M +lebensraum +lecher/DMGS +lecherous/YP +lecherousness/MS +lechery/MS +lecithin/SM +lectern/SM +lecture/RSDZMG +lecturer/M +lectureship/SM +led +ledge/SRMZ +ledger/DMG +lee/MZRS +leech/MSDG +leek/SM +leer/DG +leeriness/MS +leering/Y +leery/PTR +leeward/S +leeway/MS +left/TRS +leftism/SM +leftist/SM +leftmost +leftover/MS +leftward/S +lefty/SM +leg/MS +legacy/MS +legal/SY +legalese/MS +legalism/SM +legalistic +legality/MS +legalization/MS +legalize/DSG +legalized/U +legate's/C +legate/AXCNGSD +legatee/MS +legation/AMC +legato/SM +legend/SM +legendarily +legendary/S +legerdemain/SM +legged +legginess/MS +legging/MS +leggy/PRT +leghorn/SM +legibility/MS +legible +legibly +legion/SM +legionary/S +legionnaire/SM +legislate/SDXVNG +legislation/M +legislative/SY +legislator/SM +legislature/MS +legit/S +legitimacy/MS +legitimate/SDNGY +legitimation/M +legitimatize/SDG +legitimization/MS +legitimize/RSDG +legless +legman/M +legmen +legroom/MS +legstraps +legume/SM +leguminous +legwork/SM +lei/MS +leisure/SDYM +leisureliness/MS +leisurely/P +leisurewear +leitmotif/SM +leitmotiv/MS +lemma/MS +lemme/GJ +lemming/M +lemon/GSDM +lemonade/SM +lemony +lemur/MS +lend/SRGZ +lender/M +length/MNYX +lengthen/GRD +lengthener/M +lengthily +lengthiness/MS +lengths +lengthwise +lengthy/TRP +lenience/S +leniency/MS +lenient/SY +lenitive/S +lens/SRDMJGZ +lent/A +lenticular +lentil/SM +lento/S +leonine +leopard/MS +leopardess/SM +leopardskin +leotard/MS +leper/SM +leprechaun/SM +leprosy/MS +leprous +lepta +lepton/SM +lesbian/MS +lesbianism/MS +lesion/DMSG +less/U +lessee/MS +lessen/GDS +lesser +lesses +lessing +lesson/DMSG +lessor/MS +lest/R +let/ISM +letdown/SM +lethal/YS +lethality/M +lethargic +lethargically +lethargy/MS +letter/JSZGRDM +letterbox/S +lettered/U +letterer/M +letterhead/SM +lettering/M +letterman/M +lettermen +letterpress/MS +letting/S +lettuce/SM +letup/MS +leukemia/SM +leukemic/S +leukocyte/MS +levee/SDM +leveeing +level/STZGRDYP +leveled/U +leveler/M +levelheaded/P +levelheadedness/S +leveling/U +levelness/SM +lever/SDMG +leverage/MGDS +leviathan/MS +levier/M +levitate/XNGDS +levitation/M +levity/MS +levy/SRDZG +lewd/PYRT +lewdness/MS +lewis/M +lex +lexeme/MS +lexical/Y +lexicographer/MS +lexicographic +lexicographical/Y +lexicography/SM +lexicon/SM +lg +liability/SAM +liable/AP +liaise/GSD +liaison/SM +liar/MS +lib/MS +libation/SM +libbed +libbing +libel/GMRDSZ +libeler/M +libelous/Y +liberal/YSP +liberalism/MS +liberality/MS +liberalization/SM +liberalize/GZSRD +liberalized/U +liberalizer/M +liberalness/MS +liberate/NGDSCX +liberation/MC +liberationists +liberator/SCM +libertarian/MS +libertarianism/M +libertine/MS +liberty/MS +libidinal +libidinous/PY +libidinousness/M +libido/MS +librarian/MS +library/MS +libretoes +libretos +librettist/MS +libretto/MS +lice/M +license/MGBRSD +licensed/AU +licensee/SM +licenser/M +licenses/A +licensing/A +licensor/M +licentiate/MS +licentious/PY +licentiousness/MS +lichee's +lichen/DMGS +licit/Y +lick/GRDSJ +licked/U +licker/M +lickerish +licking/M +licorice/SM +lid/MS +lidded +lidding +lidless +lido/MS +lie/DRS +lied/MR +lief/TSR +liefs/A +liege/SR +lien/SM +lier/IMA +lies/A +lieu/SM +lieut +lieutenancy/MS +lieutenant/SM +life/MZR +lifeblood/SM +lifeboat/SM +lifebuoy/S +lifeforms +lifeguard/MDSG +lifeless/PY +lifelessness/SM +lifelike/P +lifelikeness/M +lifeline/SM +lifelong +lifer/M +lifesaver/SM +lifesaving/S +lifespan/S +lifestyle/S +lifetaking +lifetime/MS +lifework/MS +lift/GZMRDS +lifter/M +liftoff/MS +ligament/MS +ligand/MS +ligate/XSDNG +ligation/M +ligature/DSGM +light's +light/ADSCG +lighted/U +lighten/ZGDRS +lightener/M +lightening/M +lighter/CM +lightered +lightering +lighters +lightest +lightface/SDM +lightheaded +lighthearted/PY +lightheartedness/MS +lighthouse/MS +lighting/MS +lightly +lightness/MS +lightning/SMD +lightproof +lightship/SM +lightweight/S +ligneous +lignite/MS +lignum +likability/MS +likable/P +likableness/MS +like/USPBY +likeability's +liked/E +likelihood/MSU +likely/UPRT +liken/GSD +likeness/MSU +liker's +liker/E +likes/E +likest +likewise +liking/SM +lilac/MS +lilliputian/S +lilt/MDSG +lilting/YP +lily/MSD +limb/SGZRDM +limber/RDYTGP +limbered/U +limberness/SM +limbers/U +limbic +limbless +limbo/GDMS +lime/DSMG +limeade/SM +limekiln/M +limelight/DMGS +limerick/SM +limestone/SM +limit's +limit/CSZGRD +limitability +limitably +limitation/MCS +limited/PSY +limitedly/U +limitedness/M +limiter/M +limiting/S +limitless/PY +limitlessness/SM +limn/GSD +limo/S +limousine/SM +limp/SGTPYRD +limper/M +limpet/SM +limpid/YP +limpidity/MS +limpidness/SM +limpness/MS +limy/TR +linage/MS +linchpin/MS +linden/MS +line's +line/AGDS +lineage/SM +lineal/Y +lineament/MS +linear/Y +linearity/MS +linearize/SDGNB +linebacker/SM +lined/U +linefeed +lineman/M +linemen +linen/SM +liner/SM +linesman/M +linesmen +lineup/S +ling/ZR +linger/ZGJRD +lingerer/M +lingerie/SM +lingering/Y +lingo/M +lingoes +lingua/M +lingual/SY +linguine +linguini's +linguist/SM +linguistic/S +linguistically +linguistics/M +liniment/MS +lining/SM +link's +link/USGD +linkable +linkage/SM +linked/A +linker/S +linking/S +linkup/S +linnet/SM +lino/M +linoleum/SM +linseed/SM +lint/SMR +lintel/SM +linter/M +linty/RST +lion/MS +lioness/SM +lionhearted +lionization/SM +lionize/ZRSDG +lionizer/M +lip/MS +lipase/M +lipid/MS +liposuction/S +lipped +lipper +lipping +lippy/TR +lipread/GSRJ +lipstick/MDSG +liq +liquefaction/SM +liquefier/M +liquefy/DRSGZ +liqueur/DMSG +liquid/SPMY +liquidate/GNXSD +liquidation/M +liquidator/SM +liquidity/SM +liquidize/ZGSRD +liquidizer/M +liquidness/M +liquor/SDMG +liquorice/SM +liquorish +lira/M +lire +lisle/SM +lisp/MRDGZS +lisper/M +lissome/P +lissomeness/M +lissomness/M +list/JMRDNGZXS +listed/U +listen/ZGRD +listener/M +lister/M +listing/M +listless/PY +listlessness/SM +lit/RZS +litany/MS +litchi/SM +lite/S +liter/M +literacy/MS +literal/PYS +literalism/M +literalistic +literalness/MS +literariness/SM +literary/P +literate/YNSP +literati +literation/M +literature/SM +lithe/PRTY +litheness/SM +lithesome +lithium/SM +lithograph/DRMGZ +lithographer/M +lithographic +lithographically +lithographs +lithography/MS +lithology/M +lithosphere/MS +lithospheric +litigant/MS +litigate/NGXDS +litigation/M +litigator/SM +litigious/PY +litigiousness/MS +litmus/SM +litotes/M +litter/SZGRDM +litterbug/SM +little/RSPT +littleneck/M +littleness/SM +littoral/S +littérateur/S +liturgic/S +liturgical/Y +liturgics/M +liturgist/MS +liturgy/SM +livability/MS +livable/U +livableness/M +livably +live/YHZTGJDSRPB +lived/A +livelihood/SM +liveliness/SM +livelong/S +lively/RTP +liven/SDG +liveness/M +liver's +liver/CSGD +liveried +liverish +liverwort/SM +liverwurst/SM +livery/CMS +liveryman/MC +liverymen/C +lives's +lives/A +livestock/SM +livid/YP +lividness/M +living/YP +livingness/M +lizard/MS +ll/C +llama/SM +llano/SM +lo +load's/A +load/SURDZG +loadable +loaded/A +loader/MU +loading/MS +loads/A +loadstar's +loadstone's +loaf/SRDMGZ +loafer/M +loam/SMDG +loamy/RT +loan/SGZRDMB +loaner/M +loaning/M +loansharking/S +loanword/S +loath/JPSRDYZG +loathe +loather/M +loathing/M +loathness/M +loathsome/PY +loathsomeness/MS +loaves/M +lob/MDSG +lobar +lobbed +lobber/MS +lobbing +lobby/GSDM +lobbyist/MS +lobe/SM +lobotomist +lobotomize/GDS +lobotomy/MS +lobster/MDGS +lobular/Y +lobularity +lobule/SM +local/SGDY +locale/MS +localisms +locality/MS +localization/MS +localize/ZGDRS +localized/U +localizer/M +localizes/U +locatable +locate/AXESDGN +locater/M +location/EMA +locational/Y +locative/S +locator's +loch/M +lochs +loci/M +lock's +lock/UGSD +lockable +locked/A +locker/SM +locket/SM +locking/S +lockjaw/SM +locknut/M +lockout/MS +locksmith/MG +locksmithing/M +locksmiths +lockstep/S +lockup/MS +loco/SDMG +locomotion/SM +locomotive/YMS +locomotor +locomotory +locoweed/MS +locus/M +locust/SM +locution/MS +lode/SM +lodestar/MS +lodestone/MS +lodge/GMZSRDJ +lodged/E +lodgepole +lodger/M +lodges/E +lodging/M +lodgment/M +loft/SGMRD +lofter/M +loftily +loftiness/SM +lofty/PTR +log's/K +log/SM +loganberry/SM +logarithm/MS +logarithmic +logarithmically +logbook/MS +loge/SMNX +logged/U +logger/SM +loggerhead/SM +loggia/SM +logging/MS +logic/SM +logical/SPY +logicality/MS +logicalness/M +logician/SM +login/S +logion/M +logistic/MS +logistical/Y +logjam/SM +logo/SM +logotype/MS +logout +logrolling/SM +logy/RT +loin/SM +loincloth/M +loincloths +loiter/RDJSZG +loiterer/M +loll/RDGS +loller/M +lollipop/MS +lolly/SM +lone/PYZR +loneliness/SM +lonely/TRP +loneness/M +loner/M +lonesome/PSY +lonesomeness/MS +long/JGTYRDPS +longboat/MS +longbow/SM +longed/K +longeing +longer/K +longevity/MS +longhair/SM +longhand/SM +longhorn/SM +longing/MY +longish +longitude/MS +longitudinal/Y +longness/M +longs/K +longshoreman/M +longshoremen +longsighted +longstanding +longsword +longterm +longtime +longueur/SM +longways +longword/SM +loofah/M +loofahs +look/GZRDS +lookahead +lookalike/S +looker/M +lookout/MS +lookup/SM +loom/MDGS +looming/M +loon/MS +loony/SRT +loop/MRDGS +looper/M +loophole/MGSD +loopy/TR +loose/SRDPGTY +loosed/U +looseleaf +loosen/UDGS +loosener/M +looseness/MS +looses/U +loosing/M +loot/MRDGZS +looter/M +lop/SDRG +lope/S +loper/M +lopped +lopper/MS +lopping +lopsided/YP +lopsidedness/SM +loquacious/YP +loquaciousness/MS +loquacity/SM +lord/MYDGS +lording/M +lordliness/SM +lordly/PTR +lordship/SM +lore/MS +lorgnette/SM +loris/SM +lorn +lorry/SM +lorryload/S +lose/ZGJBSR +loser/M +loss/SM +lossage +lossless +lossy/RT +lost/P +lot/MS +lotion/MS +lotted +lotter +lottery/MS +lotting +lotto/MS +lotus/SM +loud/YRNPT +louden/DG +loudhailer/S +loudly/RT +loudmouth/DM +loudmouths +loudness/MS +loudspeaker/SM +loudspeaking +lounge/SRDZG +lounger/M +lour/GSD +louse's +louse/CSDG +lousewort/M +lousily +lousiness/MS +lousy/PRT +lout/SGMD +loutish/YP +loutishness/M +louver/DMS +lovable/U +lovableness/MS +lovably +love/DSRMYZGJB +lovebird/SM +lovechild +loved/U +loveless/YP +lovelessness/M +lovelies +loveliness/UM +lovelinesses +lovelorn/P +lovelornness/M +lovely/URPT +lovemaking/SM +lover/YMG +lovesick +lovestruck +loving/U +lovingly +lovingness/M +low/PDRYSZTG +lowborn +lowboy/SM +lowbrow/MS +lowdown/S +lower/DG +lowercase/GSD +lowermost +lowish +lowland/RMZS +lowlife/SM +lowlight/MS +lowliness/MS +lowly/PTR +lowness/MS +lox/MDSG +loyal/EY +loyaler +loyalest +loyalism/SM +loyalist/SM +loyalty/EMS +lozenge/SDM +ls +ltd +luau/MS +lubber/YMS +lube/DSMG +lubricant/SM +lubricate/VNGSDX +lubrication/M +lubricator/MS +lubricious/Y +lubricity/SM +lucent/Y +lucid/YP +lucidity/MS +lucidness/MS +luck/GSDM +luckier/U +luckily/U +luckiness/UMS +luckless +lucky/RSPT +lucrative/YP +lucrativeness/SM +lucre/MS +lucubrate/GNSDX +lucubration/M +ludicrous/PY +ludicrousness/SM +ludo/M +luff/GSDM +lug/RS +luge/MC +luggage/SM +lugged +lugger/SM +lugging +lugsail/SM +lugubrious/YP +lugubriousness/MS +lukewarm/PY +lukewarmness/SM +lull/SDG +lullaby/GMSD +lulu/M +lumbago/SM +lumbar/S +lumber/RDMGZSJ +lumberer/M +lumbering/M +lumberjack/MS +lumberman/M +lumbermen +lumberyard/MS +lumen/M +luminance/M +luminary/MS +luminescence/SM +luminescent +luminosity/MS +luminous/YP +luminousness/M +lummox/MS +lump/SGMRDN +lumper/M +lumpiness/MS +lumpish/YP +lumpishness/M +lumpy/TPR +lunacy/MS +lunar/S +lunary +lunate/YND +lunatic/S +lunation/M +lunch/GMRSD +luncheon/SMDG +luncheonette/SM +luncher/M +lunchpack +lunchroom/MS +lunchtime/MS +lune/M +lung/SGRDM +lunge/MS +lunger/M +lungfish/SM +lungful +lunkhead/SM +lupine/SM +lupus/SM +lurch/RSDG +lurcher/M +lure/DSRG +lurer/M +lurex +lurid/YP +luridness/SM +lurk/GZSRD +lurker/M +luscious/PY +lusciousness/MS +lush/YSRDGTP +lushness/MS +lust/MRDGZS +luster/GDM +lustering/M +lusterless +lustful/PY +lustfulness/M +lustily +lustiness/MS +lustrous/PY +lustrousness/M +lusty/PRT +lutanist/MS +lute/DSMG +lutenist/MS +lutetium/MS +luting/M +luxe/MS +luxuriance/MS +luxuriant/Y +luxuriate/GNSDX +luxuriation/M +luxurious/PY +luxuriousness/SM +luxury/MS +lyceum/MS +lychee's +lycopodium/M +lye/JSMG +lying/Y +lymph/M +lymphatic/S +lymphocyte/SM +lymphoid +lymphoma/MS +lymphs +lynch/ZGRSDJ +lyncher/M +lynching/M +lynx/MS +lyre/SM +lyrebird/MS +lyric/S +lyrical/YP +lyricalness/M +lyricism/SM +lyricist/SM +lysine/M +m's/K +m/ASK +ma'am +ma/MH +mac/SGMDR +macabre/Y +macadam/SM +macadamize/SDG +macaque/SM +macaroni/SM +macaroon/MS +macaw/SM +mace/MS +macer/M +macerate/DSXNG +maceration/M +machete/SM +machinate/SDXNG +machination/M +machine/MGSDB +machinelike +machinery/SM +machinist/MS +machismo/SM +macho/S +macintosh's +mack/M +mackerel/SM +mackinaw/SM +mackintosh/SM +macramé/S +macro/SM +macrobiotic/S +macrobiotics/M +macrocosm/MS +macrodynamic +macroeconomic/S +macroeconomics/M +macromolecular +macromolecule/SM +macron/MS +macrophage/SM +macroscopic +macroscopically +macrosimulation +macrosocioeconomic +mad/PSY +madam/SM +madame/M +madcap/S +madded +madden/GSD +maddening/Y +madder/MS +maddest +madding +made/AU +mademoiselle/MS +madhouse/SM +madman/M +madmen +madness/SM +madras/SM +madrigal/MSG +madwoman/M +madwomen +maelstrom/SM +maestro/MS +mafia/S +mafiosi +mafioso/M +mag/S +magazine/DSMG +magenta/MS +magged +magging +maggot/MS +maggoty/RT +magi +magic/SM +magical/Y +magician/MS +magicked +magicking +magisterial/Y +magistracy/MS +magistrate/MS +magma/SM +magnanimity/SM +magnanimosity +magnanimous/PY +magnate/SM +magnesia/MS +magnesite/M +magnesium/SM +magnet/SM +magnetic/S +magnetically +magnetics/M +magnetism/SM +magnetite/SM +magnetizable +magnetization/ASCM +magnetize/CGDS +magnetized/U +magneto/MS +magnetodynamics +magnetohydrodynamical +magnetohydrodynamics/M +magnetometer/MS +magnetosphere/M +magnetron/M +magnification/M +magnificence/SM +magnificent/Y +magnified/U +magnify/DRSGNXZ +magniloquence/MS +magniloquent +magnitude/SM +magnolia/SM +magnum/SM +magpie/SM +maharajah/M +maharajahs +maharanee's +maharani/MS +maharishi/SM +mahatma/SM +mahjong's +mahogany/MS +mahout/SM +maid/SMNX +maiden/YM +maidenhair/MS +maidenhead/SM +maidenhood/SM +maidenly/P +maidservant/MS +maier +mail/BSJGZMRD +mailbag/MS +mailbox/MS +mailer/M +maillot/SM +mailman/M +mailmen +maim/SGZRD +maimed/P +maimedness/M +maimer/M +main/SA +mainbrace/M +mainframe/MS +mainland/SRMZ +mainlander/M +mainline/RSDZG +mainliner/M +mainly +mainmast/SM +mains/M +mainsail/SM +mainspring/SM +mainstay/MS +mainstream/DRMSG +maintain/BRDZGS +maintainability +maintainable/U +maintained/U +maintainer/M +maintenance/SM +maintop/SM +maiolica's +maisonette/MS +maize/MS +majestic +majestically +majesty/MS +majolica/SM +major/DMGS +majordomo/S +majorette/SM +majority/SM +makable +make/UGSA +makefile/S +makeover/S +maker/SM +makeshift/S +makeup/MS +making/SM +malachite/SM +maladapt/DV +maladjust/DLV +maladjustment/MS +maladministration +maladroit/YP +maladroitness/MS +malady/MS +malaise/SM +malamute/SM +malaprop +malapropism/SM +malaria/MS +malarial +malarious +malarkey/SM +malathion/S +malcontent/SMD +malcontented/PY +malcontentedness/M +male/PSM +maledict +malediction/MS +malefaction/MS +malefactor/MS +malefic +maleficence/MS +maleficent +maleness/MS +malevolence/S +malevolencies +malevolent/Y +malfeasance/SM +malfeasant +malformation/MS +malformed +malfunction/SDG +malice/MGSD +malicious/YU +maliciousness/MS +malign/GSRDYZ +malignancy/SM +malignant/YS +malignity/MS +malinger/GZRDS +malingerer/M +mall/SGMD +mallard/SM +malleability/SM +malleable/P +malleableness/M +mallet/MS +mallow/MS +malnourished +malnutrition/SM +malocclusion/MS +malodorous +malposed +malpractice/SM +malt/SGMD +malted/S +malting/M +maltose/SM +maltreat/GDSL +maltreatment/S +malty/RT +mama/SM +mamba/SM +mambo/GSDM +mamma's +mammal/SM +mammalian/SM +mammary +mammogram/S +mammography/S +mammon/SM +mammoth/M +mammoths +mammy/SM +man's +man/USY +manacle/SDMG +manage/ZLGRSD +manageability/S +manageable/U +manageableness +managed/U +management/SM +manager/M +manageress/M +managerial/Y +managership/M +mananas +manatee/SM +manciple/M +mandala/SM +mandamus/GMSD +mandarin/MS +mandate/SDMG +mandatory/S +mandible/MS +mandibular +mandolin/MS +mandrake/MS +mandrel/SM +mandrill/SM +mane/MDS +maneuver/MRDSGB +maneuverability/MS +maneuverer/M +manful/Y +manganese/MS +mange/GMSRDZ +manger/M +manginess/S +mangle/RSDG +mangler/M +mango/M +mangoes +mangrove/MS +mangy/PRT +manhandle/GSD +manhole/MS +manhood/MS +manhunt/SM +mania/SM +maniac/SM +maniacal/Y +manic/S +manically +manicure/MGSD +manicurist/SM +manifest/YDPGS +manifestation/SM +manifesto/GSDM +manifold/GPYRDMS +manifolder/M +manifoldness/M +manikin/MS +manila/S +manilla's +manioc/SM +manipulability +manipulable +manipulate/SDXBVGN +manipulative/PM +manipulator/MS +manipulatory +mankind/M +manlike +manliness's/U +manliness/SM +manly/URPT +manna/MS +manned/U +mannequin/MS +manner/SDYM +mannered/U +mannerism/SM +mannerist/M +mannerliness/MU +mannerly/UP +mannikin's +manning/U +mannish/YP +mannishness/SM +manometer/SM +manor/MS +manorial +manpower/SM +manqué/M +mans/S +mansard/SM +manse/XNM +manservant/M +mansion/M +manslaughter/SM +manta/MS +mantel/SM +mantelpiece/MS +mantes +mantilla/MS +mantis/SM +mantissa/SM +mantle's +mantle/ESDG +mantling/M +mantra/MS +mantrap/SM +manual/SMY +manufacture/JZGDSR +manufacturer/M +manumission/MS +manumit/S +manumitted +manumitting +manure/RSDMZG +manuscript/MS +many +manège/GSD +map/SM +maple/MS +mapmaker/S +mappable +mapped/UA +mapper/S +mapping/MS +maps/AU +mar/S +marabou/MS +marabout's +maraca/MS +maraschino/SM +marathon/MRSZ +marathoner/M +maraud/ZGRDS +marauder/M +marble/JRSDMG +marbleize/GSD +marbler/M +marbling/M +march/RSDZG +marcher/M +marchioness/SM +mare/MS +margarine/MS +margarita/SM +margin/GSDM +marginal/YS +marginalia +marginality +marginalization +marginalize/SDG +maria/M +mariachi/SM +marigold/MS +marijuana/SM +marimba/SM +marina/MS +marinade/MGDS +marinara/SM +marinate/NGXDS +marination/M +marine/ZRS +mariner/M +marionette/MS +marital/Y +maritime/R +marjoram/SM +mark/GZRDMBSJ +markdown/SM +marked/AU +markedly +marker/M +market/GSMRDJBZ +marketability/SM +marketable/U +marketeer/S +marketer/M +marketing/M +marketplace/MS +marking/M +markka/M +markkaa +marks/A +marksman/M +marksmanship/S +marksmen +markup/SM +marl/MDSG +marlin/SM +marlinespike/SM +marmalade/MS +marmoreal +marmoset/MS +marmot/SM +maroon/GRDS +marque/SM +marquee/MS +marquess/MS +marquetry/SM +marquis/SM +marquise/M +marquisette/MS +marred/U +marriage/ASM +marriageability/SM +marriageable +married/US +marring +marrow/GDMS +marrowbone/MS +marry/SDGA +marsh/MS +marshal/GMDRSZ +marshaller +marshallings +marshiness/M +marshland/MS +marshmallow/SM +marshy/PRT +marsupial/MS +mart/MDNGXS +marten/M +martial/Y +martin/SM +martinet/SM +martingale/MS +martini/MS +martyr/GDMS +martyrdom/SM +marvel/DGS +marvelous/PY +marzipan/SM +mas/SRZ +masc +mascara/SGMD +mascot/SM +masculine/PYS +masculineness/M +masculinity/SM +maser/M +mash/JGZMSRD +mask/GZSRDMJ +masked/U +masker/M +masks/U +masochism/MS +masochist/MS +masochistic +masochistically +mason/SDMG +masonic +masonry/MS +masque/RSMZ +masquer/M +masquerade/RSDGMZ +masquerader/M +mass/VGSD +massacre/DRSMG +massage/SRDMG +massager/M +masseur/MS +masseuse/SM +massif/SM +massing/R +massive/YP +massiveness/SM +massless +mast/GZSMRD +mastectomy/MS +master/JGDYM +masterclass +mastered/A +masterful/YP +masterfulness/M +masterliness/M +masterly/P +mastermind/GDS +masterpiece/MS +mastership/M +masterstroke/MS +masterwork/S +mastery/MS +masthead/SDMG +mastic/SM +masticate/SDXGN +mastication/M +mastiff/MS +mastodon/MS +mastoid/S +masturbate/SDNGX +masturbation/M +masturbatory +mat/SJGMDR +matador/SM +match's/A +match/BMRSDZGJ +matchable/U +matchbook/SM +matchbox/SM +matched/UA +matcher/M +matches/A +matchless/Y +matchlock/MS +matchmake/GZJR +matchmaker/M +matchmaking/M +matchplay +matchstick/MS +matchwood/SM +mate/IMS +mated/U +mater/M +material/SPYM +materialism/SM +materialist/SM +materialistic +materialistically +materiality/M +materialization/SM +materialize/CDS +materialized/A +materializer/SM +materializes/A +materializing +materialness/M +maternal/Y +maternity/MS +mates/U +math/M +mathematic/S +mathematical/Y +mathematician/SM +mathematics/M +maths +mating/M +matins/M +matinée/S +matriarch/M +matriarchal +matriarchs +matriarchy/MS +matrices +matricidal +matricide/MS +matriculate/XSDGN +matriculation/M +matrimonial/Y +matrimony/SM +matrix/M +matron/YMS +matt's +matte/JGMZSRD +matter/GDM +matting/M +mattins's +mattock/MS +mattress/MS +maturate/DSNGVX +maturation/M +maturational +mature/RSDTPYG +matureness/M +maturer/M +maturity/MS +matzo/SHM +matzot +matériel/MS +maudlin/Y +maul/RDGZS +mauler/M +maunder/GDS +mausoleum/SM +mauve/SM +maven/S +maverick/SMDG +mavin's +maw/SGMD +mawkish/PY +mawkishness/SM +max/GDS +maxi/S +maxilla/M +maxillae +maxillary/S +maxim/SM +maxima's +maximal/SY +maximality +maximization/SM +maximize/RSDZG +maximizer/M +maximum/MYS +maxwell/M +may/EGS +maybe/S +mayday/S +mayer +mayest +mayflower/SM +mayfly/MS +mayhap +mayhem/MS +mayn't +mayo/S +mayonnaise/MS +mayor/MS +mayoral +mayoralty/MS +mayoress/MS +mayorship/M +maypole/MS +mayst +maze/MGDSR +mazed/YP +mazedness/SM +mazurka/SM +mañana/M +mdse +me/G +mead/SM +meadow/MS +meadowland +meadowlark/SM +meadowsweet/M +meager/PY +meagerness/SM +meagres +meal/MDGS +mealiness/MS +mealtime/MS +mealy/PRST +mealybug/S +mealymouthed +mean/YRGJTPS +meander/JDSG +meaneing +meanie/MS +meaning/M +meaningful/YP +meaningfulness/SM +meaningless/PY +meaninglessness/SM +meanness/S +means/M +meant/U +meantime/SM +meanwhile/S +meany's +meas/Y +measle/SD +measles/M +measly/TR +measurable/U +measurably +measure/BLMGRSD +measured/Y +measureless +measurement/SM +measurer/M +measures/A +measuring/A +meat/MS +meataxe +meatball/MS +meatiness/MS +meatless +meatloaf +meatloaves +meatpacking/S +meaty/RPT +mecca/S +mechanic/MS +mechanical/YS +mechanism/SM +mechanist/M +mechanistic +mechanistically +mechanization/SM +mechanize/RSDZGB +mechanized/U +mechanizer/M +mechanizes/U +mechanochemically +med +medal/SGMD +medalist/MS +medallion/MS +meddle/GRSDZ +meddlesome +media/SM +mediaeval's +medial/AY +medials +median/YMS +mediate/PSDYVNGX +mediateness/M +mediation/ASM +mediator/SM +medic/SM +medical/YS +medicament/MS +medicate/DSXNGV +medication/M +medicinal/SY +medicine/DSMG +medico/SM +medieval/YMS +medievalist/MS +mediocre +mediocrity/MS +meditate/NGVXDS +meditation/M +meditative/PY +meditativeness/M +medium/SM +mediumistic +medley/SM +medulla/SM +meed/MS +meek/TPYR +meekness/MS +meerschaum/MS +meet/JGSYR +meeter/M +meeting/M +meetinghouse/S +mega +megabit/MS +megabuck/S +megabyte/S +megacycle/MS +megadeath/M +megadeaths +megahertz/M +megalith/M +megalithic +megaliths +megalomania/SM +megalomaniac/SM +megalopolis/SM +megaphone/SDGM +megaton/MS +megavolt/M +megawatt/SM +megaword/S +megohm/MS +meioses +meiosis/M +meiotic +melamine/SM +melancholia/SM +melancholic/S +melancholy/MS +melange/S +melanin/MS +melanoma/SM +meld/SGD +meliorate/XSDVNG +melioration/M +mellifluous/YP +mellifluousness/SM +mellow/TGRDYPS +mellowness/MS +melodic/S +melodically +melodious/YP +melodiousness/S +melodrama/SM +melodramatic/S +melodramatically +melody/MS +melon/MS +melt/SAGD +meltdown/S +melter/M +melting/Y +member/DMS +membered/AE +members/EA +membership/SM +membrane/MSD +membranous +memento/SM +memo/SM +memoir/MS +memorabilia +memorability/SM +memorable/P +memorableness/M +memorably +memorandum/SM +memorial/SY +memorialize/DSG +memorialized/U +memoriam +memorization/MS +memorize/RSDZG +memorized/U +memorizer/M +memorizes/A +memory/MS +memoryless +men/MS +menace/GSD +menacing/Y +menage/S +menagerie/SM +menarche/MS +mend/RDSJGZ +mendacious/PY +mendaciousness/M +mendacity/MS +mendelevium/SM +mender/M +mendicancy/MS +mendicant/S +mending/M +menfolk/S +menhaden/M +menial/YS +meningeal +meninges +meningitides +meningitis/M +meninx +menisci +meniscus/M +menopausal +menopause/SM +menorah/M +menorahs +mens/SDG +mensch/S +menservants/M +menstrual +menstruate/NGDSX +menstruation/M +mensurable/P +mensuration/MS +menswear/M +mental/Y +mentalist/MS +mentality/MS +menthol/SM +mentholated +mention/ZGBRDS +mentionable/U +mentioned/U +mentioner/M +mentor/DMSG +menu/SM +meow/DSG +mer/TGDR +mercantile +mercenariness/M +mercenary/SMP +mercer/SM +mercerize/SDG +merchandise/SRDJMZG +merchandiser/M +merchant/SBDMG +merchantability +merchantman/M +merchantmen +merciful/YP +mercifully/U +mercifulness/M +merciless/YP +mercilessness/SM +mercurial/SPY +mercuric +mercury/MS +mercy/SM +mere/YS +meretricious/YP +meretriciousness/SM +merganser/MS +merge/SRDGZ +merger/M +meridian/MS +meridional +meringue/MS +merino/MS +merit/SCGMD +merited/U +meritocracy/MS +meritocratic +meritocrats +meritorious/PY +meritoriousness/MS +merlin/M +mermaid/MS +merman/M +mermen +meromorphic +merrily +merriment/MS +merriness/S +merry/RPT +merrymaker/MS +merrymaking/SM +mes/S +mesa/SM +mescal/SM +mescaline/SM +mesdames/M +mesdemoiselles/M +mesh/GMSD +meshed/U +mesmeric +mesmerism/SM +mesmerize/SRDZG +mesmerized/U +mesmerizer/M +mesomorph/M +mesomorphs +meson/MS +mesosphere/MS +mesozoic +mesquite/MS +mess/GSDM +message/SDMG +messeigneurs +messenger/GSMD +messiah +messiahs +messianic +messieurs/M +messily +messiness/MS +messmate/MS +messy/PRT +mestizo/MS +met/U +meta +metabolic +metabolically +metabolism/MS +metabolite/SM +metabolize/GSD +metacarpal/S +metacarpi +metacarpus/M +metacircular +metacircularity +metal/SGMD +metalanguage/MS +metalization/SM +metalized +metallic/S +metalliferous +metallings +metallography/M +metalloid/M +metallurgic +metallurgical/Y +metallurgist/S +metallurgy/MS +metalsmith/MS +metalwork/RMJGSZ +metalworking/M +metamathematical +metamorphic +metamorphism/SM +metamorphose/GDS +metamorphosis/M +metaphor/MS +metaphoric +metaphorical/Y +metaphosphate/M +metaphysic/SM +metaphysical/Y +metastability/M +metastable +metastases +metastasis/M +metastasize/DSG +metastatic +metatarsal/S +metatarsi +metatarsus/M +metatheses +metathesis/M +metathesized +metathesizes +metathesizing +metavariable +mete/ZDGSR +metempsychoses +metempsychosis/M +meteor/SM +meteoric +meteorically +meteorite/SM +meteoritic/S +meteoritics/M +meteoroid/SM +meteorologic +meteorological +meteorologist/S +meteorology/MS +meter/GDM +methadone/SM +methane/MS +methanol/SM +methinks +methionine/M +method/MS +methodical/YP +methodicalness/SM +methodism +methodist/MS +methodological/Y +methodologists +methodology/MS +methought +methyl/SM +methylated +methylene/M +meticulous/YP +meticulousness/MS +metonymy/M +metric/SM +metrical/Y +metricate/SDNGX +metricize/GSD +metrics/M +metro/SM +metronome/MS +metropolis/SM +metropolitan/S +metropolitanization +mets +mettle/SDM +mettlesome +mew/SGD +mewl/GSD +mews/SM +mezzanine/MS +mezzo/S +mfg +mfr/S +mg +mgr +mi/MNX +miasma/SM +miasmal +mica/MS +mice/M +micelles +mickey/SM +micra's +micro/S +microamp +microanalysis/M +microanalytic +microbe/MS +microbial +microbicidal +microbicide/M +microbiological +microbiologist/MS +microbiology/SM +microbrewery/S +microchemistry/M +microchip/S +microcircuit/MS +microcode/GSD +microcomputer/MS +microcosm/MS +microcosmic +microdensitometer +microdot/MS +microeconomic/S +microeconomics/M +microelectronic/S +microelectronics/M +microfiber/S +microfiche/M +microfilm/DRMSG +microfossils +micrography/M +microgroove/MS +microhydrodynamics +microinstruction/SM +microjoule +microlevel +microlight/S +micromanage/GDSL +micromanagement/S +micrometeorite/MS +micrometeoritic +micrometer/SM +micron/MS +microorganism/SM +microphone/SGM +microprocessing +microprocessor/SM +microprogram/SM +microprogrammed +microprogramming +micros/M +microscope/SM +microscopic +microscopical/Y +microscopy/MS +microsecond/MS +microsimulation/S +microsomal +microstore +microsurgery/SM +microvolt/SM +microwave/BMGSD +microwaveable +microword/S +mid/S +midair/MS +midas +midband/M +midday/MS +midden/SM +middest +middle/GJRSD +middlebrow/SM +middleman/M +middlemen +middlemost +middleweight/SM +middling/Y +middy/SM +midfield/RM +midge/SM +midget/MS +midi/S +midland/MRS +midlife +midlives +midmorn/G +midmost/S +midnight/SYM +midpoint/MS +midrange +midrib/MS +midriff/MS +midscale +midsection/M +midship/S +midshipman/M +midshipmen +midspan +midst/SM +midstream/MS +midsummer/MS +midterm/MS +midtown/MS +midway/S +midweek/SYM +midwicket +midwife/SDMG +midwifery/SM +midwinter/YMS +midwives +midyear/MS +mien/M +miff/GDS +might/S +mightily +mightiness/MS +mightn't +mighty/TPR +mignon +mignonette/SM +migraine/SM +migrant/MS +migrate/ASDG +migration/MS +migrative +migratory/S +mikado/MS +mike/DSMG +mil/MRSZ +milady/MS +milch/M +mild/STYRNP +mildew/DMGS +mildness/MS +mile/SM +mileage/SM +milepost/SM +miler/M +milestone/MS +milieu/SM +militancy/MS +militant/YPS +militantness/M +militarily +militarism/SM +militarist/MS +militaristic +militarization/SCM +militarize/SDCG +military +militate/SDG +militia/SM +militiaman/M +militiamen +milk/GZSRDM +milker/M +milkiness/MS +milkmaid/SM +milkman/M +milkmen +milkshake/S +milksop/SM +milkweed/MS +milky/RPT +mill/SGZMRD +millage/S +millenarian +millenarianism/M +millennial +millennialism +millennium/MS +millepede's +miller/M +millet/MS +milliamp +milliampere/S +milliard/MS +millibar/MS +millidegree/S +milligram/MS +millijoule/S +milliliter/MS +millimeter/SM +milliner/SM +millinery/MS +milling/M +million/HDMS +millionaire/MS +millionth/M +millionths +millipede/SM +millisecond/MS +millivolt/SM +millivoltmeter/SM +milliwatt/S +millpond/MS +millrace/SM +millstone/SM +millstream/SM +millwright/MS +milquetoast/SM +milt/MDSG +mime/DSRMG +mimeograph/GMDS +mimeographs +mimer/M +mimesis/M +mimetic +mimetically +mimic/S +mimicked +mimicker/SM +mimicking +mimicry/MS +mimosa/SM +min/DRZGJ +minaret/MS +minatory +mince/SRDGZJ +mincemeat/MS +mincer/M +mincing/Y +mind's +mind/ARDSZG +mindbogglingly +minded/P +minder/M +mindful/U +mindfully +mindfulness/MS +mindless/YP +mindlessness/SM +mindset/S +mine/SNX +minefield/MS +miner/M +mineral/SM +mineralization/C +mineralized/U +mineralogical +mineralogist/SM +mineralogy/MS +mineshaft +minestrone/MS +minesweeper/MS +mineworkers +mingle/SDG +mini/S +miniature/GMSD +miniaturist/SM +miniaturization/MS +miniaturize/SDG +minibike/S +minibus/SM +minicab/M +minicam/MS +minicomputer/SM +minidress/SM +minify/GSD +minim/SM +minima's +minimal/SY +minimalism/S +minimalist/MS +minimalistic +minimality +minimax/M +minimization/MS +minimize/RSDZG +minimized/U +minimizer/M +minimum/MS +mining/M +minion/M +miniseries +miniskirt/MS +minister/MDGS +ministerial/Y +ministrant/S +ministration/SM +ministry/MS +minivan/S +miniver/M +mink/SM +minke +minnesinger/MS +minnow/SM +minor/DMSG +minority/MS +minotaur/S +minoxidil/S +minster/SM +minstrel/SM +minstrelsy/MS +mint/GZSMRD +mintage/SM +minter/M +minty/RT +minuend/SM +minuet/SM +minus/S +minuscule/SM +minute/RSDPMTYG +minuteman +minutemen +minuteness/SM +minutia/M +minutiae +minx/MS +miracle/MS +miraculous/PY +miraculousness/M +mirage/GSDM +mire/MGDS +mirror/DMGS +mirth/M +mirthful/PY +mirthfulness/SM +mirthless/YP +mirthlessness/M +mirths +miry/RT +mis/SRZ +misaddress/SDG +misadventure/SM +misalign/DSGL +misalignment/MS +misalliance/MS +misanalysed +misandrist +misandry +misanthrope/MS +misanthropic +misanthropically +misanthropist/S +misanthropy/SM +misapplier/M +misapply/GNXRSD +misapprehend/GDS +misapprehension/MS +misappropriate/GNXSD +misbegotten +misbehave/RSDG +misbehaver/M +misbehavior/SM +misbrand/DSG +misc +miscalculate/XGNSD +miscalculation/M +miscall/SDG +miscarriage/MS +miscarry/SDG +miscast/GS +miscegenation/SM +miscellanea +miscellaneous/PY +miscellany/MS +mischance/MGSD +mischief/MDGS +mischievous/PY +mischievousness/MS +miscibility/S +miscible/C +misclassification/M +misclassified +misclassifying +miscode/SDG +miscommunicate/NDS +miscomprehended +misconceive/GDS +misconception/MS +misconduct/GSMD +misconfiguration +misconstruction/MS +misconstrue/DSG +miscopying +miscount/DGS +miscreant/MS +miscue/MGSD +misdeal/SG +misdealt +misdeed/MS +misdemeanant/SM +misdemeanor/SM +misdiagnose/GSD +misdid +misdirect/GSD +misdirection/MS +misdirector/S +misdo/JG +misdoes +misdone +miser/KM +miserable/SP +miserableness/SM +miserably +miserliness/MS +miserly/P +misery/MS +mises/KC +misfeasance/MS +misfeature/M +misfield +misfile/SDG +misfire/SDG +misfit/MS +misfitted +misfitting +misfortune/SM +misgauge/GDS +misgiving/MYS +misgovern/LDGS +misgovernment/S +misguidance/SM +misguide/DRSG +misguided/PY +misguidedness/M +misguider/M +mishandle/SDG +mishap/MS +mishapped +mishapping +mishear/GS +misheard +mishitting +mishmash/SM +misidentification/M +misidentify/GNSD +misinform/GDS +misinformation/SM +misinterpret/RDSZG +misinterpretation/MS +misinterpreter/M +misjudge/DSG +misjudging/Y +misjudgment/MS +mislabel/DSG +mislaid +mislay/GS +mislead/GRJS +misleader/M +misleading/Y +misled +mismanage/LGSD +mismanagement/MS +mismatch/GSD +misname/GSD +misnomer/GSMD +misogamist/MS +misogamy/MS +misogynist/MS +misogynistic +misogynous +misogyny/MS +misperceive/SD +misplace/GLDS +misplacement/MS +misplay/GSD +mispositioned +misprint/SGDM +misprision/SM +mispronounce/DSG +mispronunciation/MS +misquotation/MS +misquote/GDS +misread/RSGJ +misreader/M +misrelated +misremember/DG +misreport/DGS +misrepresent/SDRG +misrepresentation/MS +misrepresenter/M +misroute/DS +misrule/SDG +miss/SDEGV +missal/ESM +misshape/DSG +misshapen/PY +misshapenness/SM +missile/MS +missilery/SM +mission/AMS +missionary/MS +missioned +missioner/SM +missioning +missis's +missive/MS +misspeak/SG +misspecification +misspecified +misspell/SGJD +misspelling/M +misspend/GS +misspent +misspoke +misspoken +misstate/GLDRS +misstatement/MS +misstater/M +misstep/MS +misstepped +misstepping +missus/SM +mist/MRDGZS +mistakable/U +mistake/BMGSR +mistaken/Y +mistaker/M +mistaking/Y +mister/GDM +mistily +mistime/GSD +mistiness/S +mistletoe/MS +mistook +mistral/MS +mistranslated +mistranslates +mistranslating +mistranslation/SM +mistreat/DGSL +mistreatment/SM +mistress/MSY +mistrial/SM +mistrust/SRDG +mistruster/M +mistrustful/Y +misty/PRT +mistype/SDGJ +misunderstand/JSRZG +misunderstander/M +misunderstanding/M +misunderstood +misuse/RSDMG +misuser/M +miswritten +mite/SRMZ +miter/GRDM +miterer/M +mitigate/XNGVDS +mitigated/U +mitigation/M +mitoses +mitosis/M +mitotic +mitt/XSMN +mitten/M +mitzvahs +mix/AGSD +mixable +mixed/U +mixer/SM +mixture/SM +mizzen/MS +mizzenmast/SM +mks +ml +mm +mnemonic/SM +mnemonically +mnemonics/M +mo/CSK +moan/GSZRDM +moat/SMDG +mob/MS +mobbed +mobber +mobbing +mobcap/SM +mobile/S +mobility/MS +mobilizable +mobilization/AMCS +mobilize/CGDS +mobilized/U +mobilizer/MS +mobilizes/A +mobster/MS +moccasin/SM +mocha/SM +mock/GZSRD +mockers/M +mockery/MS +mocking/Y +mockingbird/MS +mod/TSR +modal/Y +modality/MS +mode/MS +model/ZGSJMRD +modeled/A +modeler/M +modeling/M +models/A +modem/SM +moderate/PNGDSXY +moderated/U +moderateness/SM +moderation/M +moderator/MS +modern/PTRYS +modernism/MS +modernist/S +modernistic +modernity/SM +modernization/MS +modernize/SRDGZ +modernized/U +modernizer/M +modernizes/U +modernness/SM +modest/TRY +modesty/MS +modicum/SM +modifiability/M +modifiable/U +modifiableness/M +modification/M +modified/U +modifier/M +modify/NGZXRSD +modish/YP +modishness/MS +modular/SY +modularity/SM +modularization +modularize/SDG +modulate/ADSNCG +modulation/CMS +modulator/ACSM +module/SM +moduli +modulo +modulus/M +modus +mogul/MS +mohair/SM +moiety/MS +moil/SGD +moire/MS +moist/TXPRNY +moisten/ZGRD +moistener/M +moistness/MS +moisture/MS +moisturize/GZDRS +molal +molar/MS +molarity/SM +molasses/MS +mold/MRDJSGZ +moldboard/SM +molder/DG +moldiness/SM +molding/M +moldy/PTR +mole/MTS +molecular/Y +molecularity/SM +molecule/MS +molehill/SM +moleskin/MS +molest/RDZGS +molestation/SM +molested/U +molester/M +moll/MS +mollification/M +mollify/XSDGN +mollusc's +mollusk/S +molly/SM +mollycoddle/SRDG +mollycoddler/M +molt/RDNGZS +molter/M +molybdenite/M +molybdenum/MS +mom/SM +moment/MYS +momenta +momentarily +momentariness/SM +momentary/P +momentous/YP +momentousness/MS +momentum/SM +momma/S +mommy/SM +monad/SM +monadic +monarch/M +monarchic +monarchical +monarchism/MS +monarchist/MS +monarchistic +monarchs +monarchy/MS +monastery/MS +monastic/S +monastical/Y +monasticism/MS +monaural/Y +monetarily +monetarism/S +monetarist/MS +monetary +monetization/CMA +monetize/CGADS +money/SMRD +moneybag/SM +moneychangers +moneyer/M +moneylender/SM +moneymaker/MS +moneymaking/MS +monger/SGDM +mongolism/SM +mongoloid/S +mongoose/SM +mongrel/SM +monies/M +moniker/MS +monism/MS +monist/SM +monition/SM +monitor/GSMD +monitored/U +monitory/S +monk/MS +monkey/SMDG +monkeyshine/S +monkish +monkshood/SM +mono/MS +monochromatic +monochromator +monochrome/MS +monocle/SDM +monoclinic +monoclonal/S +monocotyledon/SM +monocotyledonous +monocular/SY +monodic +monodist/S +monody/MS +monogamist/MS +monogamous/PY +monogamy/MS +monogram/MS +monogrammed +monogramming +monograph/GMDS +monographs +monolingual/S +monolingualism +monolith/M +monolithic +monolithically +monoliths +monologist/S +monologue/GMSD +monomania/MS +monomaniac/MS +monomaniacal +monomer/SM +monomeric +monomial/SM +mononuclear +mononucleoses +mononucleosis/M +monophonic +monoplane/MS +monopole/S +monopolist/MS +monopolistic +monopolization/MS +monopolize/GZDSR +monopolized/U +monopolizes/U +monopoly/MS +monorail/SM +monostable +monosyllabic +monosyllable/MS +monotheism/SM +monotheist/S +monotheistic +monotone/SDMG +monotonic +monotonically +monotonicity +monotonous/YP +monotonousness/MS +monotony/MS +monovalent +monoxide/SM +monseigneur +monsieur/M +monsignor/S +monsoon/MS +monsoonal +monster/SM +monstrance/ASM +monstrosity/SM +monstrous/YP +monstrousness/M +montage/SDMG +month/MY +monthly/S +months +monument/DMSG +monumental/Y +monumentality/M +moo/GSD +mooch/ZSRDG +mood/MS +moodily +moodiness/MS +moody/PTR +moon/GDMS +moonbeam/SM +moonless +moonlight/GZDRMS +moonlighting/M +moonlit +moonscape/MS +moonshine/SRZM +moonshiner/M +moonshot/MS +moonstone/SM +moonstruck +moonwalk/SDG +moor/GDMJS +mooring/M +moorland/MS +moose/M +moot/RDGS +mop/SZGMDR +mope/S +moped/MS +moper/M +mopey +mopier +mopiest +mopish +mopped +moppet/MS +mopping +moraine/MS +moral/SMY +morale/MS +moralist/MS +moralistic +moralistically +morality/UMS +moralization/CS +moralize/CGDRSZ +moralled +moraller +moralling +morass/SM +moratorium/SM +moray/SM +morbid/YP +morbidity/SM +morbidness/S +mordancy/MS +mordant/GDYS +more/DSN +morel/SM +moreover +morgen/M +morgue/SM +moribund/Y +moribundity/M +morion/M +morn/SGJDM +morning/MY +morocco/SM +moron/SM +moronic +moronically +morose/YP +moroseness/MS +morph/GDJ +morpheme/DSMG +morphemic/S +morphia/S +morphine/MS +morphism/MS +morphologic +morphological/Y +morphology/MS +morphophonemic/S +morphophonemics/M +morphs +morris +morrow/MS +morsel/GMDS +mortal/SY +mortality/SM +mortar/GSDM +mortarboard/SM +mortgage/MGDS +mortgageable +mortgagee/SM +mortgagor/SM +mortice's +mortician/SM +mortification/M +mortified/Y +mortifier/M +mortify/DRSXGN +mortise/MGSD +mortuary/MS +mos/S +mosaic/MS +mosaicked +mosaicking +mosey/SGD +mosque/SM +mosquito/M +mosquitoes +moss/SDMG +mossback/MS +mossy/SRT +most/SY +mot/MSV +mote's +mote/ASCNK +motel/MS +motet/SM +moth/ZMR +mothball/DMGS +mother/RDYMZG +motherboard/MS +motherfucker/MS! +motherfucking/! +motherhood/SM +mothering/M +motherland/SM +motherless +motherliness/MS +motherly/P +moths +motif/MS +motile/S +motility/MS +motion's/ACK +motion/GRDMS +motional/K +motioner/M +motionless/YP +motionlessness/S +motions/K +motivate/XDSNGV +motivated/U +motivation/M +motivational/Y +motivator/S +motive/MGSD +motiveless +motley/S +motlier +motliest +motocross/SM +motor/DMSG +motorbike/SDGM +motorboat/MS +motorcade/MSDG +motorcar/MS +motorcycle/GMDS +motorcyclist/SM +motoring/M +motorist/SM +motorization/SM +motorize/DSG +motorized/U +motorman/M +motormen +motormouth +motormouths +motorway/SM +mottle/GSRD +mottler/M +motto/M +mottoes +moue/DSMG +moulder/DSG +moult/GSD +mound/GMDS +mount/EGACD +mountable +mountain/SM +mountaineer/JMDSG +mountaineering/M +mountainous/PY +mountainousness/M +mountainside/MS +mountaintop/SM +mountebank/SGMD +mounted/U +mounter/SM +mounties +mounting/MS +mounts/AE +mourn/ZGSJRD +mourner/M +mournful/YP +mournfuller +mournfullest +mournfulness/S +mourning/M +mouse/SRDGMZ +mouser/M +mousetrap/SM +mousetrapped +mousetrapping +mousiness/MS +mousing/M +mousse/MGSD +mousy/PRT +mouth/MSRDG +mouthful/MS +mouthiness/SM +mouthorgan +mouthpiece/SM +mouths +mouthwash/SM +mouthwatering +mouthy/PTR +mouton/SM +movable/ASP +movableness/AM +move/ARSDGZB +moved/U +movement/SM +mover/AM +movie/SM +moviegoer/S +moving/YS +mow/SDRZG +mower/M +mowing/M +moxie/MS +mozzarella/MS +mp +mpg +mph +ms +mt +mtg +mtge +mu/M +much/SP +muchness/M +mucilage/MS +mucilaginous +muck/GRDMS +mucker/M +muckrake/ZMDRSG +muckraker/M +mucky/RT +mucosa/M +mucous +mucus/SM +mud/MS +mudded +muddily +muddiness/SM +mudding +muddle/GRSDZ +muddlehead/SMD +muddleheaded/P +muddler/M +muddy/TPGRSD +mudflat/S +mudguard/SM +mudlarks +mudroom/S +mudslide/S +mudsling/JRGZ +mudslinger/M +mudslinging/M +muenster/MS +muesli/M +muezzin/MS +muff/GDMS +muffin/SM +muffle/ZRSDG +muffler/M +mufti/MS +mug/SM +mugged +mugger/SM +mugginess/S +mugging/S +muggy/RPT +mugshot/S +mugwump/MS +mukluk/SM +mulatto/M +mulattoes +mulberry/MS +mulch/GMSD +mulct/SDG +mule/MGDS +muleskinner/S +muleteer/MS +mulish/YP +mulishness/MS +mull/RDSG +mullah/M +mullahs +mullein/MS +muller/M +mullet/MS +mulligan/SM +mulligatawny/SM +mullion/MDSG +multi +multicellular +multichannel/M +multicollinearity/M +multicolor/SDM +multicolumn +multicomponent +multicomputer/MS +multicultural +multiculturalism/S +multidimensional +multidimensionality +multidisciplinary +multifaceted +multifamily +multifarious/YP +multifariousness/SM +multifigure +multiform +multifunction/D +multilateral/Y +multilayer +multilevel/D +multilingual +multilingualism/S +multimedia/S +multimegaton/M +multimeter/M +multimillionaire/SM +multinational/S +multinomial/M +multiphase +multiple/SM +multiplet/SM +multiplex/GZMSRD +multiplexor's +multipliable +multiplicand/SM +multiplication/M +multiplicative/YS +multiplicity/MS +multiplier/M +multiply/ZNSRDXG +multiprocess/G +multiprocessor/MS +multiprogram +multiprogrammed +multiprogramming/MS +multipurpose +multiracial +multistage +multistory/S +multisyllabic +multitasking/S +multitude/MS +multitudinous/YP +multitudinousness/M +multiuser +multivalent +multivalued +multivariate +multiversity/M +multivitamin/S +mum/MS +mumble/ZJGRSD +mumbler/M +mumbletypeg/S +mummed +mummer/SM +mummery/MS +mummification/M +mummify/XSDGN +mumming +mummy/GSDM +mumps/M +mun/S +munch/ZRSDG +muncher/M +munchies +mundane/YSP +munge/JGZSRD +municipal/YS +municipality/SM +munificence/MS +munificent/Y +munition/SDG +muon/M +mural/SM +muralist/SM +murder/GZRDMS +murderer/M +murderess/S +murderous/YP +murderousness/M +muriatic +murk/TRMS +murkily +murkiness/S +murky/RPT +murmur/RDMGZSJ +murmurer/M +murmuring/U +murmurous +murrain/SM +mus/GJDSR +muscat/SM +muscatel/MS +muscle/SDMG +musclebound +muscovite/MS +muscular/Y +muscularity/SM +musculature/SM +muse +muser/M +musette/SM +museum/MS +mush/MSRDG +musher/M +mushiness/MS +mushroom/DMSG +mushy/PTR +music/SM +musical/YU +musicale/SM +musicality/SM +musicals +musician/MYS +musicianship/MS +musicked +musicking +musicological +musicologist/MS +musicology/MS +musing/Y +musk/GDMS +muskeg/SM +muskellunge/SM +musket/SM +musketeer/MS +musketry/MS +muskie/M +muskiness/MS +muskmelon/MS +muskox/N +muskrat/MS +musky/RSPT +muslin/MS +muss/SDG +mussel/MS +mussy/RT +must've +must/RDGZS +mustache/DSM +mustachio/MDS +mustang/MS +mustard/MS +muster/GD +mustily +mustiness/MS +mustn't +musty/RPT +mutability/SM +mutable/P +mutableness/M +mutably +mutagen/SM +mutant/MS +mutate/XVNGSD +mutation/M +mutational/Y +mutator/S +mute/PDSRBYTG +muted/Y +muteness/S +mutilate/XDSNG +mutilation/M +mutilator/MS +mutineer/SMDG +mutinous/Y +mutiny/MGSD +mutt/ZSMR +mutter/GZRDJ +mutterer/M +mutton/SM +muttonchops +mutual/SY +mutuality/S +muumuu/MS +muzak +muzzle/MGRSD +muzzled/U +muzzler/M +my/S +mycologist/MS +mycology/MS +myelitides +myelitis/M +myers +mylar +myna/SM +myocardial +myocardium/M +myopia/MS +myopic/S +myopically +myriad/S +myrmidon/S +myrrh/M +myrrhs +myrtle/SM +mys +myself +mysterious/YP +mysteriousness/MS +mystery/MDSG +mystic/SM +mystical/Y +mysticism/MS +mystification/M +mystifier/M +mystify/CSDGNX +mystifying/Y +mystique/MS +myth/MS +mythic +mythical/Y +mythographer/SM +mythography/M +mythological/Y +mythologist/MS +mythologize/CSDG +mythology/SM +myths +métier/S +mêlée/MS +n's/CI +n/T +nab/S +nabbed +nabbing +nabob/SM +nacelle/SM +nacho/S +nacre/MS +nacreous +nadir/SM +nae/VM +nag/MS +nagged +nagger/S +nagging/Y +naiad/SM +naifs +nail/SGMRD +nailbrush/SM +nailer/M +naive/SRTYP +naivety/MS +naiveté/SM +naked/TYRP +nakedness/MS +name's +name/ADSG +nameable/U +named's +named/U +namedrop +namedropping +nameless/PY +namely +nameplate/MS +namer/SM +namesake/SM +naming/M +nanny/SDMG +nanometer/MS +nanosecond/SM +nap/SM +napalm/MDGS +nape/SM +naphtha/SM +naphthalene/MS +napkin/SM +napless +napoleon/MS +napped +napper/MS +napping +nappy/TRSM +narc/DGS +narcissism/MS +narcissist/MS +narcissistic +narcissus/M +narcoleptic +narcoses +narcosis/M +narcotic/SM +narcotization/S +narcotize/GSD +nark's +narrate/VGNSDX +narration/M +narrative/MYS +narratology +narrator/SM +narrow/RDYTGPS +narrowing/P +narrowness/SM +narwhal/MS +nary +nasal/YS +nasality/MS +nasalization/MS +nasalize/GDS +nascence/ASM +nascent/A +nastily +nastiness/MS +nasturtium/SM +nasty/TRSP +natal +natalist +natality/M +natch/S +nation/MS +national/YS +nationalism/SM +nationalist/MS +nationalistic +nationalistically +nationality/MS +nationalization/MS +nationalize/CSDG +nationalized/AU +nationalizer/SM +nationhood/SM +nationwide +native/PYS +nativeness/M +nativity/MS +natl +natter/SGD +nattily +nattiness/SM +natty/TRP +natural/PUY +naturalism/MS +naturalist/MS +naturalistic +naturalization/SM +naturalize/GSD +naturalized/U +naturalness/US +naturals +nature's +nature/ASDCG +naturist +naught/MS +naughtily +naughtiness/SM +naughty/TPRS +nausea/SM +nauseate/DSG +nauseating/Y +nauseous/P +nauseousness/SM +nautical/Y +nautilus/MS +naval/Y +nave/SM +navel/MS +navigability/SM +navigable/P +navigableness/M +navigate/DSXNG +navigation/M +navigational +navigator/MS +navvy/M +navy/SM +nay/MS +naysayer/S +ne'er +neap/DGS +near/TYRDPSG +nearby +nearly/RT +nearness/MS +nearside/M +nearsighted/YP +nearsightedness/S +neat/YRNTXPS +neaten/DG +neath +neatness/MS +nebula/M +nebulae +nebular +nebulous/PY +nebulousness/SM +necessaries +necessarily/U +necessary/U +necessitate/DSNGX +necessitation/M +necessitous +necessity/SM +neck/GRDMJS +neckband/M +neckerchief/MS +necking/M +necklace/DSMG +neckline/MS +necktie/MS +necrology/SM +necromancer/MS +necromancy/MS +necromantic +necrophilia/M +necrophiliac/S +necropolis/SM +necropsy/M +necroses +necrosis/M +necrotic +nectar/SM +nectarine/SM +nectarous +nectary/MS +need/YRDGS +needed/U +needer/M +needful/YSP +neediness/MS +needle/GMZRSD +needlecraft/M +needlepoint/SM +needless/YP +needlessness/S +needlewoman/M +needlewomen +needlework/RMS +needn't +needy/TPR +nefarious/YP +nefariousness/MS +neg/S +negate/XRSDVNG +negated/U +negater/M +negation/M +negative/PDSYG +negativeness/SM +negativism/MS +negativity/MS +negator/MS +neglect/SDRG +neglecter/M +neglectful/YP +neglectfulness/SM +negligee/SM +negligence/MS +negligent/Y +negligibility/M +negligible +negligibly +negotiability/MS +negotiable/A +negotiant/M +negotiate/ASDXGN +negotiation/MA +negotiator/MS +negritude/MS +negroid +neigh/MDG +neighbor/SMRDYZGJ +neighbored/U +neighborer/M +neighborhood/SM +neighborliness/UM +neighborlinesses +neighborly/UP +neighs +neither +nelson/MS +nematic +nematode/SM +nemeses +nemesis +neoclassic/M +neoclassical +neoclassicism/MS +neocolonialism/MS +neocortex/M +neodymium/MS +neolithic +neologism/SM +neomycin/M +neon/DMS +neonatal/Y +neonate/MS +neophyte/MS +neoplasm/SM +neoplastic +neoprene/SM +nepenthe/MS +nephew/MS +nephrite/SM +nephritic +nephritides +nephritis/M +nepotism/MS +nepotist/S +neptunium/MS +nerd/S +nerdy/RT +nerve's +nerve/UGSD +nerveless/YP +nervelessness/SM +nerviness/SM +nerving/M +nervous/PY +nervousness/SM +nervy/TPR +nest/RDGSBM +nester/M +nestle/RSDG +nestler/M +nestling/M +net/SM +netball/M +nether +nethermost +netherworld/S +nett/JGRDS +netting/M +nettle/MSDG +nettlesome +network/SJMDG +neural/Y +neuralgia/MS +neuralgic +neurasthenia/MS +neurasthenic/S +neuritic/S +neuritides +neuritis/M +neuroanatomy +neurobiology/M +neurological/Y +neurologist/MS +neurology/SM +neuromuscular +neuron/MS +neuronal +neurone/S +neuropathology/M +neurophysiology/M +neuropsychiatric +neuroses +neurosis/M +neurosurgeon/MS +neurosurgery/SM +neurotic/S +neurotically +neurotransmitter/S +neut/ZR +neuter/JZGRD +neutral/PYS +neutralise's +neutralism/MS +neutralist/S +neutrality/MS +neutralization/MS +neutralize/GZSRD +neutralized/U +neutrino/MS +neutron/MS +never +nevermore +nevertheless +nevi +nevus/M +new/SPTGDRY +newbie/S +newborn/S +newcomer/MS +newed/A +newel/MS +newer/A +newfangled +newfound +newfoundland +newish +newline/SM +newlywed/MS +newness/MS +news's +news/A +newsagent/MS +newsboy/SM +newscast/SRMGZ +newscaster/M +newscasting/M +newsdealer/MS +newsed +newses +newsflash/S +newsgirl/S +newsgroup/SM +newsing +newsletter/SM +newsman/M +newsmen +newspaper/SMGD +newspaperman/M +newspapermen +newspaperwoman/M +newspaperwomen +newsprint/MS +newsreader/MS +newsreel/SM +newsroom/S +newsstand/MS +newsweekly/S +newswire +newswoman/M +newswomen +newsworthiness/SM +newsworthy/RPT +newsy/TRS +newt/MS +newton/SM +next +nexus/SM +niacin/SM +nib/SM +nibbed +nibbing +nibble/RSDGZ +nibbler/M +nice/YTPR +niceness/MS +nicety/MS +niche/SDGM +nichrome +nick/GZRDMS +nickel/SGMD +nickelodeon/SM +nicker/GD +nicknack's +nickname/MGDRS +nicknamer/M +nicotine/MS +niece/MS +nifty/TRS +niggard/SGMDY +niggardliness/SM +niggardly/P +nigger/SGDM! +niggle/RSDGZJ +niggler/M +niggling/Y +nigh/RDGT +nighs +night/SMYDZ +nightcap/SM +nightclothes +nightclub/MS +nightclubbed +nightclubbing +nightdress/MS +nightfall/SM +nightgown/MS +nighthawk/MS +nightie/MS +nightingale/SM +nightlife/MS +nightlong +nightmare/MS +nightmarish/Y +nightshade/SM +nightshirt/MS +nightspot/MS +nightstand/SM +nightstick/S +nighttime/S +nightwear/M +nighty's +nihilism/MS +nihilist/MS +nihilistic +nil/MYS +nilled +nilling +nilpotent +nimbi +nimble/TRP +nimbleness/SM +nimbly +nimbus/DM +nincompoop/MS +nine/MS +ninefold +ninepence/M +ninepin/S +ninepins/M +nineteen/SMH +nineteenths +ninetieths +ninety/MHS +ninja/S +ninny/SM +ninth +ninths +niobium/MS +nip/S +nipped +nipper/DMGS +nippiness/S +nipping/Y +nipple/GMSD +nippy/TPR +nirvana/MS +nisei +nit/ZSMR +niter/M +nitpick/DRSJZG +nitrate/MGNXSD +nitration/M +nitric +nitride/MGS +nitriding/M +nitrification/SM +nitrite/MS +nitrocellulose/MS +nitrogen/SM +nitrogenous +nitroglycerin/MS +nitrous +nitwit/MS +nix/GDSR +nixer/M +nm +no/A +nob/MY +nobelium/MS +nobility/MS +noble/TPSR +nobleman/M +noblemen +nobleness/SM +noblesse/M +noblewoman +noblewomen +nobody/MS +nocturnal/SY +nocturne/SM +nod/SM +nodal/Y +nodded +nodding +noddle/MSDG +noddy/M +node/MS +nodular +nodule/SM +noel/S +noes/S +noggin/SM +nohow +noise/GMSD +noiseless/YP +noiselessness/SM +noisemake/ZGR +noisemaker/M +noisily +noisiness/MS +noisome +noisy/TPR +nomad/SM +nomadic +nomenclature/MS +nominal/K +nominalized +nominally +nominals +nominate/CDSAXNG +nomination/MAC +nominative/SY +nominator/CSM +nominee/MS +non +nonabrasive +nonabsorbent/S +nonacademic/S +nonacceptance/MS +nonacid/MS +nonactive +nonadaptive +nonaddictive +nonadhesive +nonadjacent +nonadjustable +nonadministrative +nonage/MS +nonagenarian/MS +nonaggression/SM +nonagricultural +nonalcoholic/S +nonaligned +nonalignment/SM +nonallergic +nonappearance/MS +nonassignable +nonathletic +nonattendance/SM +nonautomotive +nonavailability/SM +nonbasic +nonbeliever/SM +nonbelligerent/S +nonblocking +nonbreakable +nonburnable +nonbusiness +noncaloric +noncancerous +noncarbohydrate/M +nonce/MS +nonchalance/SM +nonchalant/YP +nonchargeable +nonclerical/S +nonclinical +noncollectable +noncom/MS +noncombatant/MS +noncombustible/S +noncommercial/S +noncommissioned +noncommittal/Y +noncommunicable +noncompeting +noncompetitive +noncompliance/MS +noncomplying/S +noncomprehending +nonconducting +nonconductor/MS +nonconforming +nonconformist/SM +nonconformity/SM +nonconsecutive +nonconservative +nonconstructive +noncontagious +noncontiguous +noncontinuous +noncontributing +noncontributory +noncontroversial +nonconvertible +noncooperation/SM +noncorroding/S +noncorrosive +noncredit +noncriminal/S +noncritical +noncrystalline +noncumulative +noncustodial +noncyclic +nondairy +nondecreasing +nondeductible +nondelivery/MS +nondemocratic +nondenominational +nondepartmental +nondepreciating +nondescript/YS +nondestructive/Y +nondetachable +nondeterminacy +nondeterminate/Y +nondeterminism +nondeterministic +nondeterministically +nondisciplinary +nondisclosure/SM +nondiscrimination/SM +nondiscriminatory +nondramatic +nondrinker/SM +nondrying +nondurable +none/S +noneconomic +noneducational +noneffective/S +nonelastic +nonelectric/S +nonelectrical +nonemergency +nonempty +nonenforceable +nonentity/MS +nonequivalence/M +nonequivalent/S +nones/M +nonessential/S +nonesuch/SM +nonetheless +nonevent/MS +nonexchangeable +nonexclusive +nonexempt +nonexistence/MS +nonexistent +nonexplosive/S +nonextensible +nonfactual +nonfading +nonfat +nonfatal +nonfattening +nonferrous +nonfiction/SM +nonfictional +nonflammable +nonflowering +nonfluctuating +nonflying +nonfood/M +nonfreezing +nonfunctional +nongovernmental +nongranular +nonhazardous +nonhereditary +nonhuman +nonidentical +noninclusive +nonindependent +nonindustrial +noninfectious +noninflammatory +noninflationary +noninflected +nonintellectual/S +noninteracting +noninterchangeable +noninterference/MS +nonintervention/SM +nonintoxicating +nonintuitive +noninvasive +nonionic +nonirritating +nonjudgmental +nonjudicial +nonlegal +nonlethal +nonlinear/Y +nonlinearity/MS +nonlinguistic +nonliterary +nonliving +nonlocal +nonmagical +nonmagnetic +nonmalignant +nonmember/SM +nonmetal/MS +nonmetallic +nonmigratory +nonmilitant/S +nonmilitary +nonnarcotic/S +nonnative/S +nonnegative +nonnegotiable +nonnuclear +nonnumerical/S +nonobjective +nonobligatory +nonobservance/MS +nonobservant +nonoccupational +nonoccurence +nonofficial +nonogenarian +nonoperational +nonoperative +nonorthogonal +nonorthogonality +nonparallel/S +nonparametric +nonpareil/SM +nonparticipant/SM +nonparticipating +nonpartisan/S +nonpaying +nonpayment/SM +nonperformance/SM +nonperforming +nonperishable/S +nonperson/S +nonperturbing +nonphysical/Y +nonplus/S +nonplussed +nonplussing +nonpoisonous +nonpolitical +nonpolluting +nonporous +nonpracticing +nonprejudicial +nonprescription +nonprocedural/Y +nonproductive +nonprofessional/S +nonprofit/SB +nonprogrammable +nonprogrammer +nonproliferation/SM +nonpublic +nonpunishable +nonracial +nonradioactive +nonrandom +nonreactive +nonreciprocal/S +nonreciprocating +nonrecognition/SM +nonrecoverable +nonrecurring +nonredeemable +nonreducing +nonrefillable +nonrefundable +nonreligious +nonrenewable +nonrepresentational +nonresident/SM +nonresidential +nonresidual +nonresistance/SM +nonresistant/S +nonrespondent/S +nonresponse +nonrestrictive +nonreturnable/S +nonrhythmic +nonrigid +nonsalaried +nonscheduled +nonscientific +nonscoring +nonseasonal +nonsectarian +nonsecular +nonsegregated +nonsense/MS +nonsensical/PY +nonsensicalness/M +nonsensitive +nonsexist +nonsexual +nonsingular +nonskid +nonslip +nonsmoker/SM +nonsmoking +nonsocial +nonspeaking +nonspecialist/MS +nonspecializing +nonspecific +nonspiritual/S +nonstaining +nonstandard +nonstarter/SM +nonstick +nonstop +nonstrategic +nonstriking +nonstructural +nonsuccessive +nonsupervisory +nonsupport/GS +nonsurgical +nonsustaining +nonsympathizer/M +nontarnishable +nontaxable/S +nontechnical/Y +nontenured +nonterminal/MS +nonterminating +nontermination/M +nontheatrical +nonthinking/S +nonthreatening +nontoxic +nontraditional +nontransferable +nontransparent +nontrivial +nontropical +nonuniform +nonunion/S +nonuser/SM +nonvenomous +nonverbal/Y +nonveteran/MS +nonviable +nonviolence/SM +nonviolent/Y +nonvirulent +nonvocal +nonvocational +nonvolatile +nonvolunteer/S +nonvoter/MS +nonvoting +nonwhite/SM +nonworking +nonyielding +nonzero +noodle/GMSD +nook/MS +noon/GDMS +noonday/MS +nooning/M +noontide/MS +noontime/MS +noose/SDGM +nope/S +nor/H +noradrenalin +noradrenaline/M +norm/SMGD +normal/SY +normalcy/MS +normality/SM +normalization's +normalization/A +normalizations +normalize/SRDZGB +normalized/AU +normalizes/AU +normative/YP +normativeness/M +north/MRGZ +northbound +northeast/ZSMR +northeaster/YM +northeastern +northeastward/S +norther/MY +northerly/S +northern/RYZS +northernmost +northing/M +northland +northmen +norths +northward/S +northwest/MRZS +northwester/YM +northwestern +northwestward/S +nos/GDS +nose/M +nosebag/M +nosebleed/SM +nosecone/S +nosed/V +nosedive/DSG +nosegay/MS +nosh/MSDG +nosily +nosiness/MS +nosing/M +nostalgia/SM +nostalgic/S +nostalgically +nostril/SM +nostrum/SM +nosy/SRPMT +not/DRGB +notability/SM +notable/PS +notableness/M +notably +notarial +notarization/S +notarize/DSG +notary/MS +notate/VGNXSD +notation/CMSF +notational/CY +notative/CF +notch/MSDG +note's +note/CSDFG +notebook/MS +noted/YP +notedness/M +notepad/S +notepaper/MS +noteworthiness/SM +noteworthy/P +nothing/PS +nothingness/SM +notice/MSDG +noticeable/U +noticeably +noticeboard/S +noticed/U +notifiable +notification/M +notifier/M +notify/NGXSRDZ +notion/MS +notional/Y +notoriety/S +notorious/YP +notoriousness/M +notwithstanding +nougat/MS +noun/SMK +nourish/DRSGL +nourished/U +nourisher/M +nourishment/SM +nous/M +nouveau +nouvelle +nova/MS +novae +novel/SM +novelette/SM +novelist/SM +novelization/S +novelize/GDS +novella/SM +novelty/MS +novena/SM +novene +novice/MS +novitiate/MS +now/S +nowadays +noway/S +nowhere/S +nowise +noxious/PY +noxiousness/M +nozzle/MS +nroff/M +nth +nu/M +nuance/SDM +nub/MS +nubbin/SM +nubby/RT +nubile +nuclear/K +nuclease/M +nucleate/DSXNG +nucleated/A +nucleation/M +nuclei/M +nucleic +nucleoli +nucleolus/M +nucleon/MS +nucleotide/MS +nucleus/M +nuclide/M +nude/CRS +nudely +nudeness/M +nudest +nudge/GSRD +nudger/M +nudism/MS +nudist/MS +nudity/MS +nugatory +nugget/SM +nuisance/MS +nuke/DSMG +null/DSG +nullification/M +nullifier/M +nullify/RSDXGNZ +nullity/SM +numb/SGZTYRDP +number/RDMGJ +numbered/UA +numberer/M +numberless +numberplate/M +numbers/A +numbing/Y +numbness/MS +numbskull's +numerable/IC +numeracy/SI +numeral/YMS +numerate/SDNGX +numerates/I +numeration/M +numerator/MS +numeric/S +numerical/Y +numerological +numerologist/S +numerology/MS +numerous/YP +numerousness/M +numinous/S +numismatic/S +numismatics/M +numismatist/MS +numskull/SM +nun/MS +nuncio/SM +nunnery/MS +nuptial/S +nurse/SRDJGMZ +nursemaid/MS +nurser/M +nursery/MS +nurseryman/M +nurserymen +nursling/M +nurture/SRDGZM +nurturer/M +nus +nut/MS +nutate/NGSD +nutation/M +nutcrack/RZ +nutcracker/M +nuthatch/SM +nutmeat/SM +nutmeg/MS +nutmegged +nutmegging +nutpick/MS +nutria/SM +nutrient/MS +nutriment/MS +nutrition/SM +nutritional/Y +nutritionist/MS +nutritious/PY +nutritiousness/MS +nutritive/Y +nutshell/MS +nutted +nuttiness/SM +nutting +nutty/TRP +nuzzle/GZRSD +nylon/SM +nymph/M +nymphet/MS +nympholepsy/M +nymphomania/MS +nymphomaniac/S +nymphs +née +o +o'clock +o'er +o's +oaf/MS +oafish/PY +oafishness/S +oak/SMN +oakum/MS +oakwood +oar/GSMD +oarlock/MS +oarsman/M +oarsmen +oarswoman +oarswomen +oases +oasis/M +oat/SMNR +oatcake/MS +oater/M +oath/M +oaths +oatmeal/SM +ob +obbligato/S +obduracy/S +obdurate/PDSYG +obdurateness/S +obedience/EMS +obedient/EY +obeisance/MS +obeisant/Y +obelisk/SM +obese +obesity/MS +obey/EDRGS +obeyer/EM +obfuscate/SRDXGN +obfuscation/M +obfuscatory +obi/MDGS +obit/SMR +obituary/SM +obj +object/SGVMD +objectify/GSDXN +objection/SMB +objectionable/U +objectionableness/M +objectionably +objective/PYS +objectiveness/MS +objectivity/MS +objector/SM +objurgate/GNSDX +objurgation/M +oblate/NYPSX +oblation/M +obligate/NGSDXY +obligation/M +obligational +obligatorily +obligatory +oblige/SRDG +obliged/E +obliger/M +obliges/E +obliging/PY +obligingness/M +oblique/DSYGP +obliqueness/S +obliquity/MS +obliterate/VNGSDX +obliteration/M +obliterative/Y +oblivion/MS +oblivious/YP +obliviousness/MS +oblong/SYP +oblongness/M +obloquies +obloquy/M +obnoxious/YP +obnoxiousness/MS +oboe/SM +oboist/S +obos +obs +obscene/RYT +obscenity/MS +obscurantism/MS +obscurantist/MS +obscuration +obscure/YTPDSRGL +obscureness/M +obscurity/MS +obsequies +obsequious/YP +obsequiousness/S +obsequy +observability/M +observable/SU +observably +observance/MS +observant/U +observantly +observants +observation/MS +observational/Y +observatory/MS +observe/ZGDSRB +observed/U +observer/M +observing/Y +obsess/GVDS +obsession/MS +obsessional +obsessive/PYS +obsessiveness/S +obsidian/SM +obsolesce/GSD +obsolescence/S +obsolescent/Y +obsolete/GPDSY +obsoleteness/M +obstacle/SM +obstetric/S +obstetrical +obstetrician/SM +obstetrics/M +obstinacy/SM +obstinate/PY +obstinateness/M +obstreperous/PY +obstreperousness/SM +obstruct/RDVGS +obstructed/U +obstructer/M +obstruction/SM +obstructionism/SM +obstructionist/MS +obstructive/PSY +obstructiveness/MS +obtain/LSGDRB +obtainable/U +obtainably +obtainment/S +obtrude/DSRG +obtruder/M +obtrusion/S +obtrusive/UPY +obtrusiveness/MSU +obtuse/PRTY +obtuseness/S +obverse/YS +obviate/XGNDS +obvious/YP +obviousness/SM +ocarina/MS +occasion/MDSJG +occasional/Y +occident/M +occidental/SY +occipital/Y +occlude/GSD +occlusion/MS +occlusive/S +occult/SRDYG +occulter/M +occultism/SM +occupancy/SM +occupant/MS +occupation/SAM +occupational/Y +occupied/AU +occupier/M +occupies/A +occupy/RSDZG +occur/AS +occurred/A +occurrence/SM +occurring/A +ocean/MS +oceanfront/MS +oceangoing +oceanic +oceanographer/SM +oceanographic +oceanography/SM +oceanology/MS +oceanside +ocelot/SM +ocher/DMGS +octagon/SM +octagonal/Y +octahedral +octahedron/M +octal/S +octane/MS +octant/M +octave/MS +octavo/MS +octennial +octet/SM +octile +octillion/M +octogenarian/MS +octopi +octopus/SM +octoroon/M +ocular/S +oculist/SM +odalisque/SM +odd/TRYSPL +oddball/SM +oddity/MS +oddment/MS +oddness/MS +ode/MDRS +odious/PY +odiousness/MS +odium/MS +odometer/SM +odor/DMS +odoriferous +odorless +odorous/YP +odyssey/S +oedipal +oenology/MS +oenophile/S +oesophagi +oeuvre/SM +of/K +off/SZGDRJ +offal/MS +offbeat/MS +offcuts +offend/SZGDR +offender/M +offense/MSV +offensive/YSP +offensively/I +offensiveness/MSI +offer/RDJGZ +offerer/M +offering/M +offertory/SM +offhand/D +offhanded/YP +offhandedness/S +office/SRMZ +officeholder/SM +officemate/S +officer/GMD +officership/S +official/PSYM +officialdom/SM +officialism/SM +officially/U +officiant/SM +officiate/XSDNG +officiation/M +officiator/MS +officio +officious/YP +officiousness/MS +offing/M +offish +offload/GDS +offprint/GSDM +offramp +offset/SM +offsetting +offshoot/MS +offshore +offside/RS +offspring/M +offstage/S +offtrack +oft/NRT +often/RT +oftentimes +ofttimes +ogive/M +ogle/ZGDSR +ogre/MS +ogreish +ogress/S +oh +ohm/SM +ohmic +ohmmeter/MS +oho/S +ohs +oil/MDRSZG +oilcloth/M +oilcloths +oiler/M +oilfield/MS +oiliness/SM +oilman/M +oilmen +oilseed/SM +oilskin/MS +oily/TPR +oink/GDS +ointment/SM +okapi/SM +okay/M +okra/MS +old/XTNRPS +olden/DG +oldie/MS +oldish +oldness/S +oldster/SM +oleaginous +oleander/SM +olefin/M +oleo/S +oleomargarine/SM +oles +olfactory +oligarch/M +oligarchic +oligarchical +oligarchs +oligarchy/SM +oligopolistic +oligopoly/MS +olive/MSR +olé +om/XN +ombudsman/M +ombudsmen +omega/MS +omelet/SM +omelette's +omen/DMG +omicron/MS +ominous/YP +ominousness/SM +omission/MS +omit/S +omitted +omitting +omni/M +omnibus/MS +omnipotence/SM +omnipotent/SY +omnipresence/MS +omnipresent/Y +omniscience/SM +omniscient/YS +omnivore/MS +omnivorous/PY +omnivorousness/MS +oms +on/RY +onanism/M +once/SR +oncer/M +oncogene/S +oncologist/S +oncology/SM +oncoming/S +one/NPMSX +oneiric +oneiric's +oneness/MS +oner/M +onerous/YP +onerousness/SM +oneself +onetime +oneupmanship +ongoing/S +onion/GDM +onionskin/MS +onlooker/MS +onlooking +only/TP +onomatopoeia/SM +onomatopoeic +onomatopoetic +onrush/GMS +ons +onset/SM +onsetting +onshore +onside +onslaught/MS +onto +ontogeny/SM +ontological/Y +ontology/SM +onus/SM +onward/S +onyx/MS +oodles +ooh/GD +oohs +oolitic +oops/S +ooze/GDS +oozy/RT +op/XGDN +opacity/SM +opal/SM +opalescence/S +opalescent/Y +opaque/GTPYRSD +opaqueness/SM +opcode/MS +ope/S +open/YRDJGZTP +opencast +opened/AU +opener/M +openhanded/P +openhandedness/SM +openhearted +opening/M +openness/S +opens/A +openwork/MS +opera/SM +operable/I +operand/SM +operandi +operant/YS +operate/XNGVDS +operatic/S +operatically +operation/M +operational/Y +operationalization/S +operationalize/D +operative/IP +operatively +operativeness/MI +operatives +operator/SM +operetta/MS +ophthalmic/S +ophthalmologist/SM +ophthalmology/MS +opiate/GMSD +opine/XGNSD +opinion/M +opinionated/PY +opinionatedness/M +opioid +opium/MS +opossum/SM +opp +opponent/MS +opportune/IY +opportunism/SM +opportunist/SM +opportunistic +opportunistically +opportunity/MS +oppose/BRSDG +opposed/U +opposer/M +opposite/SXYNP +oppositeness/M +opposition/M +oppositional +oppress/DSGV +oppression/MS +oppressive/YP +oppressiveness/MS +oppressor/MS +opprobrious/Y +opprobrium/SM +ops +opt/DSG +opthalmic +opthalmologic +opthalmology +optic/S +optical/Y +optician/SM +optics/M +optima +optimal/Y +optimality +optimise's +optimism/SM +optimist/SM +optimistic +optimistically +optimization/SM +optimize/DRSZG +optimized/U +optimizer/M +optimizes/U +optimum/SM +option/GDMS +optional/YS +optionality/M +optoelectronic +optometric +optometrist/MS +optometry/SM +opulence/SM +opulent/Y +opus/SM +or/MY +oracle/GMSD +oracular +oral/YS +orange/MS +orangeade/MS +orangery/SM +orangutan/MS +orate/SDGNX +oration/M +orator/MS +oratorical/Y +oratorio/MS +oratory/MS +orb/SMDG +orbicular +orbiculares +orbit/MRDGZS +orbital/MYS +orchard/SM +orchestra/MS +orchestral/Y +orchestrate/GNSDX +orchestrater's +orchestration/M +orchestrator/M +orchid/SM +ordain/SGLDR +ordainer/M +ordainment/MS +ordeal/SM +order's/E +order/AESGD +ordered/U +orderer +ordering/S +orderless +orderliness/SE +orderly/PS +ordinal/S +ordinance/MS +ordinarily +ordinariness/S +ordinary/RSPT +ordinate's +ordinate/I +ordinated +ordinates +ordinating +ordination/SM +ordnance/SM +ordure/MS +ore/NSM +oregano/SM +organ/MS +organdie's +organdy/MS +organelle/MS +organic/S +organically/I +organism/MS +organismic +organist/MS +organizable/UMS +organization/MEAS +organizational/MYS +organize/AGZDRS +organized/UE +organizer/MA +organizes/E +organizing/E +organometallic +organza/SM +orgasm/GSMD +orgasmic +orgiastic +orgy/SM +oriel/MS +orient's +orient/GADES +orientable +oriental/SY +orientate/ESDXGN +orientated/A +orientates/A +orientation/AMES +orienteering/M +orienter +orifice/MS +orig +origami/MS +origin/MS +original/US +originality/SM +originally +originate/VGNXSD +origination/M +originative/Y +originator/SM +oriole/SM +orison/SM +ormolu/SM +ornament/GSDM +ornamental/SY +ornamentation/SM +ornate/YP +ornateness/SM +orneriness/SM +ornery/PRT +ornithological +ornithologist/SM +ornithology/MS +orographic/M +orography/M +orotund +orotundity/MS +orphan/SGDM +orphanage/MS +orphanhood/M +orris/SM +ors +orthodontia/S +orthodontic/S +orthodontics/M +orthodontist/MS +orthodox/YS +orthodoxies +orthodoxly/U +orthodoxy's +orthodoxy/U +orthogonal/Y +orthogonality/M +orthogonalization/M +orthogonalized +orthographic +orthographically +orthography/MS +orthonormal +orthopedic/S +orthopedics/M +orthopedist/SM +orthophosphate/MS +orthorhombic +oscillate/SDXNG +oscillation/M +oscillator/SM +oscillatory +oscilloscope/SM +osculate/XDSNG +osculation/M +osier/MS +osmium/MS +osmoses +osmosis/M +osmotic +osprey/SM +osseous/Y +ossification/M +ossify/NGSDX +ostensible +ostensibly +ostentation/MS +ostentatious/PY +ostentatiousness/M +osteoarthritides +osteoarthritis/M +osteology/M +osteopath/M +osteopathic +osteopaths +osteopathy/MS +osteoporoses +osteoporosis/M +ostracise's +ostracism/MS +ostracize/GSD +ostrich/MS +other/SMP +otherness/M +otherwise +otherworld/Y +otherworldly/P +otiose +otter/DMGS +ottoman/MS +oubliette/SM +ouch/SDG +ought/SGD +oughtn't +ounce/MS +our/S +ourself +ourselves +oust/RDGZS +ouster/M +out/PJZGSDR +outage/MS +outargue/GDS +outback/MRS +outbalance/GDS +outbid/S +outbidding +outboard/S +outboast/GSD +outbound/S +outbreak/SMG +outbroke +outbroken +outbuilding/SM +outburst/MGS +outcast/GSM +outclass/SDG +outcome/SM +outcrop/SM +outcropped +outcropping/S +outcry/MSDG +outdated/P +outdid +outdistance/GSD +outdo/G +outdoes +outdone +outdoor/S +outdoorsy +outdraw/GS +outdrawn +outdrew +outermost +outerwear/M +outface/SDG +outfall/MS +outfield/RMSZ +outfielder/M +outfight/SG +outfit/MS +outfitted +outfitter/MS +outfitting +outflank/SGD +outflow/SMDG +outfought +outfox/GSD +outgeneraled +outgo/GJ +outgoes +outgoing/P +outgrew +outgrip +outgrow/GSH +outgrown +outgrowth/M +outgrowths +outguess/SDG +outhit/S +outhitting +outhouse/SM +outing/M +outlaid +outland/ZR +outlander/M +outlandish/PY +outlandishness/MS +outlast/GSD +outlaw/SDMG +outlawry/M +outlay/GSM +outlet/SM +outliers +outline/SDGM +outlive/GSD +outlook/MDGS +outlying +outmaneuver/GSD +outmatch/SDG +outmigration +outmoded +outness/M +outnumber/GDS +outpaced +outpatient/SM +outperform/DGS +outplacement/S +outplay/GDS +outpoint/GDS +outpost/SM +outpour/MJG +outpouring/M +outproduce/GSD +output/SM +outputted +outputting +outrace/GSD +outrage/GSDM +outrageous/YP +outrageousness/M +outran +outrank/GSD +outreach/SDG +outrider/MS +outrigger/SM +outright/Y +outrun/S +outrunning +outré +outscore/GDS +outsell/GS +outset/MS +outsetting +outshine/SG +outshone +outshout/GDS +outside/ZSR +outsider/PM +outsize/S +outskirt/SM +outsmart/SDG +outsold +outsource/SDJG +outspend/SG +outspent +outspoke +outspoken/YP +outspokenness/SM +outspread/SG +outstanding/Y +outstate/NX +outstation/M +outstay/SDG +outstretch/GSD +outstrip/S +outstripped +outstripping +outtake/S +outvote/GSD +outward/SYP +outwardness/M +outwear/SG +outweigh/GD +outweighs +outwit/S +outwitted +outwitting +outwore +outwork/SMDG +outworn +ouzo/SM +ova/M +oval/MYPS +ovalness/M +ovarian +ovary/SM +ovate/SDGNX +ovation/GMD +oven/MS +ovenbird/SM +over/YGS +overabundance/MS +overabundant +overachieve/SRDGZ +overact/DGVS +overage/S +overaggressive +overall/SM +overallocation +overambitious +overanxious +overarching +overarm/GSD +overate +overattentive +overawe/GDS +overbalance/DSG +overbear/GS +overbearing/YP +overbearingness/M +overbid/S +overbidding +overbite/MS +overblown +overboard +overbold +overbook/SDG +overbore +overborne +overbought +overbuild/GS +overbuilt +overburden/SDG +overburdening/Y +overbuy/GS +overcame +overcapacity/M +overcapitalize/DSG +overcareful +overcast/GS +overcasting/M +overcautious +overcerebral +overcharge/DSG +overcloud/DSG +overcoat/SMG +overcoating/M +overcome/RSG +overcomer/M +overcommitment/S +overcompensate/XGNDS +overcompensation/M +overcomplexity/M +overcomplicated +overconfidence/MS +overconfident/Y +overconscientious +overconsumption/M +overcook/SDG +overcooled +overcorrection +overcritical +overcrowd/DGS +overcurious +overdecorate/SDG +overdependent +overdetermined +overdevelop/SDG +overdid +overdo/G +overdoes +overdone +overdose/DSMG +overdraft/SM +overdraw/GS +overdrawn +overdress/GDS +overdrew +overdrive/GSM +overdriven +overdrove +overdub/S +overdubbed +overdubbing +overdue +overeager/PY +overeagerness/M +overeat/GNRS +overeater/M +overeducated +overemotional +overemphases +overemphasis/M +overemphasize/GZDSR +overenthusiastic +overestimate/DSXGN +overestimation/M +overexcite/DSG +overexercise/SDG +overexert/GDS +overexertion/SM +overexploitation +overexploited +overexpose/GDS +overexposure/SM +overextend/DSG +overextension +overfall/M +overfed +overfeed/GS +overfill/GDS +overfishing +overflew +overflight/SM +overflow/DGS +overflown +overfly/GS +overfond +overfull +overgeneralize/GDS +overgenerous +overgraze/SDG +overgrew +overground +overgrow/GSH +overgrown +overgrowth/M +overgrowths +overhand/DGS +overhang/GS +overhasty +overhaul/GRDJS +overhead/S +overhear/SRG +overheard +overhearer/M +overheat/SGD +overhung +overincredulous +overindulge/SDG +overindulgence/SM +overindulgent +overinflated +overjoy/SGD +overkill/SDMG +overladed +overladen +overlaid +overlain +overland/S +overlap/MS +overlapped +overlapping +overlarge +overlay/GS +overleaf +overlie +overload/SDG +overlong +overlook/DSG +overlord/DMSG +overloud +overly/GRS +overmanning +overmaster/GSD +overmatching +overmodest +overmuch/S +overnice +overnight/SDRGZ +overoptimism/SM +overoptimistic +overpaid +overparticular +overpass/GMSD +overpay/LSG +overpayment/M +overplay/SGD +overpopulate/DSNGX +overpopulation/M +overpopulous +overpower/GSD +overpowering/Y +overpraise/DSG +overprecise +overpressure +overprice/SDG +overprint/DGS +overproduce/SDG +overproduction/S +overprotect/GVDS +overprotection/M +overqualified +overran +overrate/DSG +overreach/DSRG +overreact/SGD +overreaction/SM +overred +overrefined +overrepresented +overridden +override/RSG +overrider/M +overripe +overrode +overrule/GDS +overrun/S +overrunning +oversample/DG +oversaturate +oversaw +oversea/S +oversee/ZRS +overseeing +overseen +overseer/M +oversell/SG +oversensitive/P +oversensitiveness/S +oversensitivity +oversexed +overshadow/GSD +overshoe/SM +overshoot/SG +overshot/S +oversight/SM +oversimple +oversimplification/M +oversimplify/GXNDS +oversize/GS +oversleep/GS +overslept +oversoft/P +oversoftness/M +oversold +overspecialization/MS +overspecialize/GSD +overspend/SG +overspent +overspill/DMSG +overspread/SG +overstaffed +overstate/SDLG +overstatement/SM +overstay/GSD +overstep/S +overstepped +overstepping +overstimulate/DSG +overstock/SGD +overstraining +overstressed +overstretch/D +overstrict +overstrike/GS +overstrung +overstuffed +oversubscribe/SDG +oversubtle +oversupply/MDSG +oversuspicious +overt/PY +overtake/RSZG +overtaken +overtax/DSG +overthrew +overthrow/GS +overthrown +overtightened +overtime/MGDS +overtire/DSG +overtone/MS +overtook +overture/DSMG +overturn/SDG +overuse/DSG +overvalue/GSD +overview/MS +overweening +overweight/GSD +overwhelm/GDS +overwhelming/Y +overwinter/SDG +overwork/GSD +overwrap +overwrite/SG +overwritten +overwrote +overwrought +overzealous/P +overzealousness/M +oviduct/SM +oviform +oviparous +ovoid/S +ovular +ovulate/GNXDS +ovulatory +ovule/MS +ovum/MS +ow/DYG +owe/S +owl/GSMDR +owlet/SM +owlish/PY +owlishness/M +own/EGDS +owned/U +owner/SM +ownership/MS +ox/MNS +oxalate/M +oxalic +oxaloacetic +oxblood/S +oxbow/SM +oxcart/MS +oxen/M +oxford/MS +oxidant/SM +oxidate/NVX +oxidation/M +oxidative/Y +oxide/SM +oxidization/MS +oxidize/JDRSGZ +oxidized/U +oxidizer/M +oxidizes/A +oxtail/M +oxyacetylene/MS +oxygen/MS +oxygenate/XSDMGN +oxygenation/M +oxyhydroxides +oxymora +oxymoron/M +oyster/GSDM +oystering/M +oz +ozone/SM +p's/A +p/XTGJ +pH/M +pa/MH +pablum/S +pabulum/SM +pace/DRSMZG +pacemaker/SM +pacer/M +pacesetter/MS +pacesetting +pachyderm/MS +pachysandra/MS +pacific +pacifically +pacification/M +pacifier/M +pacifism/MS +pacifist/MS +pacifistic +pacify/NRSDGXZ +pack/GZSJDRMB +package's +package/ARSDG +packaged/U +packager/S +packages/U +packaging/SM +packed/AU +packer/MUS +packet/MSDG +packhorse/M +packing/M +packinghouse/S +packs/UA +packsaddle/SM +pact/SM +pad/MS +padded/U +padding/SM +paddle/MZGRSD +paddler/M +paddock/SDMG +paddy/SM +padlock/SGDM +padre/MS +paean/MS +paediatrician/MS +paediatrics/M +paedophilia's +paella/SM +paeony/M +pagan/SM +paganism/MS +page/MZGDRS +pageant/SM +pageantry/SM +pageboy/SM +paged/U +pageful +pager/M +paginate/DSNGX +pagoda/MS +paid/AU +pail/SM +pailful/SM +pain/GSDM +painful/YP +painfuller +painfullest +painfulness/MS +painkiller/MS +painkilling +painless/YP +painlessness/S +painstaking/SY +paint's +paint/ADRZGS +paintbox/M +paintbrush/SM +painted/U +painter/YM +painterly/P +painting/SM +paintwork +pair/JSDMG +paired/UA +pairs/A +pairwise +paisley/MS +pajama/MDS +pal/SJMDRYTG +palace/MS +paladin/MS +palaeolithic +palaeontologists +palaeontology/M +palanquin/MS +palatability/M +palatable/P +palatableness/M +palatal/YS +palatalization/MS +palatalize/SDG +palate/BMS +palatial/Y +palatinate/SM +palatine/S +palaver/GSDM +pale/SPY +paleface/SM +paleness/S +paleographer/SM +paleography/SM +paleolithic +paleontologist/S +paleontology/MS +palette/MS +palfrey/MS +palimony/S +palimpsest/MS +palindrome/MS +palindromic +paling/M +palisade/MGSD +palish +pall/GSMD +palladium/SM +pallbearer/SM +pallet/SMGD +palletized +palliate/SDVNGX +palliation/M +palliative/SY +pallid/PY +pallidness/MS +pallor/MS +palm/GSMDR +palmate +palmer/M +palmetto/MS +palmist/MS +palmistry/MS +palmtop/S +palmy/RT +palomino/MS +palpable +palpably +palpate/SDNGX +palpation/M +palpitate/NGXSD +palpitation/M +palsy/GSDM +paltriness/SM +paltry/TRP +paludal +pampas/M +pamper/RDSG +pamperer/M +pamphlet/SM +pamphleteer/DMSG +pan/SMD +panacea/MS +panache/MS +panama/S +pancake/MGSD +panchromatic +pancreas/MS +pancreatic +panda/SM +pandemic/S +pandemonium/SM +pander/ZGRDS +pane/KMS +panegyric/SM +panel/JSGDM +paneling/M +panelist/MS +panelization +panelized +pang/GDMS +pangolin/M +panhandle/RSDGMZ +panic/SM +panicked +panicking +panicky/RT +panier's +panjandrum/M +panned +pannier/SM +panning +panoply/MSD +panorama/MS +panoramic +panpipes +pansy/SM +pant/GDS +pantaloons +pantheism/MS +pantheist/S +pantheistic +pantheon/MS +panther/SM +pantie/SM +pantiled +pantograph/M +pantomime/SDGM +pantomimic +pantomimist/SM +pantry/SM +pantsuit/SM +pantyhose +pantyliner +pantywaist/SM +pap/SZMNR +papa/MS +papacy/SM +papal/Y +paparazzi +papaw/SM +papaya/MS +paper/GJMRDZ +paperback/GDMS +paperboard/MS +paperboy/SM +paperer/M +papergirl/SM +paperhanger/SM +paperhanging/SM +paperiness/M +paperless +paperweight/MS +paperwork/SM +papery/P +papilla/M +papillae +papillary +papist/MS +papoose/SM +papped +papping +pappy/RST +paprika/MS +papyri +papyrus/M +par/ZGSJBMDR +para/MS +parable/MGSD +parabola/MS +parabolic +paraboloid/MS +paraboloidal/M +paracetamol/M +parachute/RSDMG +parachuter/M +parachutist/MS +parade/RSDMZG +parader/M +paradigm/SM +paradigmatic +paradisaic +paradisaical +paradise/MS +paradox/MS +paradoxic +paradoxical/YP +paradoxicalness/M +paraffin/GSMD +paragon/SGDM +paragraph/MRDG +paragrapher/M +paragraphs +parakeet/MS +paralegal/S +paralinguistic +parallax/SM +parallel/DSG +paralleled/U +parallelepiped/MS +parallelism/SM +parallelization/MS +parallelize/ZGDSR +parallelogram/MS +paralysis/M +paralytic/S +paralytically +paralyze/ZGDRS +paralyzed/Y +paralyzedly/S +paralyzer/M +paralyzing/Y +paralyzingly/S +paramagnet/M +paramagnetic +paramecia +paramecium/M +paramedic/MS +paramedical/S +parameter/SM +parameterization/SM +parameterize/BSDG +parameterized/U +parameterless +parametric +parametrically +parametrization +parametrize/DS +paramilitary/S +paramount/S +paramour/MS +paranoia/SM +paranoiac/S +paranoid/S +paranormal/SY +parapet/SMD +paraphernalia +paraphrase/GMSRD +paraphraser/M +paraplegia/MS +paraplegic/S +paraprofessional/SM +parapsychologist/S +parapsychology/MS +paraquat/S +parasite/SM +parasitic/S +parasitically +parasitism/SM +parasitologist/M +parasitology/M +parasol/SM +parasympathetic/S +parathion/SM +parathyroid/S +paratroop/RSZ +paratrooper/M +paratyphoid/S +parboil/DSG +parcel/SGMD +parceled/U +parceling/M +parch/GSDL +parchment/SM +pardon/ZBGRDS +pardonable/U +pardonableness/M +pardonably/U +pardoner/M +pare/S +paregoric/SM +parent/MDGJS +parentage/MS +parental/Y +parenteral +parentheses +parenthesis/M +parenthesize/GSD +parenthetic +parenthetical/Y +parenthood/MS +pares/S +paresis/M +parfait/SM +pariah/M +pariahs +parietal/S +parimutuel/S +paring/M +parish/MS +parishioner/SM +parity/ESM +park/GJZDRMS +parka/MS +parking/M +parkish +parkland/M +parklike +parkway/MS +parlance/SM +parlay/DGS +parley/MDSG +parliament/MS +parliamentarian/SM +parliamentary/U +parlor/SM +parlous +parmigiana +parochial/Y +parochialism/SM +parochiality +parodied/U +parodist/SM +parody/SDGM +parole/MSDG +parolee/MS +paroxysm/MS +paroxysmal +parquet/SMDG +parquetry/SM +parrakeet's +parred +parricidal +parricide/MS +parring +parrot/GMDS +parrotlike +parry/GSD +pars/JDSRGZ +parse +parsec/SM +parsed/U +parser/M +parsimonious/Y +parsimony/SM +parsley/MS +parsnip/MS +parson/MS +parsonage/MS +part's +part/CDGS +partake/ZGSR +partaken +partaker/M +parter/S +parterre/MS +parthenogeneses +parthenogenesis/M +partial/SY +partiality/MS +participant/MS +participate/NGVDSX +participation/M +participator/S +participatory +participial/Y +participle/MS +particle/MS +particleboard/S +particolored +particular/SY +particularistic +particularity/SM +particularization/MS +particularize/GSD +particulate/S +parting/MS +partisan/SM +partisanship/SM +partition/AMRDGS +partitioned/U +partitioner/M +partitive/S +partizan's +partly +partner/DMGS +partnership/SM +partook +partridge/MS +parturition/SM +partway +party/RSDMG +parvenu/SM +pas/S +pascal/SM +paschal/S +pasha/MS +pass/JGVBZDSR +passably +passage/MGSD +passageway/MS +passband +passbook/MS +passel/MS +passenger/MYS +passer/M +passerby +passersby +passim +passing/Y +passion/SEM +passionate/EYP +passionated +passionateness/EM +passionates +passionating +passioned +passionflower/MS +passioning +passionless +passivated +passive/SYP +passiveness/S +passivity/S +passkey/SM +passmark +passover +passport/SM +password/SDM +passé/M +past's/A +past/PGMDRS +pasta/MS +paste/MS +pasteboard/SM +pasted/UA +pastel/MS +pastern/SM +pasteup +pasteurization/MS +pasteurize/RSDGZ +pasteurized/U +pasteurizer/M +pastiche/MS +pastille/SM +pastime/SM +pastiness/SM +pastor/GSDM +pastoral/SPY +pastoralization/M +pastorate/MS +pastrami/MS +pastry/SM +pasts/A +pasturage/SM +pasture/MGSRD +pasturer/M +pasty/PTRS +pat/MNDRS +patch's +patch/EGRSD +patcher/EM +patchily +patchiness/S +patchwork/RMSZ +patchy/PRT +pate/SM +patella/MS +patellae +paten/M +patent/ZGMRDYSB +patentee/SM +pater/M +paterfamilias/SM +paternal/Y +paternalism/MS +paternalist +paternalistic +paternity/SM +paternoster/SM +path/M +pathetic +pathetically +pathfinder/MS +pathless/P +pathname/SM +pathogen/SM +pathogenesis/M +pathogenic +pathologic +pathological/Y +pathologist/MS +pathology/SM +pathos/SM +paths +pathway/MS +patience/SM +patient's/I +patient/MRYTS +patients/I +patina/SM +patine +patio/MS +patois/M +patresfamilias +patriarch/M +patriarchal +patriarchate/MS +patriarchs +patriarchy/MS +patrician/MS +patricide/MS +patrimonial +patrimony/SM +patriot/SM +patriotic/U +patriotically +patriotism/SM +patristic/S +patrol/MS +patrolled +patrolling +patrolman/M +patrolmen +patrolwoman +patrolwomen +patron/YMS +patronage/MS +patroness/S +patronization +patronize/GZRSDJ +patronized/U +patronizer/M +patronizes/A +patronizing's/U +patronizing/YM +patronymic/S +patronymically +patroon/MS +patsy/SM +patted +patten/MS +patter/RDSGJ +patterer/M +pattern/GSDM +patternless +patting +patty/SM +paucity/SM +paunch/GMSD +paunchiness/M +paunchy/RTP +pauper/SGDM +pauperism/SM +pauperize/SDG +pause/DSG +pave/GDRSJL +paved/UA +pavement/SGDM +paver/M +paves/A +pavilion/SMDG +paving's +paving/A +paw/MDSG +pawl/SM +pawn/GSDRM +pawnbroker/SM +pawnbroking/S +pawner/M +pawnshop/MS +pawpaw's +paxes +pay/AGSLB +payable/S +payback/S +paycheck/SM +payday/MS +payed +payee/SM +payer/SM +payload/SM +paymaster/SM +payment/ASM +payoff/MS +payola/MS +payout/S +payroll/MS +payslip/S +pct +pd +pea/MS +peace/GMDS +peaceable/P +peaceableness/M +peaceably +peaceful/PY +peacefuller +peacefullest +peacefulness/S +peacekeeping/S +peacemaker/MS +peacemaking/MS +peacetime/MS +peach/GSDM +peachy/RT +peacock/SGMD +peafowl/SM +peahen/MS +peak/SGDM +peaked/P +peakiness/M +peaky/P +peal/MDSG +pealed/A +peals/A +peanut/SM +pear/SYM +pearl/SGRDM +pearler/M +pearly/TRS +peartrees +peasant/SM +peasanthood +peasantry/SM +peashooter/MS +peat/SM +peats/A +peaty/TR +pebble/MGSD +pebbling/M +pebbly/TR +pecan/SM +peccadillo/M +peccadilloes +peccary/MS +peck/GZSDRM +pecker/M +pectic +pectin/SM +pectoral/S +peculate/NGDSX +peculator/S +peculiar/SY +peculiarity/MS +pecuniary +pedagogic/S +pedagogical/Y +pedagogics/M +pedagogue/SDGM +pedagogy/MS +pedal/SGRDM +pedant/SM +pedantic +pedantically +pedantry/MS +peddle/ZGRSD +peddler/M +pederast/SM +pederasty/SM +pedestal/GDMS +pedestrian/MS +pedestrianization +pedestrianize/GSD +pediatric/S +pediatrician/SM +pedicab/SM +pedicure/DSMG +pedicurist/SM +pedigree/DSM +pediment/DMS +pedlar's +pedometer/MS +pedophile/S +pedophilia +peduncle/MS +pee/ZDRS +peeing +peek/GSD +peekaboo/SM +peel/SJGZDR +peeler/M +peeling/M +peen/GSDM +peep/SGZDR +peeper/M +peephole/SM +peepshow/MS +peepy +peer/DMG +peerage/MS +peeress/MS +peerless/PY +peerlessness/M +peeve/GZMDS +peevers/M +peevish/YP +peevishness/SM +peewee/S +peg/MS +pegboard/SM +pegged +pegging +peignoir/SM +pejoration/SM +pejorative/SY +peke/MS +pekingese +pekoe/SM +pelagic +pelf/SM +pelican/SM +pellagra/SM +pellet/SGMD +pellucid +pelt/GSDR +pelter/M +pelvic/S +pelvis/SM +pemmican/SM +pen/M +penal/Y +penalization/SM +penalize/SDG +penalized/U +penalty/MS +penance/SDMG +pence/M +penchant/MS +pencil/SGJMD +pend/DCGS +pendant/SM +pendent/CS +pendulous +pendulum/MS +penetrability/SM +penetrable +penetrate/SDVGNX +penetrating/Y +penetration/M +penetrative/PY +penetrativeness/M +penetrator/MS +penguin/MS +penicillin/SM +penile +peninsula/SM +peninsular +penis/MS +penitence/MS +penitent/SY +penitential/YS +penitentiary/MS +penknife/M +penknives +penlight/MS +penman/M +penmanship/MS +penmen +pennant/SM +penned +penniless +penning +pennis +pennon/SM +penny/SM +pennyweight/SM +pennyworth/M +penologist/MS +penology/MS +pens/V +pension/ZGMRDBS +pensioner/M +pensive/PY +pensiveness/S +pent/AS +pentacle/MS +pentagon/SM +pentagonal/SY +pentagram/MS +pentameter/SM +pentathlete/S +pentathlon/MS +pentatonic +pentecostal +penthouse/SDGM +penuche/SM +penultimate/SY +penumbra/MS +penumbrae +penurious/YP +penuriousness/MS +penury/SM +peon/MS +peonage/MS +peony/SM +people/SDMG +pep/SM +pepped +pepper/SGRDM +peppercorn/MS +pepperer/M +peppergrass/M +peppermint/MS +pepperoni/S +peppery +peppiness/SM +pepping +peppy/PRT +pepsin/SM +peptic/S +peptidase/SM +peptide/SM +peptizing +per/K +peradventure/S +perambulate/DSNGX +perambulation/M +perambulator/MS +percale/MS +perceivably +perceive/DRSZGB +perceived/U +perceiver/M +percent/MS +percentage/MS +percentile/SM +percept/VMS +perceptible +perceptibly +perception/MS +perceptional +perceptive/YP +perceptiveness/MS +perceptual/Y +perch/GSDM +perchance +perchlorate/M +perchlorination +percipience/MS +percipient/S +percolate/NGSDX +percolation/M +percolator/MS +percuss/DSGV +percussion/SAM +percussionist/MS +percussive/PY +percussiveness/M +percutaneous/Y +perdition/MS +perdurable +peregrinate/XSDNG +peregrination/M +peregrine/S +peremptorily +peremptory/P +perennial/SY +perestroika/S +perfect/DRYSTGVP +perfecta/S +perfecter/M +perfectibility/MS +perfectible +perfection/MS +perfectionism/MS +perfectionist/MS +perfective/PY +perfectiveness/M +perfectness/MS +perfidious/YP +perfidiousness/M +perfidy/MS +perforate/XSDGN +perforated/U +perforation/M +perforce +perform/SDRZGB +performance/MS +performed/U +performer/M +perfume/ZMGSRD +perfumer/M +perfumery/SM +perfunctorily +perfunctoriness/M +perfunctory/P +perfused +perfusion/M +pergola/SM +perhaps/S +pericardia +pericardium/M +perigee/SM +perihelia +perihelion/M +peril/GSDM +perilous/PY +perilousness/M +perimeter/MS +perinatal +perinea +perineum/M +period/MS +periodic +periodical/YMS +periodicity/MS +periodontal/Y +periodontics/M +periodontist/S +peripatetic/S +peripheral/SY +periphery/SM +periphrases +periphrasis/M +periphrastic +periscope/SDMG +perish/BZGSRD +perishable/SM +perishing/Y +peristalses +peristalsis/M +peristaltic +peristyle/MS +peritoneal +peritoneum/SM +peritonitis/MS +periwig/MS +periwigged +periwigging +periwinkle/SM +perjure/SRDZG +perjurer/M +perjury/MS +perk/GDS +perkily +perkiness/S +perky/TRP +perm/MDGS +permafrost/MS +permalloy/M +permanence/SM +permanency/MS +permanent/YSP +permanentness/M +permeability/SM +permeable/P +permeableness/M +permeate/NGVDSX +permissibility/M +permissible/P +permissibleness/M +permissibly +permission/SM +permissive/YP +permissiveness/MS +permit/SM +permitted +permitting +permutation/MS +permute/SDG +pernicious/PY +perniciousness/MS +peroration/SM +peroxidase/M +peroxide/MGDS +perpend/DG +perpendicular/SY +perpendicularity/SM +perpetrate/NGXSD +perpetration/M +perpetrator/SM +perpetual/SY +perpetuate/NGSDX +perpetuation/M +perpetuity/MS +perplex/DSG +perplexed/Y +perplexity/MS +perquisite/SM +persecute/XVNGSD +persecution/M +persecutor/MS +persecutory +perseverance/MS +persevere/GSD +persevering/Y +persiflage/MS +persimmon/SM +persist/DRSG +persistence/SM +persistent/Y +persnickety +person's/U +person/BMS +persona/M +personable/P +personableness/M +personae +personage/SM +personal/YS +personality/SM +personalization/CMS +personalize/CSDG +personalized/U +personalty/MS +personification/M +personifier/M +personify/XNGDRS +personnel/SM +persons/U +perspective/YMS +perspex +perspicacious/PY +perspicaciousness/M +perspicacity/S +perspicuity/SM +perspicuous/YP +perspicuousness/M +perspiration/MS +perspire/DSG +persuade/ZGDRSB +persuaded/U +persuader/M +persuasion/SM +persuasive/U +persuasively +persuasiveness/MS +pert/YRTSP +pertain/GSD +pertinacious/YP +pertinaciousness/M +pertinacity/MS +pertinence/S +pertinent/YS +pertness/MS +perturb/GDS +perturbation/MS +perturbed/U +pertussis/SM +peruke/SM +perusal/SM +peruse/RSDZG +peruser/M +pervade/SDG +pervasion/M +pervasive/PY +pervasiveness/MS +perverse/PXYNV +perverseness/SM +perversion/M +perversity/MS +pervert/DRSG +perverted/YP +perverter/M +perviousness +peseta/SM +peskily +peskiness/S +pesky/RTP +peso/MS +pessimal/Y +pessimism/SM +pessimist/SM +pessimistic +pessimistically +pest/RZSM +pester/DG +pesticide/MS +pestiferous +pestilence/SM +pestilent/Y +pestilential/Y +pestle/SDMG +pesto/S +pet/SMRZ +petal/SDM +petard/MS +petcock/SM +peter/GD +pethidine/M +petiole/SM +petite/XNPS +petiteness/M +petition's/A +petition/GZMRD +petitioner/M +petitions/A +petits +petrel/SM +petri +petrifaction/SM +petrify/NDSG +petrochemical/SM +petrodollar/MS +petroglyph/M +petrol/MS +petrolatum/MS +petroleum/MS +petrolled +petrolling +petrologist/MS +petrology/MS +petted +petter/MS +petticoat/SMD +pettifog/S +pettifogged +pettifogger/SM +pettifogging +pettily +pettiness/S +petting +pettis +pettish/YP +pettishness/M +petty/PRST +petulance/MS +petulant/Y +petunia/SM +pew/SM +pewee/MS +pewit/MS +pewter/SRM +peyote/SM +pf +pfennig/SM +pg +phaeton/MS +phage/M +phagocyte/SM +phalanger/MS +phalanges +phalanx/SM +phalli +phallic +phallus/M +phantasm/SM +phantasmagoria/SM +phantasmal +phantasy's +phantom/MS +pharaoh +pharaohs +pharisaic +pharisee/S +pharmaceutic/S +pharmaceutical/SY +pharmaceutics/M +pharmacist/SM +pharmacological/Y +pharmacologist/SM +pharmacology/SM +pharmacopoeia/SM +pharmacy/SM +pharyngeal/S +pharynges +pharyngitides +pharyngitis/M +pharynx/M +phase/DSRGZM +phaseout/S +pheasant/SM +phenacetin/MS +phenobarbital/SM +phenol/MS +phenolic +phenolphthalein/M +phenomena/SM +phenomenal/Y +phenomenological/Y +phenomenology/MS +phenomenon/SM +phenotype/MS +phenyl/M +phenylalanine/M +pheromone/MS +phew/S +phi/SM +phial/MS +phialled +phialling +philander/SRDGZ +philanderer/M +philanthropic +philanthropically +philanthropist/MS +philanthropy/SM +philatelic +philatelist/MS +philately/SM +philharmonic/S +philippic/SM +philistine/S +philistinism/S +philodendron/MS +philological/Y +philologist/MS +philology/MS +philosopher/MS +philosophic +philosophical/Y +philosophize/ZDRSG +philosophized/U +philosophizer/M +philosophizes/U +philosophy/MS +philter/SGDM +philtre/DSMG +phlebitides +phlebitis/M +phlegm/SM +phlegmatic +phlegmatically +phloem/MS +phlox/M +phobia/SM +phobic/S +phoebe/SM +phoenix/MS +phone/DSGM +phoneme/SM +phonemic/S +phonemically +phonemics/M +phonetic/S +phonetically +phonetician/SM +phonetics/M +phonic/S +phonically +phonics/M +phoniness/MS +phonograph/RM +phonographer/M +phonographic +phonographs +phonologic +phonological/Y +phonologist/MS +phonology/MS +phonon/M +phony/PTRSDG +phooey/S +phosphatase/M +phosphate/MS +phosphide/M +phosphine/MS +phosphor/MS +phosphoresce +phosphorescence/SM +phosphorescent/Y +phosphoric +phosphorous +phosphorus/SM +photo/SGMD +photocell/MS +photochemical/Y +photochemistry/M +photocopier/M +photocopy/MRSDZG +photoelectric +photoelectrically +photoelectronic +photoelectrons +photoengrave/RSDJZG +photoengraver/M +photoengraving/M +photofinishing/MS +photogenic +photogenically +photograph's +photograph/AGD +photographer/SM +photographic +photographically +photographs/A +photography/MS +photojournalism/SM +photojournalist/SM +photoluminescence/M +photolysis/M +photolytic +photometer/SM +photometric +photometrically +photometry/M +photomicrograph/M +photomicrography/M +photomultiplier/M +photon/MS +photorealism +photosensitive +photosphere/M +photostatic +photosyntheses +photosynthesis/M +photosynthesize/DSG +photosynthetic +phototypesetter +phototypesetting/M +phrasal +phrase's +phrase/AGDS +phrasebook +phrasemaking +phraseology/MS +phrasing/SM +phrenological/Y +phrenologist/MS +phrenology/MS +phyla/M +phylactery/MS +phylae +phylogeny/MS +phylum/M +phys +physic/SM +physical/PYS +physicality/M +physician/SM +physicist/MS +physicked +physicking +physiochemical +physiognomy/SM +physiography/MS +physiologic +physiological/Y +physiologist/SM +physiology/MS +physiotherapist/MS +physiotherapy/SM +physique/MSD +phytoplankton/M +pi/ZGDRH +pianism/M +pianissimo/S +pianist/SM +pianistic +piano/SM +pianoforte/MS +pianola +piaster/MS +piazza/SM +pibroch/M +pibrochs +pica/SM +picador/MS +picaresque/S +picayune/S +piccalilli/MS +piccolo/MS +pick/GZSJDR +pickaback's +pickax/GMSD +pickaxe's +picker/MG +pickerel/MS +picket/MSRDZG +picketer/M +pickle/SDMG +pickoff/S +pickpocket/GSM +pickup/SM +picky/RT +picnic/SM +picnicked +picnicker/MS +picnicking +picofarad/MS +picojoule +picoseconds +picot/DMGS +pictograph/M +pictographs +pictorial/PYS +pictorialness/M +picture/MGSD +picturesque/PY +picturesqueness/SM +piddle/GSD +piddly +pidgin/SM +pie/MS +piebald/S +piece/GMDSR +piecemeal +piecer/M +piecewise +piecework/ZSMR +pieceworker/M +piedmont +pieing +pier/M +pierce/RSDZGJ +piercer/M +piercing/Y +piety/SM +piezoelectric +piezoelectricity/M +piffle/MGSD +pig/MLS +pigeon/DMGS +pigeonhole/SDGM +pigged +piggery/M +pigging +piggish/YP +piggishness/SM +piggy/RSMT +piggyback/MSDG +pigheaded/YP +pigheadedness/S +piglet/MS +pigment/MDSG +pigmentation/MS +pigpen/SM +pigroot +pigskin/MS +pigsty/SM +pigswill/M +pigtail/SMD +pike/MZGDRS +piker/M +pikestaff/MS +pilaf/MS +pilaster/SM +pilau's +pilchard/SM +pile/JDSMZG +pileup/MS +pilfer/ZGSRD +pilferage/SM +pilferer/M +pilgrim/MS +pilgrimage/DSGM +piling/M +pill/GSMD +pillage/RSDZG +pillar/DMSG +pillbox/MS +pillion/DMGS +pillory/MSDG +pillow/GDMS +pillowcase/SM +pillowslip/S +pilot/DMGS +pilothouse/SM +piloting/M +pimento/MS +pimiento/SM +pimp/GSMYD +pimpernel/SM +pimple/SDM +pimplike +pimply/TRM +pin's +pin/US +pinafore/MS +pinball/MS +pincer/GSD +pinch/GRSD +pincher/M +pincushion/SM +pine/MNGXDS +pineapple/MS +pined/A +pines/A +pinfeather/SM +ping/GDRM +pinhead/SMD +pinheaded/P +pinhole/SM +pining/A +pinion/DMG +pink/GTYDRMPS +pinkeye/MS +pinkie/SM +pinkish/P +pinkness/S +pinko/MS +pinky's +pinnacle/MGSD +pinnate +pinned/U +pinning/S +pinochle/SM +pinpoint/SDG +pinprick/MDSG +pinsetter/SM +pinstripe/SDM +pint/MRS +pintail/SM +pinto/S +pinup/MS +pinwheel/DMGS +piny/RT +pinyin +pion/M +pioneer/SDMG +pious/YP +piousness/MS +pip/JSZMGDR +pipe/MS +pipeline/DSMG +piper/M +pipet's +pipette/MGSD +pipework +piping/YM +pipit/MS +pipped +pippin/SM +pipping +pipsqueak/SM +piquancy/MS +piquant/PY +piquantness/M +pique/GMDS +piracy/MS +piranha/SM +pirate/MGSD +piratical/Y +pirogi +pirogies +pirouette/MGSD +pis +piscatorial +pismire/SM +piss/DSRG! +pistachio/MS +piste/SM +pistil/MS +pistillate +pistol/SMGD +pistole/M +pistoleers +piston/SM +pit/MS +pita/SM +pitapat/S +pitapatted +pitapatting +pitch/RSDZG +pitchblende/SM +pitcher/M +pitchfork/GDMS +pitching/M +pitchman/M +pitchmen +pitchstone/M +piteous/YP +piteousness/SM +pitfall/SM +pith/MGDS +pithily +pithiness/SM +piths +pithy/RTP +pitiable/P +pitiableness/M +pitiably +pitier/M +pitiful/PY +pitifuller +pitifullest +pitifulness/M +pitiless/PY +pitilessness/SM +pitman/M +piton/SM +pittance/SM +pitted +pitting +pituitary/SM +pity/ZDSRMG +pitying/Y +pivot/DMSG +pivotal/Y +pivoting/M +pix/DSG +pixel/SM +pixie/MS +pixiness +pixmap/SM +pizazz/S +pizza/SM +pizzeria/SM +pizzicati +pizzicato +piñata/S +piñon/S +pj's +pk +pkg +pkt +pkwy +pl +placard/DSMG +placate/NGVXDRS +placatory +place/DSRJLGZM +placeable/A +placebo/SM +placed/EAU +placeholder/S +placekick/DGS +placeless/Y +placement/AMES +placenta/SM +placental/S +placer/EM +places/EA +placid/PY +placidity/SM +placidness/M +placing/AE +placket/SM +plagiarism/MS +plagiarist/MS +plagiarize/GZDSR +plagiary/SM +plague/MGRSD +plagued/U +plaguer/M +plaice/M +plaid/DMSG +plain/SPTGRDY +plainclothes +plainclothesman +plainclothesmen +plainness/MS +plainsman/M +plainsmen +plainsong/SM +plainspoken +plaint/VMS +plaintiff/MS +plaintive/YP +plaintiveness/M +plait/SRDMG +plaiting/M +plan/DRMSGZ +planar +planarity +plane's +plane/SCGD +planeload +planer/M +planet/MS +planetarium/MS +planetary +planetesimal/M +planetoid/SM +plangency/S +plangent +plank/SJMDG +planking/M +plankton/MS +planned/U +planner/SM +planning +planoconcave +planoconvex +plant's +plant/SADG +plantain/MS +plantar +plantation/MS +planter/MS +planting/S +plantlike +plaque/MS +plash/GSDM +plasm/M +plasma/MS +plasmid/S +plaster/MDRSZG +plasterboard/MS +plasterer/M +plastering/M +plasterwork/M +plastic/MYS +plastically +plasticine +plasticity/SM +plasticize/GDS +plat/JDNRSGXZ +plate/SM +plateau/GDMS +plateful/S +platelet/SM +platen/M +plater/M +platform/SGDM +plating/M +platinize/GSD +platinum/MS +platitude/SM +platitudinous/Y +platonic +platoon/MDSG +platted +platter/MS +platting +platy/TR +platypus/MS +platys +plaudit/MS +plausibility/S +plausible/P +plausibly +play/DRSEBG +playability/U +playable/U +playact/SJDG +playacting/M +playback/MS +playbill/SM +playboy/SM +played/A +player's/E +player/SM +playfellow/S +playful/PY +playfulness/MS +playgirl/SM +playgoer/MS +playground/MS +playgroup/S +playhouse/SM +playing/S +playmate/MS +playoff/S +playpen/SM +playroom/SM +plays/A +plaything/MS +playtime/SM +playwright/SM +playwriting/M +plaza/SM +plea/SM +plead/ZGJRDS +pleader/MA +pleading/MY +pleas/RSDJG +pleasant/UYP +pleasanter +pleasantest +pleasantness/SMU +pleasantry/MS +please/Y +pleased/EU +pleaser/M +pleases/E +pleasing/YP +pleasingness/M +pleasurable/P +pleasurableness/M +pleasurably +pleasure's/E +pleasure/MGBDS +pleasureful +pleasures/E +pleat/RDMGS +pleater/M +plebe/MS +plebeian/SY +plebiscite/SM +plectra +plectrum/SM +pledge/RSDMG +pledger/M +plenary/S +plenipotentiary/S +plenitude/MS +plenteous/PY +plenteousness/M +plentiful/YP +plentifulness/M +plenty/SM +plenum/M +pleonasm/MS +plethora/SM +pleura/M +pleurae +pleural +pleurisy/SM +plexus/SM +pliability/MS +pliable/P +pliableness/M +pliancy/MS +pliant/YP +pliantness/M +plication/MA +plier/MA +plight/GMDRS +plimsolls +plink/GRDS +plinker/M +plinth/M +plinths +plod/S +plodded +plodder/SM +plodding/SY +plop/SM +plopped +plopping +plosive +plot/SM +plotted/A +plotter/MDSG +plotting +plover/MS +plow/SGZDRM +plowed/U +plower/M +plowman/M +plowmen +plowshare/MS +ploy's +ploy/SCDG +pluck/SGRD +plucker/M +pluckily +pluckiness/SM +plucky/TPR +plug's +plug/US +pluggable +plugged/UA +plugging/AU +plughole +plum/SMDG +plumage/DSM +plumb/JSZGMRD +plumbago/M +plumbed/U +plumber/M +plumbing/M +plume/SM +plummer +plummest +plummet/DSG +plummy +plump/RDNYSTGP +plumper/M +plumpness/S +plumy/TR +plunder/GDRSZ +plunge/RSDZG +plunger/M +plunk/ZGSRD +plunker/M +pluperfect/S +plural/SY +pluralism/MS +pluralist/S +pluralistic +plurality/SM +pluralization/MS +pluralize/GZRSD +pluralizer/M +plus/S +plush/RSYMTP +plushness/MS +plushy/RPT +plussed +plussing +plutocracy/MS +plutocrat/SM +plutocratic +plutonium/SM +pluvial/S +ply/AZNGRSD +plywood/MS +pm +pneumatic/S +pneumatically +pneumatics/M +pneumonia/MS +poach/ZGSRD +poacher/M +pock/GDMS +pocket/MSRDG +pocketbook/SM +pocketful/SM +pocketing/M +pocketknife/M +pocketknives +pockmark/MDSG +pod/SM +podded +podding +podge/ZR +podiatrist/MS +podiatry/MS +podium/MS +poem/MS +poesy/GSDM +poet/MS +poetaster/MS +poetess/MS +poetic/S +poetical/U +poetically +poeticalness +poetics/M +poetry/SM +pogo +pogrom/GMDS +poi/SM +poignancy/MS +poignant/Y +poinciana/SM +poinsettia/SM +point/RDMZGS +pointblank +pointed/PY +pointedness/M +pointer/M +pointillism/SM +pointillist/SM +pointing/M +pointless/YP +pointlessness/SM +pointy/TR +pois/GDS +poise/M +poison/RDMZGSJ +poisoner/M +poisoning/M +poisonous/PY +poke/DRSZG +poker/M +pokerface/D +poky/SRT +pol/GMDRS +polar/S +polarimeter/SM +polarimetry +polariscope/M +polarity/MS +polarization/CMS +polarize/RSDZG +polarized/UC +polarizes/C +polarizing/C +polarogram/SM +polarograph +polarography/M +pole/MS +polecat/SM +polemic/S +polemical/Y +polemicist/S +polemics/M +poler/M +polestar/S +poleward/S +police/MSDG +policeman/M +policemen/M +policewoman/M +policewomen +policy/SM +policyholder/MS +policymaker/S +policymaking +polio/SM +poliomyelitides +poliomyelitis/M +polis/M +polish/RSDZGJ +polished/U +polisher/M +politburo/S +polite/PRTY +politeness/MS +politesse/SM +politic/S +political/U +politically +politician/MS +politicization/S +politicize/CSDG +politicked +politicking/SM +politico/SM +politics/M +polity/MS +polka/SDMG +poll/MDNRSGX +pollack/SM +polled/U +pollen/GDM +pollinate/XSDGN +pollination/M +pollinator/MS +polliwog/SM +pollock's +pollster/MS +pollutant/MS +pollute/RSDXZVNG +polluted/U +polluter/M +pollution/M +pollywog's +polo/MS +polonaise/MS +polonium/MS +poltergeist/SM +poltroon/MS +polyandrous +polyandry/MS +polyatomic +polybutene/MS +polycarbonate +polychemicals +polychrome +polyclinic/MS +polycrystalline +polyelectrolytes +polyester/SM +polyether/S +polyethylene/SM +polygamist/MS +polygamous/Y +polygamy/MS +polyglot/S +polygon/MS +polygonal/Y +polygraph/MDG +polygraphs +polygynous +polyhedral +polyhedron/MS +polyisobutylene +polyisocyanates +polymath/M +polymaths +polymer/MS +polymerase/S +polymeric +polymerization/SM +polymerize/SDG +polymorph/M +polymorphic +polymorphism/MS +polymyositis +polynomial/YMS +polyp/MS +polyphonic +polyphony/MS +polyphosphate/S +polypropylene/MS +polystyrene/SM +polysyllabic +polysyllable/SM +polytechnic/MS +polytheism/SM +polytheist/SM +polytheistic +polythene/M +polytonal/Y +polytopes +polyunsaturated +polyurethane/SM +polyvinyl/MS +pomade/MGSD +pomander/MS +pomegranate/SM +pommel/GSMD +pomp/SM +pompadour/MDS +pompano/SM +pompom/SM +pompon's +pomposity/MS +pompous/YP +pompousness/S +ponce/M +poncho/MS +pond/SMDRGZ +ponder/ZGRD +ponderer/M +ponderous/PY +ponderousness/MS +pone/SM +pongee/MS +poniard/GSDM +pons/M +pontiff/MS +pontifical/YS +pontificate/XGNDS +pontoon/SMDG +pony/DSMG +ponytail/SM +pooch/GSDM +poodle/MS +poof/MS +pooh/DG +poohs +pool/MDSG +poolroom/MS +poolside +poop/MDSG +poor/TYRP +poorboy +poorhouse/MS +poorness/MS +pop/SM +popcorn/MS +pope/SM +popgun/SM +popinjay/MS +poplar/SM +poplin/MS +popover/SM +poppa/MS +popped +popper/SM +poppet/M +popping +poppy/SDM +poppycock/MS +poppyseed +populace/MS +popular/YS +popularism +popularity/UMS +popularization/SM +popularize/A +popularized +popularizer/MS +popularizes/U +popularizing +populate/CXNGDS +populated/UA +populates/A +populating/A +population/MC +populism/S +populist/SM +populous/YP +populousness/MS +porcelain/SM +porch/SM +porcine +porcupine/MS +pore/ZGDRS +porgy/SM +poring/Y +pork/ZRMS +porker/M +porky/TSR +porn/S +porno/S +pornographer/SM +pornographic +pornographically +pornography/SM +porosity/SM +porous/PY +porousness/MS +porphyritic +porphyry/MS +porpoise/DSGM +porridge/MS +porringer/MS +port/ABSGZMRD +portability/S +portable/U +portables +portably +portage/ASM +portaged +portaging +portal/SM +portamento/M +portcullis/MS +ported/CE +portend/SDG +portent/SM +portentous/PY +portentousness/M +porter's/A +porter/DMG +porterage/M +porterhouse/SM +portfolio/MS +porthole/SM +portico/M +porticoes +porting/E +portion/KGSMD +portière/SM +portliness/SM +portly/PTR +portmanteau/SM +portrait/MS +portraitist/SM +portraiture/MS +portray/GDRS +portrayal/SM +portrayer/M +ports/CE +portulaca/MS +pose/ZGKDRSE +posed/CA +poser/KME +poses/CA +poseur/MS +posh/DSRGT +posing/CA +posit/SCGD +positifs +position's/EC +position/KGASMD +positionable +positional/KY +positions/EC +positive/RSPYT +positiveness/S +positivism/M +positivist/S +positivity +positron/SM +poss/S +posse/M +possess/AGEDS +possessed/PY +possession/AEMS +possessional +possessive/PSMY +possessiveness/MS +possessor/MS +possibility/SM +possible/TRS +possibly +possum/MS +post's +post/ASDRJG +postage/MS +postal/S +postbag/M +postbox/SM +postcard/SM +postcode/SM +postcondition/S +postconsonantal +postdate/DSG +postdoctoral +poster/MS +posterior/SY +posteriori +posterity/SM +postfix/GDS +postgraduate/SM +posthaste/S +posthumous/YP +posthumousness/M +posthypnotic +postilion/MS +postindustrial +posting/M +postlude/MS +postman/M +postmarital +postmark/GSMD +postmaster/SM +postmen +postmeridian +postmistress/MS +postmodern +postmodernist +postmortem/S +postnasal +postnatal +postoperative/Y +postorder +postpaid +postpartum +postpone/GLDRS +postponement/S +postpositions +postprandial +postscript/SM +postsecondary +postulate/XGNSD +postulation/M +postural +posture/MGSRD +posturer/M +postvocalic +postwar +posy/SM +pot/CMS +potability/SM +potable/SP +potableness/M +potage/M +potash/MS +potassium/MS +potato/M +potatoes +potbelly/MSD +potboil/ZR +potboiler/M +potency/MS +potent/YS +potentate/SM +potential/SY +potentiality/MS +potentiating +potentiometer/SM +potful/SM +pothead/MS +pother/GDMS +potherb/MS +potholder/MS +pothole/SDMG +potholing/M +pothook/SM +potion/SM +potlatch/SM +potluck/MS +potpie/SM +potpourri/SM +potsherd/MS +potshot/S +pottage/SM +potted +potter/RDMSG +pottery/MS +potting +potty/SRT +pouch/SDMG +poulterer/MS +poultice/DSMG +poultry/MS +pounce/SDG +pound/KRDGS +poundage/MS +pounder/MS +pour/DSG +pourer's +pout/GZDRS +pouter/M +poverty/MS +pow/RZ +powder/RDGMS +powderpuff +powdery +power/GMD +powerboat/MS +powerful/YP +powerfulness/M +powerhouse/MS +powerless/YP +powerlessness/SM +powwow/GDMS +pox/GMDS +pp +ppm +ppr +pr +practicability/S +practicable/P +practicably +practical/YPS +practicality/SM +practicalness/M +practice/BDRSMG +practiced/U +practicer/M +practicum/SM +practitioner/SM +praetor/MS +praetorian/S +pragmatic/S +pragmatical/Y +pragmatics/M +pragmatism/MS +pragmatist/MS +prairie/MS +praise's +praise/ESDG +praiser/S +praiseworthiness/MS +praiseworthy/P +praising/Y +praline/MS +pram/MS +prance/ZGSRD +prancer/M +prancing/Y +prank/SMDG +prankster/SM +praseodymium/SM +prate/DSRGZ +prater/M +pratfall/MS +prating/Y +prattle/DRSGZ +prattler/M +prattling/Y +prawn/MDSG +praxes +praxis/M +pray/DRGZS +prayer/M +prayerbook +prayerful/YP +prayerfulness/M +preach/DRSGLZJ +preacher/M +preaching/Y +preachment/MS +preachy/RT +preadolescence/S +preallocate/XGNDS +preallocation/M +preallocator/S +preamble/MGDS +preamp +preamplifier/M +prearrange/LSDG +prearrangement/SM +preassign/SDG +preauthorize +prebendary/M +precancel/DGS +precancerous +precarious/PY +precariousness/MS +precaution/SGDM +precautionary +precede/DSG +precedence/SM +precedent/SDM +precedented/U +precept/SMV +preceptive/Y +preceptor/MS +precess/DSG +precession/M +precinct/MS +preciosity/MS +precious/PYS +preciousness/S +precipice/MS +precipitable +precipitant/S +precipitate/YNGVPDSX +precipitateness/M +precipitation/M +precipitous/YP +precipitousness/M +precise/XYTRSPN +preciseness/SM +precision/M +preclude/GDS +preclusion/S +precocious/YP +precociousness/MS +precocity/SM +precode/D +precognition/SM +precognitive +precollege/M +precolonial +precomputed +preconceive/GSD +preconception/SM +precondition/GMDS +preconscious +precook/GDS +precursor/SM +precursory +precut +predate/NGDSX +predation/CMS +predator/SM +predatory +predecease/SDG +predecessor/MS +predeclared +predecline +predefine/GSD +predefinition/SM +predesignate/GDS +predestination/SM +predestine/SDG +predetermination/MS +predetermine/ZGSRD +predeterminer/M +predicable/S +predicament/SM +predicate/VGNXSD +predication/M +predicator +predict/BSDGV +predictability/UMS +predictable/U +predictably/U +predicted/U +prediction/MS +predictive/Y +predictor/MS +predigest/GDS +predilect +predilection/SM +predispose/SDG +predisposition/MS +predoctoral +predominance/SM +predominant/Y +predominate/YSDGN +predomination/M +preemie/MS +preeminence/SM +preeminent/Y +preemployment/M +preempt/GVSD +preemption/SM +preemptive/Y +preemptor/M +preen/SRDG +preener/M +preexist/DSG +preexistence/SM +preexistent +pref/RZ +prefab/MS +prefabbed +prefabbing +prefabricate/XNGDS +prefabrication/M +preface/DRSGM +prefacer/M +prefatory +prefect/MS +prefecture/MS +prefer/BL +preferable/P +preferableness/M +preferably +preference/MS +preferential/Y +preferment/SM +preferred +preferring +prefiguration/M +prefigure/SDG +prefix/MDSG +preflight/SGDM +preform/DSG +pregnancy/SM +pregnant/Y +preheat/GDS +prehensile +prehistoric +prehistorical/Y +prehistory/SM +preindustrial +preinitialize/SDG +preinterview/M +preisolated +prejudge/DRSG +prejudger/M +prejudgment/SM +prejudice/MSDG +prejudiced/U +prejudicial/PY +prekindergarten/MS +prelacy/MS +prelate/SM +preliminarily +preliminary/S +preliterate/S +preloaded +prelude/GMDRS +preluder/M +premarital/Y +premarket +premature/SPY +prematureness/M +prematurity/M +premed/S +premedical +premeditate/XDSGNV +premeditated/Y +premeditation/M +premenstrual +premier/GSDM +premiere/MS +premiership/SM +premise/GMDS +premiss's +premium/MS +premix/GDS +premolar/S +premonition/SM +premonitory +prenatal/Y +prenuptial +preoccupation/MS +preoccupy/DSG +preoperative +preordain/DSLG +prep/SM +prepackage/GSD +prepaid +preparation/SM +preparative/SYM +preparatory +prepare/ZDRSG +prepared/UP +preparedly +preparedness/USM +prepay/GLS +prepayment/SM +prepender/S +prepends +preplanned +preponderance/SM +preponderant/Y +preponderate/DSYGN +preposition/SDMG +prepositional/Y +prepossess/GSD +prepossessing/U +prepossession/MS +preposterous/PY +preposterousness/M +prepped +prepping +preppy/RST +preprepared +preprint/SGDM +preprocessed +preprocessing +preprocessor/S +preproduction +preprogrammed +prepubescence/S +prepubescent/S +prepublication/M +prepuce/SM +prequel/S +preradiation +prerecord/DGS +preregister/DSG +preregistration/MS +prerequisite/SM +prerogative/SDM +pres/S +presage/GMDRS +presager/M +presbyopia/MS +presbyter/MS +presbyterian +presbytery/MS +preschool/RSZ +prescience/SM +prescient/Y +prescribe/RSDG +prescribed/U +prescriber/M +prescript/SVM +prescription/SM +prescriptive/Y +preselect/SGD +presence/SM +present/SLBDRYZGP +presentable/P +presentableness/M +presentably/A +presentation/AMS +presentational/A +presented/A +presenter/A +presentiment/MS +presentment/SM +presents/A +preservation/SM +preservationist/S +preservative/SM +preserve/DRSBZG +preserved/U +preserver/M +preset/S +presetting +preshrank +preshrink/SG +preshrunk +preside/DRSG +presidency/MS +president/SM +presidential/Y +presider/M +presidia +presidium/M +presoaks +presort/GDS +press/ACDSG +pressed/U +presser/MS +pressing/YS +pressingly/C +pressman/M +pressmen +pressure/DSMG +pressurization/MS +pressurize/DSRGZ +pressurized/U +prestidigitate/NX +prestidigitation/M +prestidigitator/M +prestidigitatorial +prestige/MS +prestigious/PY +presto/S +presumably +presume/BGDRS +presumer/M +presuming/Y +presumption/MS +presumptive/Y +presumptuous/YP +presumptuousness/SM +presuppose/GDS +presupposition/S +pretax +preteen/S +pretend/SDRZG +pretended/Y +pretender/M +pretending/U +pretense/MNVSX +pretension/GDM +pretentious/UYP +pretentiousness/S +preterit/SM +preterite's +preternatural/Y +pretest/SDG +pretext/SMDG +pretreated +pretreatment/S +pretrial +prettify/SDG +prettily +prettiness/SM +pretty/TGPDRS +pretzel/SM +prevail/SGD +prevailing/Y +prevalence/MS +prevalent/SY +prevaricate/DSXNG +prevaricator/MS +prevent/BSDRGV +preventable/U +preventably +preventative/S +preventer/M +prevention/MS +preventive/SPY +preventiveness/M +preview/ZGSDRM +previous/Y +prevision/SGMD +prewar +prexes +prey/SMDG +preyer's +priapic +price's +price/AGSD +priced/U +priceless +pricer/MS +pricey +pricier +priciest +prick/RDSYZG +pricker/M +pricking/M +prickle/GMDS +prickliness/S +prickly/RTP +pride/GMDS +prideful/Y +prier/M +priest/SMYDG +priestess/MS +priesthood/SM +priestliness/SM +priestly/PTR +prig/SM +prigged +prigging +priggish/PYM +priggishness/S +prim/SPJGZYDR +primacy/MS +primal +primarily +primary/MS +primate/MS +prime/PYS +primed/U +primely/M +primeness/M +primer/M +primeval/Y +priming/M +primitive/YPS +primitiveness/SM +primitivism/M +primmed +primmer +primmest +primming +primness/MS +primogenitor/MS +primogeniture/MS +primordial/YS +primp/DGS +primrose/MGSD +prince/SMY +princedom/MS +princeliness/SM +princely/PRT +princess/MS +principal/SY +principality/MS +principle/SDMG +principled/U +print/AGDRS +printable/U +printably +printed/U +printer/AM +printers +printing/SM +printmake/ZGR +printmaker/M +printmaking/M +printout/S +prior/YS +prioress/MS +priori +prioritize/DSRGZJ +priority/MS +priory/SM +prise/GMAS +prised +prism/MS +prismatic +prison/DRMSGZ +prisoner/M +prissily +prissiness/SM +prissy/RSPT +pristine/Y +prithee/S +privacy/MS +private/NVYTRSXP +privateer/SMDG +privateness/M +privation/MCS +privative/Y +privatization/S +privatize/GSD +privet/SM +privilege/SDMG +privileged/U +privily +privy/SRMT +prize/DSRGZM +prized/A +prizefight/SRMGJZ +prizefighter/M +prizefighting/M +prizewinner/S +prizewinning +pro/MS +proactive +prob/RBJ +probabilist +probabilistic +probabilistically +probability/SM +probable/S +probably +probate/NVMX +probated/A +probates/A +probating/A +probation's/A +probation/MRZ +probational +probationary/S +probationer/M +probative/A +prober/M +probity/SM +problem/SM +problematic/S +problematical/UY +proboscis/MS +procaine/MS +procedural/SY +procedure/MS +proceed/JRDSG +proceeder/M +proceeding/M +process/BSDMG +processed/UA +processes/A +procession/GD +processional/YS +processor/MS +proclamation/MS +proclivity/MS +proconsular +procrastinate/XNGDS +procrastination/M +procrastinator/MS +procreational +procreatory +procrustean +proctor/GSDM +proctorial +procurable/U +procure/L +procurement/MS +prod/S +prodded +prodding +prodigal/SY +prodigality/S +prodigious/PY +prodigiousness/M +prodigy/MS +produce/AZGDRS +producer/AM +producible/A +product/V +production/ASM +productive/PY +productively/UA +productiveness/MS +productivities +productivity's +productivity/A +productize/GZRSD +prof/S +profanation/S +profane/YPDRSG +profaneness/MS +profanity/MS +professed/Y +profession/SM +professional/USY +professionalism/SM +professionalize/GSD +professor/SM +professorial/Y +professorship/SM +proffer/GSD +proficiency/SM +proficient/YS +profit/GZDRB +profitability/MS +profitable/UP +profitableness/MU +profitably/U +profiteer/GSMD +profiterole/MS +profitless +profligacy/S +profligate/YS +proforma/S +profound/PTYR +profoundity +profoundness/SM +profundity/MS +profuse/YP +profuseness/MS +progenitor/SM +progeny/M +progesterone/SM +prognathous +prognoses +prognosis/M +prognostic/S +prognosticate/NGVXDS +prognostication/M +prognosticator/S +program/CSA +programed +programing +programmability +programmable/S +programmed/CA +programmer/ASM +programming/CA +programmings +progress/MSDVG +progression/SM +progressive/SPY +progressiveness/SM +progressivism +prohibit/VGSRD +prohibiter/M +prohibition/MS +prohibitionist/MS +prohibitive/PY +prohibitiveness/M +prohibitory +project/MDVGS +projected/AU +projectile/MS +projection/MS +projectionist/MS +projective/Y +projector/SM +prolegomena +proletarian/S +proletarianization/M +proletarianized +proletariat/SM +proliferate/GNVDSX +proliferation/M +prolific/P +prolifically +prolix/Y +prolixity/MS +prologize +prologue/MGSD +prologuize +prolong/G +prolongate/NGSDX +prolongation/M +prolonger/M +promenade/GZMSRD +promenader/M +promethium/SM +prominence/MS +prominent/Y +promiscuity/MS +promiscuous/PY +promiscuousness/M +promise/GD +promising/UY +promissory +promontory/MS +promote/GVZBDR +promoter/M +promotive/P +promotiveness/M +prompt/SGJTZPYDR +prompted/U +prompter/M +promptitude/SM +promptness/MS +promulgate/NGSDX +promulgation/M +promulgator/MS +pron +prone/PY +proneness/MS +prong/SGMD +pronghorn/SM +pronominalization +pronominalize +pronounce/GLSRD +pronounceable/U +pronounced/U +pronouncedly +pronouncement/SM +pronouncer/M +pronto +pronunciation/SM +proof/SEAM +proofed/A +proofer +proofing/M +proofread/GZSR +proofreader/M +prop/SZ +propaganda/SM +propagandist/SM +propagandistic +propagandize/DSG +propagate/SDVNGX +propagated/U +propagation/M +propagator/MS +propel/S +propellant/MS +propelled +propeller/MS +propelling +propensity/MS +proper/PYRT +properness/M +propertied/U +property/SDM +prophecy/SM +prophesier/M +prophesy/GRSDZ +prophet/SM +prophetess/S +prophetic +prophetical/Y +prophylactic/S +prophylaxes +prophylaxis/M +propinquity/MS +propionate/M +propitiate/GNXSD +propitiatory +propitious/YP +propitiousness/M +proponent/MS +proportion/ESGDM +proportional/SY +proportionality/M +proportionate/YGESD +proportioner/M +proportionment/M +proposal/SM +propped +propping +proprietary/S +proprietor/SM +proprietorial +proprietorship/SM +proprietress/MS +propriety/MS +proprioception +proprioceptive +propulsion/MS +propulsive +propylene/M +prorogation/SM +prorogue +pros/DSRG +prosaic +prosaically +proscenium/MS +prosciutti +prosciutto/SM +proscription/SM +proscriptive +prose/M +prosecute/SDBXNG +prosecution/M +prosecutor/MS +proselyte/SDGM +proselytism/MS +proselytize/ZGDSR +proser/M +prosodic/S +prosody/MS +prospect/DMSVG +prospection/SM +prospective/SYP +prospectiveness/M +prospector/MS +prospectus/SM +prosper/GSD +prosperity/MS +prosperous/PY +prosperousness/M +prostate +prostheses +prosthesis/M +prosthetic/S +prosthetics/M +prostitute/DSXNGM +prostitution/M +prostrate/SDXNG +prostration/M +prosy/RT +protactinium/MS +protagonist/SM +protean/S +protease/M +protect/DVGS +protected/UY +protection/MS +protectionism/MS +protectionist/MS +protective/YPS +protectiveness/S +protector/MS +protectorate/SM +protein/MS +proteolysis/M +proteolytic +protest/G +protestant/S +protestantism +protestation/MS +protesting/Y +protocol/DMGS +protoplasm/MS +protoplasmic +prototype/SDGM +prototypic +prototypical/Y +protozoa +protozoan/MS +protozoic +protozoon's +protract/DG +protrude/SDG +protrusile +protrusion/MS +protrusive/PY +protuberance/S +protuberant +protégé/SM +protégées +proud/TRY +prov/DRGZB +provabilities +provability's +provability/U +provable/P +provableness/M +provably +prove/ESDAG +proved/U +proven/U +provenance/SM +provender/SDG +provenience/SM +provenly +prover/M +proverb/DG +proverbial/Y +provide/DRSBGZ +provided/U +providence/SM +provident/Y +providential/Y +provider/M +province/SM +provincial/SY +provincialism/SM +provision/R +provisional/YS +provisioner/M +proviso/MS +provocateur/S +provocative/P +provocativeness/SM +provoke/GZDRS +provoked/U +provoking/Y +provolone/SM +provost/MS +prow/TRMS +prowess/SM +prowl/RDSZG +prowler/M +proximal/Y +proximate/PY +proximateness/M +proximity/MS +proxy/SM +prude/MS +prudence/SM +prudent/Y +prudential/SY +prudery/MS +prudish/YP +prudishness/SM +prune/DSRGZM +pruner/M +prurience/MS +prurient/Y +prussic +pry/DRSGTZ +pryer's +prying/Y +précis/MDG +psalm/SGDM +psalmist/SM +psalter +psaltery/MS +psephologist/M +pseudo/S +pseudonym/SM +pseudonymous +pseudopod +pseudoscience/S +pshaw/SDG +psi/S +psittacoses +psittacosis/M +psoriases +psoriasis/M +psst/S +psych/SDG +psyche/M +psychedelic/S +psychedelically +psychiatric +psychiatrist/SM +psychiatry/MS +psychic/MS +psychical/Y +psycho/SM +psychoacoustic/S +psychoacoustics/M +psychoactive +psychoanalysis/M +psychoanalyst/S +psychoanalytic +psychoanalytical +psychoanalyze/SDG +psychobabble/S +psychobiology/M +psychocultural +psychodrama/MS +psychogenic +psychokinesis/M +psycholinguistic/S +psycholinguistics/M +psycholinguists +psychological/Y +psychologist/MS +psychology/MS +psychometric/S +psychometrics/M +psychometry/M +psychoneuroses +psychoneurosis/M +psychopath/M +psychopathic/S +psychopathology/M +psychopaths +psychopathy/SM +psychophysic/S +psychophysical/Y +psychophysics/M +psychophysiology/M +psychos/S +psychosis/M +psychosocial/Y +psychosomatic/S +psychosomatics/M +psychotherapeutic/S +psychotherapist/MS +psychotherapy/SM +psychotic/S +psychotically +psychotropic/S +psychs +pt/C +ptarmigan/MS +pterodactyl/SM +ptomaine/MS +pub/MS +pubbed +pubbing +pubertal +puberty/MS +pubes +pubescence/S +pubescent +pubic +pubis/M +public/YSP +publican/AMS +publication/AMS +publicist/SM +publicity/SM +publicize/SDG +publicized/U +publicness/M +publics/A +publish/JDRSBZG +publishable/U +published/UA +publisher/ASM +publishes/A +publishing/M +puce/SM +puck/GZSDRM +pucker/DG +puckish/YP +puckishness/S +pudding/MS +puddle/JMGRSD +puddler/M +puddling/M +puddly +pudenda +pudendum/M +pudginess/SM +pudgy/PRT +pueblo/SM +puerile/Y +puerility/SM +puerperal +puers +puff/SGZDRM +puffball/SM +puffer/M +puffery/M +puffin/SM +puffiness/S +puffy/PRT +pug/MS +pugged +pugging +pugilism/SM +pugilist/S +pugilistic +pugnacious/YP +pugnaciousness/MS +pugnacity/SM +puissant/Y +puke/GDS +pukka +pulchritude/SM +pulchritudinous/M +pule/GDS +pull/DRGZSJ +pullback/S +pullet/SM +pulley/SM +pullout/S +pullover/SM +pulmonary +pulp/MDRGS +pulpiness/S +pulpit/MS +pulpwood/MS +pulpy/PTR +pulsar/MS +pulsate/NGSDX +pulsation/M +pulse's +pulse/ADSG +pulser +pulverable +pulverization/MS +pulverize/GZSRD +pulverized/U +pulverizer/M +pulverizes/UA +puma/SM +pumice/SDMG +pummel/SDG +pump/GZSMDR +pumpernickel/SM +pumping/M +pumpkin/MS +pun/MS +punch/GRSDJBZ +punchbowl/M +punched/U +puncheon/MS +puncher/M +punchline/S +punchy/RT +punctilio/SM +punctilious/PY +punctiliousness/SM +punctual/PY +punctualities +punctuality/UM +punctualness/M +punctuate/SDXNG +punctuation/M +punctuational +puncture/SDMG +pundit/SM +punditry/S +pungency/MS +pungent/Y +puniness/MS +punish/RSDGBL +punished/U +punisher/M +punishment/MS +punitive/YP +punitiveness/M +punk/TRMS +punky/PRS +punned +punning +punster/SM +punt/GZMDRS +punter/M +puny/PTR +pup/MS +pupa/M +pupae +pupal +pupate/NGSD +pupil/SM +pupillage/M +pupped +puppet/SM +puppeteer/SM +puppetry/MS +pupping +puppy/GSDM +puppyish +purblind +purchasable +purchase/GASD +purchaser/MS +purdah/M +purdahs +pure/PYTGDR +purebred/S +puree/DSM +pureeing +pureness/MS +purgation/M +purgative/MS +purgatorial +purgatory/SM +purge/GZDSR +purger/M +purify/GSRDNXZ +purine/SM +purism/MS +purist/MS +puristic +puritan/SM +puritanic +puritanical/Y +puritanism/S +purity/SM +purl/MDGS +purlieu/SM +purloin/DRGS +purloiner/M +purple/MTGRSD +purplish +purport/DRSZG +purported/Y +purpose/SDVGYM +purposeful/YP +purposefulness/S +purposeless/PY +purposelessness/M +purposive/YP +purposiveness/M +purr/DSG +purring/Y +purse/DSRGZM +purser/M +pursuance/MS +pursuant +pursue/ZGRSD +pursuer/M +pursuit/MS +purulence/MS +purulent +purvey/DGS +purveyance/MS +purveyor/MS +purview/SM +pus/SM +push/DSRBGZ +pushbutton/S +pushcart/SM +pushchair/SM +pushdown +pusher/M +pushily +pushiness/MS +pushover/SM +pushy/PRT +pusillanimity/MS +pusillanimous/Y +puss/S +pussy/TRSM +pussycat/S +pussyfoot/DSG +pustular +pustule/MS +put/IS +putative/Y +putout/S +putrefaction/SM +putrefactive +putrefy/DSG +putrescence/MS +putrescent +putrid/YP +putridity/M +putridness/M +putsch/S +putt/SGZMDR +putted/I +puttee/MS +putter/RDMGZ +putting/I +putty/SDMG +puttying/M +puzzle/JRSDZLG +puzzlement/MS +puzzler/M +pvt +pygmy/SM +pyknotic +pylon/SM +pylori +pyloric +pylorus/M +pyorrhea/SM +pyramid/GMDS +pyramidal/Y +pyre/MS +pyridine/M +pyrimidine/SM +pyrite/MS +pyroelectric +pyroelectricity/SM +pyrolysis/M +pyrolyze/RSM +pyromania/MS +pyromaniac/SM +pyrometer/MS +pyrometry/M +pyrophosphate/M +pyrotechnic/S +pyrotechnical +pyrotechnics/M +pyroxene/M +pyroxenite/M +python/MS +pyx/MDSG +pères +q +q's +qr +qt +qty +qua +quack/SDG +quackery/MS +quackish +quad/SM +quadded +quadding +quadrangle/MS +quadrangular/M +quadrant/MS +quadraphonic/S +quadrapole +quadratic/SM +quadratical/Y +quadrature/MS +quadrennial/SY +quadrennium/MS +quadric +quadriceps/SM +quadrilateral/S +quadrille/XMGNSD +quadrillion/MH +quadripartite/NY +quadriplegia/SM +quadriplegic/SM +quadrivia +quadrivium/M +quadruped/MS +quadrupedal +quadruple/GSD +quadruplet/SM +quadruplicate/GDS +quadruply/NX +quadrupole +quadword/MS +quaff/SRDG +quaffer/M +quagmire/DSMG +quahog/MS +quail/GSDM +quaint/PTYR +quaintness/MS +quake/GZDSR +quaky/RT +qualification/ME +qualified/UY +qualifier/SM +qualify/EGXSDN +qualitative/Y +quality/MS +qualm/SM +qualmish +quandary/MS +quangos +quanta/M +quantifiable/U +quantified/U +quantifier/M +quantify/GNSRDZX +quantile/S +quantitative/PY +quantitativeness/M +quantity/MS +quantization/MS +quantize/ZGDRS +quantizer/M +quantum/M +quarantine/DSGM +quark/SM +quarrel/SZDRMG +quarreler/M +quarrellings +quarrelsome/PY +quarrelsomeness/MS +quarrier/M +quarry/RSDGM +quarryman/M +quarrymen +quart/RMSZ +quarter/MDRYG +quarterback/SGMD +quarterdeck/MS +quarterer/M +quarterfinal/MS +quartering/M +quarterly/S +quartermaster/MS +quarterstaff/M +quarterstaves +quartet/SM +quartic/S +quartile/SM +quarto/SM +quartz/SM +quartzite/M +quasar/SM +quash/GSD +quasi +quasilinear +quaternary/S +quaternion/SM +quatrain/SM +quaver/GDS +quavering/Y +quavery +quay/SM +quayside/M +queasily +queasiness/SM +queasy/TRP +queen/SGMDY +queenly/RT +queer/STGRDYP +queerness/S +quell/SRDG +queller/M +quench/GZRSDB +quenchable/U +quenched/U +quencher/M +quenchless +quern/M +querulous/YP +querulousness/S +query/MGRSD +quest/FSIM +quested/A +quester's +quester/AS +questing +question/SMRDGBZJ +questionable/P +questionableness/M +questionably/U +questioned/UA +questioner/M +questioning/UY +questionnaire/MS +quests/A +queue/GZMDSR +queued/C +queuer/M +queues/C +queuing/C +quibble/GZRSD +quibbler/M +quiche/SM +quick/RNYTXPS +quicken/RDG +quickie/MS +quicklime/SM +quickness/MS +quicksand/MS +quicksilver/GDMS +quickstep/SM +quid/SM +quiesce/D +quiescence/MS +quiescent/YP +quiet/UTGPSDRY +quieted/E +quieten/SGD +quieter's +quieter/E +quieting/E +quietly/E +quietness/MS +quiets/E +quietude/IEMS +quietus/MS +quill/GSDM +quilt/SZJGRDM +quilter/M +quilting/M +quince/SM +quincentenary/M +quincy/M +quinine/MS +quinquennial/Y +quinsy/SM +quint/MS +quintessence/SM +quintessential/Y +quintet/SM +quintic +quintile/SM +quintillion/MH +quintillionth/M +quintuple/SDG +quintuplet/MS +quip/MS +quipped +quipper +quipping +quipster/SM +quire/MDSG +quired/AI +quires/AI +quiring/IA +quirk/SGMD +quirkiness/SM +quirky/PTR +quirt/SDMG +quisling/SM +quit/DGS +quitclaim/GDMS +quite/SADG +quittance/SM +quitter/SM +quitting +quiver/GDS +quivering/Y +quivery +quixotic +quixotically +quiz/M +quizzed +quizzer/SM +quizzes +quizzical/Y +quizzing +quo/H +quoin/SGMD +quoit/GSDM +quondam +quonset +quorate/I +quorum/MS +quot/GDRB +quota/MS +quotability/S +quotation/SM +quote/UGSD +quoter/M +quotidian/S +quotient/SM +qwerty +qwertys +r's +r/TGVJ +rabbet/GSMD +rabbi/MS +rabbinate/MS +rabbinic +rabbinical/Y +rabbit/MRDSG +rabbiter/M +rabble/GMRSD +rabbler/M +rabid/YP +rabidness/SM +rabies +rabis +raccoon/SM +race/MZGDRSJ +racecourse/MS +racegoers +racehorse/SM +raceme/MS +racer/M +racetrack/SMR +raceway/SM +racial/Y +racialism/MS +racialist/MS +racily +raciness/MS +racism/S +racist/MS +rack/GDRMS +racket/SMDG +racketeer/MDSJG +rackety +raconteur/SM +racoon's +racquet's +racquetball/S +racy/RTP +rad/S +radar/SM +radarscope/MS +radded +radder +raddest +radding +radial/SY +radian/SM +radiance/SM +radiant/YS +radiate/XSDYVNG +radiation/M +radiative/Y +radiator/MS +radical/SPY +radicalism/MS +radicalization/S +radicalize/GSD +radicalness/M +radices's +radii/M +radio/SMDG +radioactive/Y +radioactivity/MS +radioastronomical +radioastronomy +radiocarbon/MS +radiochemical/Y +radiochemistry/M +radiogalaxy/S +radiogram/SM +radiographer/MS +radiographic +radiography/MS +radioisotope/SM +radiologic +radiological/Y +radiologist/MS +radiology/MS +radioman/M +radiomen +radiometer/SM +radiometric +radiometry/MS +radionics +radionuclide/M +radiopasteurization +radiophone/MS +radiophysics +radioscopy/SM +radiosonde/SM +radiosterilization +radiosterilized +radiotelegraph +radiotelegraphs +radiotelegraphy/MS +radiotelephone/SM +radiotherapist/SM +radiotherapy/SM +radish/MS +radium/MS +radius/M +radix/SM +radon/SM +raffia/SM +raffish/PY +raffishness/SM +raffle/MSDG +raft/GZSMDR +rafter/DM +rag/GSMD +raga/MS +ragamuffin/MS +ragbag/SM +rage/MS +ragged/PRYT +raggedness/SM +raggedy/TR +ragging +raging/Y +raglan/MS +ragout/SMDG +ragtag/MS +ragtime/MS +ragweed/MS +ragwort/M +rah/DG +rahs +raid/MDRSGZ +raider/M +rail's +rail/CDGS +railbird/S +railer/SM +railhead/SM +railing/MS +raillery/MS +railroad/SZRDMGJ +railroader/M +railroading/M +railway/MS +railwaymen +raiment/SM +rain/GSDM +rainbow/MS +raincloud/S +raincoat/SM +raindrop/SM +rainfall/SM +rainforest's +rainless +rainmaker/SM +rainmaking/MS +rainproof/GSD +rainstorm/SM +rainwater/MS +rainy/RT +raise/DSRGZ +raiser/M +raisin/MS +raising/M +raj/M +rajah/M +rajahs +rake/MGDRS +raker/M +rakish/PY +rakishness/MS +rally/GSD +ram/SM +ramble/JRSDGZ +rambler/M +rambling/Y +rambunctious/PY +rambunctiousness/S +ramekin/SM +ramie/MS +ramification/M +ramify/XNGSD +ramjet/SM +rammed +ramming +ramp/GMDS +rampage/SDG +rampancy/S +rampant/Y +rampart/SGMD +ramrod/MS +ramrodded +ramrodding +rams/S +ramshackle +ran/A +ranch/ZRSDMJG +rancher/M +rancho/SM +rancid/P +rancidity/MS +rancidness/SM +rancor/SM +rancorous/Y +rand/MDGS +randiness/S +random/PYS +randomization/SM +randomize/SRDG +randomness/SM +randy/PRST +ranee/SM +rang/GZDR +range/SM +ranged/C +rangeland/S +ranger/M +ranges/C +ranginess/S +ranging/C +rangy/RPT +rani's +rank/GZTYDRMPJS +ranked/U +ranker/M +ranking/M +rankle/SDG +rankness/MS +ransack/GRDS +ransacker/M +ransom/ZGMRDS +ransomer/M +rant/GZDRJS +ranter/M +ranting/Y +rap/MDRSZG +rapacious/YP +rapaciousness/MS +rapacity/MS +rape/SM +rapeseed/M +rapid/YRPST +rapidity/MS +rapidness/S +rapier/SM +rapine/SM +rapist/MS +rapped +rappel/S +rappelled +rappelling +rapper/SM +rapping/M +rapport/SM +rapporteur/SM +rapprochement/SM +rapscallion/MS +rapt/YP +raptness/S +rapture/MGSD +rapturous/YP +rapturousness/M +rare/YTPGDRS +rarebit/MS +rarefaction/MS +rarefy/GSD +rareness/MS +rarity/SM +rascal/SMY +rash/PZTYSR +rasher/M +rashness/S +rasp/SGJMDR +raspberry/SM +rasper/M +rasping/Y +raspy/RT +raster/MS +rat/MDRSJZGB +ratchet/MDSG +rate's +rate/KNGSD +rateable +rated/U +ratepayer/SM +rater/M +rather +rathskeller/SM +ratifier/M +ratify/ZSRDGXN +rating/M +ratio/MS +ratiocinate/VNGSDX +ratiocination/M +ration/DSMG +rational/YPS +rationale/SM +rationalism/SM +rationalist/S +rationalistic +rationality/MS +rationalization/SM +rationalize/ZGSRD +rationalizer/M +rationalness/M +ratlike +ratline/SM +rattail +rattan/MS +ratted +ratter/MS +ratting +rattle/RSDJGZ +rattlebrain/DMS +rattlesnake/MS +rattletrap/MS +rattling/Y +rattly/TR +rattrap/SM +ratty/RT +raucous/YP +raucousness/SM +raunchily +raunchiness/S +raunchy/RTP +ravage/GZRSD +ravager/M +rave/ZGDRSJ +ravel/UGDS +raveling/S +raven/JGMRDS +ravenous/YP +raver/M +ravine/SDGM +ravioli/SM +ravish/LSRDZG +ravisher/M +ravishing/Y +ravishment/SM +raw/PSRYT +rawboned +rawhide/SDMG +rawness/SM +ray/GSMD +rayon/SM +raze/DRSG +razer/M +razor/MDGS +razorback/SM +razorblades +razz/GDS +razzmatazz/S +rcpt +rd +re/YM +reabbreviate +reach/GRB +reachability +reachable/U +reachably +reached/U +reacher/M +reacquisition +reactant/SM +reacted/U +reaction +reactionary/SM +reactivity +read/JGZBR +readability/MS +readable/P +readably +readdress/G +reader/M +readership/MS +readied +readies +readily +readiness/UM +readinesses +reading/M +readopt/G +readout/MS +reads/A +ready/TUPR +readying +real/RSTP +realism's +realism/U +realisms +realist/SM +realistic/U +realistically/U +reality/USM +realizability/MS +realizable/SMP +realizableness/M +realizably/S +realization/MS +realize/JRSDBZG +realized/U +realizer/M +realizes/U +realizing/MY +realm/M +realness/S +realpolitik/SM +realtor's +realty/SM +ream/MDRGZ +reamer/M +reanimate +reap/SGZ +reaper/M +reappraise/G +rear/DRMSG +rearguard/MS +rearmost +rearrange/L +rearward/S +reason/UBDMG +reasonable/UP +reasonableness/SMU +reasonably/U +reasoner/SM +reasoning/MS +reasonless +reasons +reassess/GL +reassuringly/U +reattach/GSL +reawakening/M +rebate/M +rebel/MS +rebeller +rebellion/SM +rebellious/YP +rebelliousness/MS +rebid +rebidding +rebind/G +rebirth +reboil/G +rebook +reboot/ZR +rebound/G +rebroadcast/MG +rebuke/RSDG +rebuking/Y +rebus +rebuttal/SM +rebutting +rec +rec'd +recalcitrance/SM +recalcitrant/S +recalibrate/N +recant/G +recantation/S +recap +recappable +recapping +recast/G +recd +recede +receipt/SGDM +receivable/S +receive/ZGRSDB +received/U +receiver/M +receivership/SM +recency/M +recension/M +recent/YPT +recentness/SM +receptacle/SM +reception/MS +receptionist/MS +receptive/YP +receptiveness/S +receptivity/S +receptor/MS +recess/SDMVG +recessional/S +recessionary +recessive/YPS +recessiveness/M +rechargeable +recheck/G +recherches +recherché +recidivism/MS +recidivist/MS +recipe/MS +recipiency +recipient/MS +reciprocal/SY +reciprocate/NGXVDS +reciprocation/M +reciprocity/MS +recital/MS +recitalist/S +recitative/MS +recite/ZR +reciter/M +recked +recking +reckless/PY +recklessness/S +reckon/SGRDJ +reckoner/M +reckoning/M +reclaim/B +reclamation/SM +recline/RSDZG +recliner/M +recluse/MVNS +reclusion/M +recode/G +recognizability +recognizable/U +recognizably +recognize/BZGSRD +recognized/U +recognizedly/S +recognizer/M +recognizing/UY +recognizingly/S +recoilless +recoinage +recolor/GD +recombinant +recombine +recommended/U +recompense/GDS +recompute/B +reconcile/SRDGB +reconciled/U +reconciler/M +recondite/YP +reconditeness/M +reconfigurability +reconfigure/R +reconnaissance/MS +reconnect/R +reconnoiter/GSD +reconquer/G +reconsecrate +reconstitute +reconstructed/U +reconsult/G +recontact/G +recontaminate/N +recontribute +recook/G +recopy/G +record/ZGJ +recorded/AU +records/A +recourse +recover/B +recoverability +recoverable/U +recovery/MS +recreant/S +recreational +recriminate/GNVXDS +recrimination/M +recriminatory +recross/G +recrudesce/GDS +recrudescence/MS +recrudescent +recruit/ZSGDRML +recruiter/M +recruitment/MS +recrystallize +recta's +rectal/Y +rectangle/SM +rectangular/Y +rectifiable +rectification/M +rectifier/M +rectify/DRSGXZN +rectilinear/Y +rectitude/MS +recto/MS +rector/SM +rectory/MS +rectum/SM +recumbent/Y +recuperate/VGNSDX +recuperation/M +recur +recurrence/MS +recurrent +recurse/NX +recursion/M +recusant/M +recuse +recyclable/S +recycle/BZ +red/PYS +redact/DGS +redaction/SM +redactor/MS +redbird/SM +redbreast/SM +redbrick/M +redbud/M +redcap/MS +redcoat/SM +redcurrant/M +redden/DGS +redder +reddest +redding +reddish/P +redeclaration +redecorate +redeem/BRZ +redeemable/U +redeemed/U +redeemer/M +redemption/RMS +redemptioner/M +redemptive +redeposit/M +redetermination +redhead/DRMS +redial/G +redirect/G +redirection +redlining/S +redneck/SMD +redness/MS +redo/G +redolence/MS +redolent +redouble/S +redoubtably +redound/GDS +redshift/S +redskin/SM +reduce/RSDGZ +reduced/U +reducer/M +reducibility/M +reducible +reducibly +reduct/V +reduction/SM +reductionism/M +reductionist/S +redundancy/SM +redundant/Y +redwood/SM +redye +redyeing +reecho/G +reed/GMDR +reediness/SM +reeding/M +reedy/PTR +reef/GZSDRM +reefer/M +reek/GSR +reeker/M +reel's +reel/USDG +reeler/M +reenforcement +reentrant +reestimate/M +reeve/G +reexamine +ref/ZS +refection/SM +refectory/SM +refer/B +referee/MSD +refereed/U +refereeing +reference's +reference/CGSRD +referenced/U +referencing/U +referendum/MS +referent/SM +referential/YM +referentiality +referral/SM +referred +referrer/S +referring +reffed +reffing +refile +refinance +refine/LZ +refined/U +refinement/MS +refinish/G +refit +reflect/SDGV +reflectance/M +reflected/U +reflection/SM +reflectional +reflective/YP +reflectiveness/M +reflectivity/M +reflector/MS +reflex/YV +reflexion/MS +reflexive/PSY +reflexiveness/M +reflexivity/M +reflooring +refluent +reflux/G +refocus/G +refold/G +reforestation +reforge/G +reform/B +reformatory/SM +reformed/U +reformer/M +reformism/M +reformist/S +refract/DGVS +refractive/PY +refractiveness/M +refractometer/MS +refractoriness/M +refractory/PS +refrain/DGS +refresh/LB +refreshed/U +refreshing/Y +refreshment/MS +refrigerant/MS +refrigerate/XDSGN +refrigerated/U +refrigeration/M +refrigerator/MS +refrozen +refry/GS +refuge/SDGM +refugee/MS +refulgence/SM +refulgent +refund/B +refunder/M +refurbish/L +refurbishment/S +refusal/SM +refuse/R +refuser/M +refutation/MS +refute/GZRSDB +refuter/M +reg +regal/GYRD +regale/L +regalement/S +regalia/M +regard/EGDS +regardless/PY +regather/G +regatta/MS +regency/MS +regeneracy/MS +regenerate/U +regenerately +regenerateness/M +reggae/SM +regicide/SM +regime/MS +regimen/MS +regiment/SDMG +regimental/S +regimentation/MS +region/SM +regional/SY +regionalism/MS +register's +register/UDSG +registrable +registrant/SM +registrar/SM +registration/AM +registrations +registry/MS +regnant +regress/DSGV +regression/MS +regressive/PY +regressiveness/M +regressors +regret/S +regretful/PY +regretfulness/M +regrettable +regrettably +regretted +regretting +reground +regroup/G +regrow/G +regular/YS +regularity/MS +regularization/MS +regularize/SDG +regulate/CSDXNG +regulated/U +regulation/M +regulative +regulator/SM +regulatory +regurgitate/XGNSD +regurgitation/M +rehab/S +rehabbed +rehabbing +rehabilitate/SDXVGN +rehabilitation/M +rehang/G +rehear/GJ +rehears/R +rehearsal/SM +rehearse +rehearsed/U +rehearser/M +reheat/G +reheating/M +rehydrate +reign/MDSG +reimburse/GSDBL +reimbursement/MS +rein/GDM +reindeer/M +reinforce/GSRDL +reinforced/U +reinforcement/MS +reinforcer/M +reinstate/L +reinstatement/MS +reinsurance +reissue +reiterative/SP +reject/RDVGS +rejecter/M +rejecting/Y +rejection/SM +rejector/MS +rejigger +rejoice/RSDJG +rejoicing/Y +rejoinder/SM +rejuvenate/NGSDX +rejuvenatory +rel/V +relapse +relate/XVNGSZ +related/U +relatedly +relatedness/MS +relater/M +relation/M +relational/Y +relationship/MS +relative/SPY +relativeness/M +relativism/M +relativist/MS +relativistic +relativistically +relativity/MS +relator's +relax/GZD +relaxant/SM +relaxation/MS +relaxed/YP +relaxedness/M +relaxing/Y +relay/GDM +relearn/G +releasable/U +release/B +released/U +relent/SDG +relenting/U +relentless/PY +relentlessness/SM +relevance/SM +relevancy/MS +relevant/Y +reliability/UMS +reliable/U +reliables +reliably/U +reliance/MS +reliant/Y +relic/MS +relicense/R +relict's +relict/C +relief/M +relieve/RSDZG +relieved/U +relievedly +reliever/M +religion/SM +religionists +religiosity/M +religious/PY +religiousness/MS +relink/G +relinquish/GSDL +relinquishment/SM +reliquary/MS +relish/GSD +relive/GB +reload/GR +relocate/B +reluctance/MS +reluctant/Y +rely/DG +rem +remade/S +remain/GD +remainder/SGMD +remake/M +remand/DGS +remap +remapping +remark/BG +remarkable/U +remarkableness/S +remarkably +remarked/U +rematch/G +remeasure/D +remediable/P +remediableness/M +remedy/SDMG +remember/GR +remembered/U +rememberer/M +remembrance/MRS +remembrancer/M +reminisce/GSD +reminiscence/SM +reminiscent/Y +remiss/YP +remissness/MS +remit/S +remittance/MS +remitted +remitting/U +remnant/MS +remodel/G +remolding +remonstrant/MS +remonstrate/SDXVNG +remonstration/M +remonstrative/Y +remorse/SM +remorseful/PY +remorsefulness/M +remorseless/YP +remorselessness/MS +remote/RPTY +remoteness/MS +remoulds +removal/MS +remunerate/VNGXSD +remunerated/U +remuneration/M +remunerative/YP +remunerativeness/M +renaissance/S +renal +renaturation +rend +rend/RGZS +render/GJRD +renderer/M +rendering/M +rendezvous/DSMG +rendition/GSDM +renegade/SDMG +renege/GZRSD +reneger/M +renew/BG +renewal/MS +renewer/M +rennet/MS +rennin/SM +renounce/LGRSD +renouncement/MS +renouncer/M +renovate/NGXSD +renovation/M +renovator/SM +renown/SGDM +rent/GZMDRS +rental/SM +rentaller +renter/M +renumber/G +renumeration +renunciate/VNX +renunciation/M +reoccupy/G +reopen/G +reorganized/U +rep/S +repack/G +repair/BZGR +repairable/U +repairer/M +repairman/M +repairmen +repairs/E +repaper +reparable +reparation/SM +repartee/MDS +reparteeing +repartition/Z +repast/G +repatriate/SDXNG +repave +repeal/GR +repealer/M +repeat/RDJBZG +repeatability/M +repeatable/U +repeatably +repeated/Y +repeater/M +repel/S +repelled +repellent/SY +repelling/Y +repent/RDG +repentance/SM +repentant/SY +repertoire/SM +repertory/SM +repetition +repetitious/YP +repetitiousness/S +repetitive/PY +repetitiveness/MS +repine/R +repiner/M +replace/RL +replay/GM +replenish/LRSDG +replenishment/S +replete/SDPXGN +repleteness/MS +repletion/M +replica/SM +replicate/SDVG +replicator/S +replug +reply/X +repopulate +reported/Y +reportorial/Y +repose/M +reposeful +repository/MS +reprehend/GDS +reprehensibility/MS +reprehensible/P +reprehensibleness/M +reprehensibly +reprehension/MS +represent/GB +representable/U +representational/Y +representative/SYMP +representativeness/M +representativity +represented/U +repress/V +repression/SM +repressive/YP +repressiveness/M +reprieve/GDS +reprimand/SGMD +reprint/M +reprisal/MS +reproach/GRSDB +reproacher/M +reproachful/YP +reproachfulness/M +reproaching/Y +reprobate/N +reprocess/G +reproducibility/MS +reproducible/S +reproducibly +reproductive/S +reproof/G +reprove/R +reproving/Y +reptile/SM +reptilian/S +republic/M +republicanism/SM +republish/G +repudiate/XGNSD +repudiation/M +repudiator/S +repugnance/MS +repugnant/Y +repulse/VNX +repulsion/M +repulsive/PY +repulsiveness/MS +reputability/SM +reputably/E +reputation/SM +repute/ESB +reputed/Y +reputing +request/G +requested/U +requiem/SM +require/LR +requirement/MS +requisite/PNXS +requisiteness/M +requisition/GDRM +requisitioner/M +requital/MS +requite/RZ +requited/U +requiter/M +reread/G +rerecord/G +rerouteing +rerunning +res/C +rescale +rescind/SDRG +rescission/SM +rescue/GZRSD +reseal/BG +research/MB +reselect/G +resemblant +resemble/DSG +resend/G +resent/DSLG +resentful/PY +resentfulness/SM +resentment/MS +reserpine/MS +reservation/MS +reserved/UYP +reservedness/UM +reservednesses +reservist/SM +reservoir/MS +reset/RDG +resettle/L +reshipping +reshow/G +reshuffle/M +reside/G +residence/MS +residency/SM +resident/SM +residential/Y +resider/M +residua +residual/YS +residuary +residue/SM +residuum/M +resignation/MS +resigned/YP +resilience/MS +resiliency/S +resilient/Y +resin/D +resinlike +resinous +resiny +resist/RDZVGS +resistance/SM +resistant/U +resistantly +resistants +resisted/U +resistible +resistibly +resisting/U +resistive/PY +resistiveness/M +resistivity/M +resistless +resistor/MS +resize/G +resold +resole/G +resoluble +resolute/PYTRV +resoluteness/MS +resolvability/M +resolvable/U +resolved/U +resolvent +resonance/SM +resonant/YS +resonate/DSG +resonator/MS +resorption/MS +resort/R +resound/G +resourceful/PY +resourcefulness/SM +resp +respect's/E +respect/BSDRMZGV +respectability/SM +respectable/SP +respectably +respected/E +respectful/EY +respectfulness/SM +respecting/E +respective/PY +respectiveness/M +respects/E +respell/G +respiration/MS +respirator/SM +respiratory/M +resplendence/MS +resplendent/Y +respond/SDRZG +respondent/MS +response/RSXMV +responser/M +responsibility/MS +responsible/P +responsibleness/M +responsibly +responsive/YPU +responsiveness/MSU +respray/G +rest's/U +rest/DRSGVM +restart/B +restate/L +restaurant/SM +restaurateur/SM +rested/U +rester/M +restful/YP +restfuller +restfullest +restfulness/MS +restitution/SM +restive/PY +restiveness/SM +restless/YP +restlessness/MS +restorability +restoration/MS +restorative/PYS +restore/Z +restorer/M +restrained/UY +restraint/MS +restrict/DVGS +restricted/YU +restriction/SM +restrictive/U +restrictively +restrictiveness/MS +restrictives +restroom/SM +restructurability +restructure +rests/U +restudy/M +restyle +resubstitute +result/SGMD +resultant/YS +resume/SDBG +resumption/MS +resurface +resurgence/MS +resurgent +resurrect/GSD +resurrection/SM +resurvey/G +resuscitate/XSDVNG +resuscitation/M +resuscitator/MS +retail/Z +retain/LZGSRD +retainer/M +retake +retaliate/VNGXSD +retaliation/M +retaliatory +retard/ZGRDS +retardant/SM +retardation/SM +retarder/M +retch/SDG +retention/SM +retentive/YP +retentiveness/S +retentivity/M +retest/G +rethought +reticence/S +reticent/Y +reticle/SM +reticular +reticulate/GNYXSD +reticulation/M +reticule/MS +reticulum/M +retina/SM +retinal/S +retinue/MS +retire/L +retiredness/M +retiree/MS +retirement/SM +retiring/YP +retort/GD +retract/DG +retractile +retrench/L +retrenchment/MS +retributed +retribution/MS +retributive +retrieval/SM +retrieve/ZGDRSB +retriever/M +retro/SM +retroactive/Y +retrofire/GMSD +retrofit/S +retrofitted +retrofitting +retroflection +retroflex/D +retroflexion/M +retrogradations +retrograde/GYDS +retrogress/SDVG +retrogression/MS +retrogressive/Y +retrorocket/MS +retrospect/SVGMD +retrospection/MS +retrospective/SY +retrovirus/S +retrovision +retry/G +retsina/SM +returnable/S +returned/U +returnee/SM +retype +reuse/B +reutilization +rev/ZM +revanchist +reveal/JBG +revealed/U +revealing/U +revealingly +reveille/MS +revel/SJRDGZ +revelation/MS +revelatory +revelry/MS +revenge/MGSRD +revenger/M +revenue/ZR +revenuer/M +reverberant +reverberate/XVNGSD +reverberation/M +revere/GSD +reverence/SRDGM +reverencer/M +reverend/SM +reverent/Y +reverential/Y +reverie/SM +revers/M +reversal/MS +reverse/Y +reverser/M +reversibility/M +reversible/S +reversibly +reversion/R +reversioner/M +revert/RDVGS +reverter/M +revertible +revet/L +revetment/SM +review/G +revile/GZSDL +revilement/MS +reviler/M +revise/BRZ +revised/U +revisionary +revisionism/SM +revisionist/SM +revitalize/ZR +revival/SM +revivalism/MS +revivalist/MS +revive/RSDG +reviver/M +revivification/M +revivify/X +revocable +revoke/GZRSD +revolt/GRD +revolter/M +revolting/Y +revolution/SM +revolutionariness/M +revolutionary/MSP +revolutionist/MS +revolutionize/GDSRZ +revolutionizer/M +revolve/BSRDZJG +revolver/M +revue/MS +revulsion/MS +revved +revving +rewarded/U +rewarding/Y +rewarm/G +reweave +rewedding +reweigh/G +rewind/BGR +rewire/G +rework/G +rexes +rezone +rhapsodic +rhapsodical +rhapsodize/GSD +rhapsody/SM +rhea/SM +rhenium/MS +rheology/M +rheostat/MS +rhesus/S +rhetoric/MS +rhetorical/YP +rhetorician/MS +rheum/MS +rheumatic/S +rheumatically +rheumatics/M +rheumatism/SM +rheumatoid +rheumy/RT +rhinestone/SM +rhinitides +rhinitis/M +rhino/MS +rhinoceros/MS +rhinotracheitis +rhizome/MS +rho/MS +rhodium/MS +rhododendron/SM +rhodolite/M +rhodonite/M +rhombic +rhomboid/SM +rhomboidal +rhombus/SM +rhubarb/MS +rhyme/DSRGZM +rhymester/MS +rhythm/MS +rhythmic/S +rhythmical/Y +rhythmics/M +rial/MS +rib/MS +ribald/S +ribaldry/MS +ribbed +ribber/S +ribbing/M +ribbon/DMSG +ribcage +riboflavin/MS +ribonucleic +ribosomal +ribosome/MS +rice/DRSMZG +ricer/M +rich/YNSRPT +richen/DG +richness/MS +rick/GSDM +rickets/M +rickety/RT +rickrack/MS +rickshaw/SM +ricochet/GSD +ricotta/MS +rid/ZGRJSB +riddance/SM +ridden +ridding +riddle/GMRSD +ride/CZSGR +rider/CM +riderless +ridership/S +ridge/DSGM +ridgepole/SM +ridgy/RT +ridicule/MGDRS +ridiculer/M +ridiculous/PY +ridiculousness/MS +riding/M +rife/RT +riff/GSDM +riffle/SDG +riffraff/SM +rifle/GZMDSR +rifled/U +rifleman/M +riflemen +rifler/M +rifling/M +rift/GSMD +rig/MS +rigamarole's +rigatoni/M +rigged +rigger/SM +rigging/MS +right/SGTPYRDN +righteous/PYU +righteousness/MS +righteousnesses/U +rightful/PY +rightfulness/MS +rightism/SM +rightist/S +rightmost +rightness/MS +rights/M +rightsize/SDG +rightward/S +rigid/YP +rigidify/S +rigidity/S +rigidness/S +rigmarole/MS +rigor/MS +rigorous/YP +rigorousness/S +rile/DSG +rill/GSMD +rim/GSMDR +rime/MS +rimer/M +rimless +rimmed +rimming +rind/MDGS +ring/GZJDRM +ringer/M +ringing/Y +ringleader/MS +ringlet/SM +ringlike +ringmaster/MS +ringside/ZMRS +ringworm/SM +rink/GDRMS +rinse/DSRG +riot/SMDRGZJ +rioter/M +riotous/PY +riotousness/M +rip/NDRSXTG +riparian/S +ripcord/SM +ripe/PSY +ripen/RDG +ripened/U +ripeness/UM +ripenesses +riper/U +ripest/U +ripoff/S +riposte/SDMG +ripped +ripper/SM +ripping +ripple/RSDGM +rippler/M +ripply/TR +ripsaw/GDMS +riptide/SM +rise/RSJZG +risen +riser/M +risibility/SM +risible/S +rising/M +risk/GSDRM +risker/M +riskily +riskiness/MS +risky/RTP +risotto/SM +risqué +rissole/M +rite/DSM +ritual/MSY +ritualism/SM +ritualistic +ritualistically +ritualized +ritzy/TR +riv/ZGNDR +rival/SGDM +rivaled/U +rivalry/MS +rive/CSGRD +river/CM +riverbank/SM +riverbed/S +riverboat/S +riverfront +riverine +riverside/S +rivet/GZSRDM +riveter/M +riveting/Y +rivulet/SM +riyal/SM +rm +roach/GSDM +road/MIS +roadbed/MS +roadblock/SMDG +roadhouse/SM +roadie/S +roadkill/S +roadrunner/MS +roadshow/S +roadside/S +roadsigns +roadster/SM +roadsweepers +roadway/SM +roadwork/SM +roadworthy +roam/DRGZS +roan/S +roar/DRSJGZ +roarer/M +roaring/T +roast/SGJZRD +roaster/M +rob/SDG +robbed +robber/SM +robbery/SM +robbing +robe's +robe/ESDG +robin/MS +robot/MS +robotic/S +robotism +robotize/GDS +robust/RYPT +robustness/SM +rock/GZDRMS +rockabilly/MS +rockabye +rockbound +rocker/M +rocket/SMDG +rocketry/MS +rockfall/S +rockiness/MS +rocky/SRTP +rococo/MS +rod/SGMD +rodded +rodder +rodding +rode/S +rodent/MS +rodeo/SMDG +roe/SM +roebuck/SM +roentgen/SM +roger/GSD +rogue/GMDS +rogued/K +roguery/MS +rogues/K +roguing/K +roguish/PY +roguishness/SM +roil/SGD +roister/SZGRD +roisterer/M +role/MS +roll/UDSG +rollback/SM +rolled/A +roller/SM +rollerskating +rollick/DGS +rollicking/Y +rolling/S +rollover/S +romaine/MS +roman/S +romance/RSDZMG +romancer/M +romantic/MS +romantically/U +romanticism/MS +romanticist/S +romanticize/SDG +romeo/S +romp/GSZDR +romper/M +rondo/SM +rood/MS +roof/DRMJGZS +roofer/M +roofgarden +roofing/M +roofless +rooftop/S +rook/GDMS +rookery/MS +rookie/SRMT +room/MDRGZS +roomer/M +roomette/SM +roomful/MS +roominess/MS +roommate/SM +roomy/TPSR +roost/SGZRDM +rooster/M +root/MGDRZS +rooted/P +rooter/M +rootless/P +rootlessness/M +rootlet/SM +rootstock/M +rope/DRSMZG +roper/M +roping/M +rosary/SM +rose/MGDS +roseate/Y +rosebud/MS +rosebush/SM +rosemary/MS +rosette/SDMG +rosewater +rosewood/SM +rosily +rosin/SMDG +rosiness/MS +roster/DMGS +rostra's +rostrum/SM +rosy/RTP +rot/SDG +rota/MS +rotary/S +rotate/VGNXSD +rotated/U +rotation/M +rotational/Y +rotative/Y +rotator/SM +rotatory +rote/MS +rotgut/MS +rotisserie/MS +rotogravure/SM +rotor/MS +rototill/RZ +rotted +rotten/RYSTP +rottenness/S +rotter/M +rotting +rotund/SDYPG +rotunda/SM +rotundity/S +rotundness/S +rouge/GMDS +rough/XPYRDNGT +roughage/SM +roughen/DG +rougher/M +roughhouse/GDSM +roughish +roughneck/MDSG +roughness/MS +roughs +roughshod +roulette/MGDS +round/YRDSGPZT +roundabout/PSM +rounded/P +roundedness/M +roundelay/SM +roundels +rounder/M +roundhead/D +roundheaded/P +roundheadedness/M +roundhouse/SM +roundish +roundness/MS +roundoff +roundup/MS +roundworm/MS +rouse/DSRG +rouser/M +roust/SGD +roustabout/SM +rout/GZJMDRS +route's +route/ASRDZGJ +router/M +routine/SYM +routing/M +routinize/GSD +roué/MS +rove/ZGJDRS +rover/M +roving/M +row/SJZMGNDR +rowboat/SM +rowdily +rowdiness/MS +rowdy/PTSR +rowdyism/MS +rowel/DMSG +rowen/M +rower/M +royal/SY +royalist/SM +royalty/MS +rpm +rps +rs +rt +rte +rub/S +rubato/MS +rubbed +rubber/SDMG +rubberize/GSD +rubberneck/DRMGSZ +rubbery/TR +rubbing/M +rubbish/DSMG +rubbishy +rubble/GMSD +rubdown/MS +rube/SM +rubella/MS +rubicund +rubidium/SM +ruble/MS +rubout +rubric/MS +ruby/MTGDSR +ruck/M +rucksack/SM +ruckus/SM +ruction/SM +rudder/MS +rudderless +ruddiness/MS +ruddy/PTGRSD +rude/PYTR +rudeness/MS +rudiment/SM +rudimentariness/M +rudimentary/P +rue/GDS +rueful/PY +ruefulness/S +ruff/GSYDM +ruffian/GSMDY +ruffle/RSDG +ruffled/U +ruffler/M +ruffly/TR +rug/MS +rugby/SM +rugged/PYRT +ruggedness/S +rugging +ruin/MGSDR +ruination/MS +ruiner/M +ruinous/YP +ruinousness/M +rule/MZGJDRS +rulebook/S +ruled/U +ruler/GMD +ruling/M +rum/XSMN +rumba/GDMS +rumble/JRSDG +rumbler/M +rumbustious +rumen/M +ruminant/YMS +ruminate/VNGXSD +ruminative/Y +rummage/GRSD +rummager/M +rummer +rummest +rummy/TRSM +rumor/ZMRDSG +rumored/U +rumorer/M +rumormonger/SGMD +rump/GMYDS +rumple/SDG +rumply/TR +rumpus/SM +run/AS +runabout/SM +runaround/S +runaway/S +rundown/SM +rune/MS +rung/MS +runic +runlet/SM +runnable +runnel/SM +runner/MS +running/S +runny/RT +runoff/MS +runt/MS +runtime +runtiness/M +runty/RPT +runway/MS +rupee/MS +rupiah/M +rupiahs +rupture/GMSD +rural/Y +rurality/M +ruse/MS +rush/DSRGZ +rusher/M +rushes/I +rushing/M +rushy/RT +rusk/MS +russet/MDS +russetting +rust/MSDG +rustic/S +rustically +rusticate/GSD +rustication/M +rusticity/S +rustiness/MS +rustle/RSDGZ +rustler/M +rustproof/DGS +rusty/XNRTP +rut/MS +rutabaga/SM +ruthenium/MS +rutherfordium/SM +ruthless/YP +ruthlessness/MS +rutted +rutting +rutty/RT +rye/MS +s's/KI +s/XJBG +sabbath +sabbatical/S +saber/GSMD +sabered/U +sable/GMDS +sabot/MS +sabotage/DSMG +saboteur/SM +sabra/MS +sac/SM +saccharides +saccharin/MS +saccharine +sacerdotal +sachem/MS +sachet/SM +sack/GJDRMS +sackcloth/M +sackcloths +sacker/M +sackful/MS +sacking/M +sacra/L +sacral +sacrament/DMGS +sacramental/S +sacred/PY +sacredness/S +sacrifice/RSDZMG +sacrificer/M +sacrificial/Y +sacrilege/MS +sacrilegious/Y +sacristan/SM +sacristy/MS +sacroiliac/S +sacrosanct/P +sacrosanctness/MS +sacrum/M +sad/PY +sadden/DSG +sadder +saddest +saddle's +saddle/UGDS +saddlebag/SM +saddler/M +sades +sadism/MS +sadist/MS +sadistic +sadistically +sadness/SM +sadomasochism/MS +sadomasochist/S +sadomasochistic +safari/GMDS +safe/URPTY +safeguard/MDSG +safekeeping/MS +safeness's/U +safeness/MS +safes +safety/SDMG +safflower/SM +saffron/MS +sag/TSR +saga/MS +sagacious/YP +sagaciousness/M +sagacity/MS +sage/MYPS +sagebrush/SM +sagged +sagger +sagging +saggy/RT +sago/MS +saguaro/SM +sahib/MS +said/U +saids +sail/GJMDRS +sailboard/DGS +sailboat/SRMZG +sailcloth/M +sailcloths +sailer/M +sailfish/SM +sailing/M +sailor/YMS +sailplane/SDMG +saint/YDMGS +sainthood/MS +saintlike +saintliness/MS +saintly/RTP +saith +saiths +sake/MRS +saker/M +saki's +salaam/GMDS +salable/U +salacious/YP +salaciousness/MS +salacity/MS +salad/SM +salamander/MS +salami/MS +salary/SDMG +sale/ABMS +saleability/M +salesclerk/SM +salesgirl/SM +saleslady/S +salesman/M +salesmanship/SM +salesmen +salespeople/M +salesperson/MS +salesroom/M +saleswoman +saleswomen +salience/MS +saliency +salient/SY +saline/S +salinger +salinity/MS +saliva/MS +salivary +salivate/XNGSD +salivation/M +sallow/TGRDSP +sallowness/MS +sally/GSDM +salmon/SM +salmonella/M +salmonellae +salon/SM +saloon/MS +saloonkeeper +salsa/MS +salsify/M +salt/GZTPMDRS +saltcellar/SM +salted/UC +salter/M +saltine/MS +saltiness/SM +saltness/M +saltpeter/SM +salts/C +saltshaker/S +saltwater +salty/RSPT +salubrious/YP +salubriousness/M +salubrity/M +salutariness/M +salutary/P +salutation/SM +salutatory/S +salute/RSDG +saluter/M +salvage/MGRSD +salvageable +salvager/M +salvation/MS +salve/GZMDSR +salver/M +salvo/GMDS +samarium/MS +samba/GSDM +same/SP +sameness/MS +samovar/SM +sampan/MS +sample/RSDJGMZ +sampler/M +sampling/M +samurai/M +sanatorium/MS +sanctification/M +sanctifier/M +sanctify/RSDGNX +sanctimonious/PY +sanctimoniousness/MS +sanctimony/MS +sanction/SMDG +sanctioned/U +sanctity/SM +sanctuary/MS +sanctum/SM +sand/SMDRGZ +sandal/MDGS +sandalwood/SM +sandbag/MS +sandbagged +sandbagging +sandbank/SM +sandbar/S +sandblast/GZSMRD +sandblaster/M +sandbox/MS +sandcastle/S +sander/M +sandhill +sandhog/SM +sandiness/S +sandlot/SM +sandlotter/S +sandman/M +sandmen +sandpaper/DMGS +sandpile +sandpiper/MS +sandpit/M +sandstone/MS +sandstorm/SM +sandwich/SDMG +sandy/PRT +sane/IRYTP +saned +saneness's/I +saneness/MS +sanes +sang/S +sangfroid/S +sangria/SM +sanguinary +sanguine/F +sanguined +sanguinely +sanguineness/M +sanguineous/F +sanguines +sanguining +saning +sanitarian/S +sanitarium/SM +sanitary/S +sanitate/NX +sanitation/M +sanitize/RSDZG +sanitizer/M +sanity/SIM +sank +sans +sanserif +sap/MS +sapience/MS +sapient +sapless +sapling/SM +sapped +sapper/SM +sapphire/MS +sappiness/SM +sapping +sappy/RPT +saprophyte/MS +saprophytic +sapsucker/SM +sapwood/SM +saran/SM +sarape's +sarcasm/MS +sarcastic +sarcastically +sarcoma/MS +sarcophagi +sarcophagus/M +sardine/SDMG +sardonic +sardonically +sarge/SM +sari/MS +sarong/MS +sarsaparilla/MS +sartorial/Y +sartorius/M +sash/GMDS +sashay/GDS +sass/GDSM +sassafras/MS +sassy/TRS +sat/DG +satanic +satanical/Y +satanism/S +satanist/S +satchel/SM +sate/S +sateen/MS +satellite/GMSD +satiable/I +satiate/GNXSD +satiation/M +satiety/MS +satin/MDSG +satinwood/MS +satiny +satire/SM +satiric +satirical/Y +satirist/SM +satirize/DSG +satirizes/U +satisfaction/ESM +satisfactorily/U +satisfactoriness/MU +satisfactory/UP +satisfiability/U +satisfiable/U +satisfied/UE +satisfier/M +satisfies/E +satisfy/GZDRS +satisfying/EU +satisfyingly +satori/SM +satrap/SM +saturate/XDRSNG +saturated/CUA +saturater/M +saturates/A +saturation/M +saturnalia +saturnine/Y +satyr/MS +satyriases +satyriasis/M +satyric +sauce/DSRGZM +saucepan/SM +saucer/M +saucily +sauciness/S +saucy/TRP +sauerkraut/SM +sauna/DMSG +saunter/DRSG +saurian/S +sauropod/SM +sausage/MS +sauté/DGS +savage/GTZYPRSD +savageness/SM +savagery/MS +savanna/MS +savant/SM +save/ZGJDRSB +saved/U +saveloy/M +saver/M +savior/SM +savor/SMRDGZ +savored/U +savorer/M +savorier +savoriest +savoriness/S +savoring/Y +savoringly/S +savory/UMPS +savoy/SM +savvy/GTRSD +saw/SMDRG +sawbones/M +sawbuck/SM +sawdust/MDSG +sawer/M +sawfly/SM +sawhorse/MS +sawmill/SM +sawtooth +sawyer/MS +sax/MS +saxifrage/SM +saxophone/MS +saxophonist/SM +say/USG +sayer/SM +sayest +saying/MS +says/M +scab/SM +scabbard/SGDM +scabbed +scabbiness/SM +scabbing +scabby/RTP +scabies/M +scabrous/YP +scabrousness/M +scad/SM +scaffold/JGDMS +scaffolding/M +scalability +scalar/SM +scalawag/SM +scald/GJRDS +scale/JGZMBDSR +scaled/AU +scaleless +scalene +scaler/M +scales/A +scaliness/MS +scaling/A +scallion/MS +scallop/GSMDR +scalloper/M +scalloping/M +scalp/GZRDMS +scalpel/SM +scalper/M +scalping/M +scaly/TPR +scam/SM +scammed +scamming +scamp/RDMGZS +scamper/GD +scampi/M +scan/AS +scandal/GMDS +scandalize/GDS +scandalized/U +scandalmonger/SM +scandalous/YP +scandalousness/M +scandium/MS +scanned/A +scanner/SM +scanning/A +scansion/SM +scant/CDRSG +scantest +scantily +scantiness/MS +scantly +scantness/MS +scanty/TPRS +scape/M +scapegoat/SGDM +scapegrace/MS +scapula/M +scapulae +scapular/S +scar/DRMSG +scarab/SM +scarce/RTYP +scarceness/SM +scarcity/MS +scare/S +scarecrow/MS +scaremonger/SGM +scaremongering/M +scarer/M +scarf/SDGM +scarface +scarification/M +scarify/DRSNGX +scarily +scariness/S +scarlatina/MS +scarlet/MDSG +scarp/SDMG +scarred +scarring +scarves/M +scary/PTR +scat/S +scathe/DG +scathed/U +scathing/Y +scatological +scatology/SM +scatted +scatter/DRJZSG +scatterbrain/MDS +scatterer/M +scattergun +scattering/YM +scatting +scavenge/GDRSZ +scavenger/M +scenario/SM +scenarist/MS +scene/GMDS +scenery/SM +scenic/S +scenically +scent's/C +scent/GDMS +scented/U +scentless +scents/C +scepter/DMSG +scepters/U +sceptically +sch +schedule's +schedule/ADSRG +scheduled/U +scheduler/MS +schema/M +schemata +schematic/S +schematically +scheme/JSRDGMZ +schemer/M +schemta +scherzo/MS +schilling/SM +schism/SM +schismatic/S +schist/SM +schizo/S +schizoid/S +schizomycetes +schizophrenia/SM +schizophrenic/S +schizophrenically +schlemiel/MS +schlep/S +schlepped +schlepping +schlock/SM +schlocky/TR +schmaltz/MS +schmaltzy/TR +schmo/M +schmoes +schmooze/GSD +schmuck/MS +schnapps/M +schnauzer/MS +schnitzel/MS +schnook/SM +schnoz/S +schnozzle/MS +scholar/SYM +scholarship/MS +scholastic/S +scholastically +school/ZGMRDJS +schoolbag/SM +schoolbook/SM +schoolboy/MS +schoolchild/M +schoolchildren +schooldays +schooled/U +schoolfellow/S +schoolfriend +schoolgirl/MS +schoolgirlish +schoolhouse/MS +schooling/M +schoolmarm/MS +schoolmarmish +schoolmaster/SGDM +schoolmate/MS +schoolmistress/MS +schoolroom/SM +schoolteacher/MS +schoolwork/SM +schoolyard/SM +schooner/SM +schuss/SDMG +schussboomer/S +schwa/SM +sci +sciatic/S +sciatica/SM +science/FMS +scientific/U +scientifically/U +scientist/SM +scimitar/SM +scintilla/MS +scintillate/GNDSX +scintillation/M +scintillator/SM +scion/SM +scissor/SGD +scleroses +sclerosis/M +sclerotic/S +scoff/RDGZS +scoffer/M +scofflaw/MS +scold/GSJRD +scolder/M +scolioses +scoliosis/M +scollop's +sconce/SDGM +scone/SM +scoop/SRDMG +scooper/M +scoot/SRDGZ +scooter/M +scope/DSGM +scops +scorbutic +scorch/ZGRSD +scorcher/M +scorching/Y +score/ZMDSRJG +scoreboard/MS +scorecard/MS +scored/M +scorekeeper/SM +scoreless +scoreline +scorn/SGZMRD +scorner/M +scornful/PY +scornfulness/M +scorpion/SM +scotch/MSDG +scotchs +scoundrel/YMS +scour/SRDGZ +scourer/M +scourge/MGRSD +scourger/M +scouring/M +scout/SRDMJG +scouter/M +scouting/M +scoutmaster/SM +scow/DMGS +scowl/SRDG +scowler/M +scrabble/DRSZG +scrabbler/M +scrag/SM +scragged +scragging +scraggly/TR +scraggy/TR +scram/S +scramble/UDSRG +scrambler's/U +scrambler/MS +scrammed +scramming +scrap/SGZJRDM +scrapbook/SM +scrape/S +scraper/M +scrapheap/SM +scrapped +scrapper/SM +scrapping +scrappy/RT +scrapyard/S +scratch/JDRSZG +scratched/U +scratcher/M +scratches/M +scratchily +scratchiness/S +scratchy/TRP +scrawl/GRDS +scrawler/M +scrawly/RT +scrawniness/MS +scrawny/TRP +scream/ZGSRD +screamer/M +screaming/Y +scree/DSM +screech/GMDRS +screecher/M +screechy/TR +screed/MS +screen/RDMJSG +screened/U +screening/M +screenplay/MS +screenwriter/MS +screw's +screw/GUSD +screwball/SM +screwdriver/SM +screwer/M +screwiness/S +screwup +screwworm/MS +screwy/RTP +scribal +scribble/JZDRSG +scribbler/M +scribe's +scribe/CDRSGIK +scriber/MKIC +scrim/SM +scrimmage/RSDMG +scrimmager/M +scrimp/DGS +scrimshaw/GSDM +scrip/SM +script/FGMDS +scripted/U +scriptural/Y +scripture/MS +scriptwriter/SM +scriptwriting/M +scriven/ZR +scrivener/M +scrod/M +scrofula/MS +scrofulous +scroll/GMDSB +scrollbar/SM +scrooge/SDMG +scrota +scrotal +scrotum/M +scrounge/ZGDRS +scroungy/TR +scrub/S +scrubbed +scrubber/MS +scrubbing +scrubby/TR +scruff/SM +scruffily +scruffiness/S +scruffy/PRT +scrum/MS +scrummage/MG +scrumptious/Y +scrunch/DSG +scrunchy/S +scruple/SDMG +scrupulosity/SM +scrupulous/UPY +scrupulousness's +scrupulousness/US +scrutable/I +scrutinize/RSDGZ +scrutinized/U +scrutinizer/M +scrutinizing/UY +scrutinizingly/S +scrutiny/MS +scuba/SDMG +scud/S +scudded +scudding +scuff/GSD +scuffle/SDG +scull/SRDMGZ +sculler/M +scullery/MS +scullion/MS +sculpt/SDG +sculptor/MS +sculptress/MS +sculptural/Y +sculpture/SDGM +scum/MS +scumbag/S +scummed +scumming +scummy/TR +scupper/SDMG +scurf/MS +scurfy/TR +scurrility/MS +scurrilous/PY +scurrilousness/MS +scurry/GJSD +scurvily +scurviness/M +scurvy/SRTP +scutcheon/SM +scuttle/MGSD +scuttlebutt/MS +scuzzy/RT +scythe/SDGM +sea/MYS +seabed/S +seabird/S +seaboard/MS +seaborne +seacoast/MS +seafare/JRZG +seafarer/M +seafood/MS +seafront/MS +seagoing +seagull/S +seahorse/S +seal/MDRSGZ +sealant/MS +sealed/AU +sealer/M +seals/UA +sealskin/SM +seam/MNDRGS +seamail +seaman/YM +seamanship/SM +seamer/M +seaminess/M +seamless/PY +seamlessness/M +seams/I +seamstress/MS +seamy/TRP +seaplane/SM +seaport/SM +seaquake/M +sear/DRSJGT +search/RSDAGZ +searcher/AM +searching/YS +searchlight/SM +searing/Y +seascape/SM +seashell/MS +seashore/SM +seasick/P +seasickness/SM +seaside/SM +season/JRDYMBZSG +seasonable/UP +seasonableness/M +seasonably/U +seasonal/Y +seasonality +seasoned/U +seasoner/M +seasoning/M +seat's +seat/UDSG +seatbelt +seated/A +seater/M +seating/SM +seawall/S +seaward/S +seawater/S +seaway/MS +seaweed/SM +seaworthiness/MU +seaworthinesses +seaworthy/TRP +sebaceous +seborrhea/SM +sec'y +sec/S +secant/SM +secede/GRSD +secession/MS +secessionist/MS +seclude/GSD +secluded/YP +secludedness/M +seclusion/SM +seclusive +second/RDYZGSL +secondarily +secondary/PS +seconder/M +secondhand +secrecy/MS +secret/TVGRDYS +secretarial +secretariat/MS +secretary/SM +secretaryship/MS +secrete/XNS +secretion/M +secretive/PY +secretiveness/S +secretory +sect/ISM +sectarian/S +sectarianism/MS +sectary/MS +section/ASEM +sectional/SY +sectionalism/MS +sectionalized +sectioned +sectioning +sector/EMS +sectoral +sectored +sectoring +sects/E +secular/SY +secularism/MS +secularist/MS +secularity/M +secularization/MS +secularize/GSD +secularized/U +secure/PGTYRSDJ +secured/U +securely/I +security/MSI +secy +sedan/SM +sedate/PXVNGTYRSD +sedateness/SM +sedation/M +sedative/S +sedentary +sedge/SM +sedgy/RT +sediment/SGDM +sedimentary +sedimentation/SM +sedition/SM +seditious/PY +seditiousness/M +seduce/RSDGZ +seducer/M +seduction/MS +seductive/YP +seductiveness/MS +seductress/SM +sedulous/Y +see/U +seed's +seed/ADSG +seedbed/MS +seedcase/SM +seeded/U +seeder/MS +seediness/MS +seeding/S +seedless +seedling/SM +seedpod/S +seedy/TPR +seeing's +seeing/U +seeings +seek/GZSR +seeker/M +seeking/Y +seem/GJSYD +seeming/Y +seemliness's +seemliness/US +seemly/UTPR +seen/U +seep/GSD +seepage/MS +seer/SM +seersucker/MS +sees +seesaw/DMSG +seethe/SDGJ +segment/SGDM +segmental/Y +segmentation/SM +segmented/U +segregant +segregate/XCNGSD +segregated/U +segregation/CM +segregationist/SM +segregative +segue/DS +segueing +seigneur/MS +seignior/SM +seine/GZMDSR +seiner/M +seismic +seismically +seismograph/ZMR +seismographer/M +seismographic +seismographs +seismography/SM +seismologic +seismological +seismologist/MS +seismology/SM +seismometer/S +seize/BJGZDSR +seizer/M +seizin/MS +seizing/M +seizor/MS +seizure/MS +seldom +select/PDSVGB +selected/UAC +selection/MS +selectional +selective/YP +selectiveness/M +selectivity/MS +selectman/M +selectmen +selectness/SM +selector/SM +selects/A +selenate/M +selenite/M +selenium/MS +selenographer/SM +selenography/MS +self/GPDMS +selfish/PUY +selfishness/SU +selfless/YP +selflessness/MS +selfness/M +selfsame/P +selfsameness/M +sell/AZGSR +seller/AM +sellout/MS +seltzer/S +selvage/MGSD +selves/M +semantic/S +semantical/Y +semanticist/SM +semantics/M +semaphore/GMSD +semblance/ASME +semen/SM +semester/SM +semi/SM +semiannual/Y +semiarid +semiautomated +semiautomatic/S +semicircle/SM +semicircular +semicolon/MS +semiconductor/SM +semiconscious +semidefinite +semidetached +semidrying/M +semifinal/MS +semifinalist/MS +semilogarithmic +semimonthly/S +seminal/Y +seminar/SM +seminarian/MS +seminary/MS +semiofficial +semiotic/S +semioticians +semiotics/M +semipermanent/Y +semipermeable +semiprecious +semiprivate +semiprofessional/YS +semipublic +semiquantitative/Y +semiretired +semisecret +semiskilled +semisolid/S +semistructured +semisweet +semitic/S +semitone/SM +semitrailer/SM +semitrance +semitransparent +semitropical +semivowel/MS +semiweekly/S +semiyearly +semolina/SM +sempiternal +sempstress/SM +sen +senate/MS +senator/MS +senatorial +send/SRGZ +sender/M +sends/A +senescence/SM +senescent +senile/SY +senility/MS +senior/MS +seniority/SM +senna/MS +senor/MS +senora/S +senorita/S +sens/DSG +sensate/YNX +sensately/I +sensation/M +sensational/Y +sensationalism/MS +sensationalist/S +sensationalize/GSD +sense/M +senseless/PY +senselessness/SM +sensibility/ISM +sensible/PRST +sensibleness/MS +sensibly/I +sensitive/YIP +sensitiveness's/I +sensitiveness/MS +sensitives +sensitivity/ISM +sensitization/CSM +sensitize/SDCG +sensitized/U +sensitizers +sensor/MS +sensory +sensual/YF +sensualist/MS +sensuality/MS +sensuous/PY +sensuousness/S +sent/UFEA +sentence/SDMG +sentential/Y +sententious/Y +sentience/ISM +sentient/YS +sentiment/MS +sentimental/Y +sentimentalism/SM +sentimentalist/SM +sentimentality/SM +sentimentalization/SM +sentimentalize/RSDZG +sentimentalizes/U +sentinel/GDMS +sentry/SM +sepal/SM +separability/MSI +separable/PI +separableness/MI +separably/I +separate/YNGVDSXP +separateness/MS +separates/M +separation/M +separatism/SM +separatist/SM +separator/SM +sepia/MS +sepses +sepsis/M +sept/M +septa/M +septate/N +septennial/Y +septet/MS +septic/S +septicemia/SM +septicemic +septillion/M +septuagenarian/MS +septum/M +sepulcher/MGSD +sepulchers/UA +sepulchral/Y +seq +sequel/MS +sequence's/F +sequence/DRSJZMG +sequenced/A +sequencer/M +sequences/F +sequent/F +sequential/YF +sequentiality/FM +sequentialize/DSG +sequester/SDG +sequestrate/XGNDS +sequestration/M +sequin/SDMG +sequitur +sequoia/MS +sera's +seraglio/SM +serape/S +seraph/M +seraphic +seraphically +seraphim's +seraphs +sere/TGDRS +serenade/MGDRS +serenader/M +serendipitous/Y +serendipity/MS +serene/GTYRSDP +sereneness/SM +serenity/MS +serf/MS +serfdom/MS +serge/DSGM +sergeant/SM +serial/MYS +serialization/MS +serialize/GSD +series/M +serif/SMD +serigraph/M +serigraphs +serious/PY +seriousness/SM +sermon/SGDM +sermonize/GSD +serological/Y +serology/MS +serons +serous +serpent/GSDM +serpentine/GYS +serrate/GNXSD +serration/M +serried +serum/MS +servant/SDMG +serve/AGCFDSR +served/U +server/MCF +servers +service's/E +service/MGSRD +serviceability/SM +serviceable/P +serviceableness/M +serviced/U +serviceman/M +servicemen +services/E +servicewoman +servicewomen +serviette/MS +servile/U +servilely +servileness/M +serviles +servility/SM +serving/SM +servitor/SM +servitude/MS +servo/S +servomechanism/MS +servomotor/MS +sesame/MS +sesquicentennial/S +sessile +session/SM +set's +set/SIA +setback/S +setscrew/SM +sett/BJGZSMR +settable/A +settee/MS +setter/M +setting's +setting/AS +settle/AUDSG +settlement/ASM +settler/MS +settling/S +setup/MS +seven/SMH +sevenfold +sevenpence +seventeen/HMS +seventeenths +sevenths +seventieths +seventy/MSH +sever/SGTRD +several/YS +severalfold +severalty/M +severance/SM +severe/PY +severed/E +severeness/SM +severing/E +severity/MS +severs/E +sew/SAGD +sewage/MS +sewer/GSMD +sewerage/SM +sewing/SM +sewn +sex/GMDS +sexagenarian/MS +sexily +sexiness/MS +sexism/SM +sexist/SM +sexless +sexologist/SM +sexology/MS +sexpot/SM +sextant/SM +sextet/SM +sextillion/M +sexton/MS +sextuple/MDG +sextuplet/MS +sexual/Y +sexuality/MS +sexualized +sexy/RTP +sf +sh/DRS +shabbily +shabbiness/SM +shabby/RTP +shack/GMDS +shackle's +shackle/UGDS +shackler/M +shad/DRJGSM +shade/SM +shaded/U +shadeless +shadily +shadiness/MS +shading/M +shadow/GSDRM +shadowbox/SDG +shadower/M +shadowiness/M +shadowy/TRP +shady/TRP +shaft/SDMG +shafting/M +shag/MS +shagged +shagginess/SM +shagging +shaggy/TPR +shah/M +shahs +shakable/U +shakably/U +shake/SRGZB +shakeable +shakedown/S +shaken/U +shakeout/SM +shaker/M +shakeup/S +shakily +shakiness/S +shaking/M +shaky/TPR +shale/SM +shall +shallot/SM +shallow/STPGDRY +shallowness/SM +shalom +shalt +sham/MDSG +shaman/SM +shamanic +shamble/DSG +shambles/M +shame/SM +shamefaced/Y +shameful/YP +shamefulness/S +shameless/PY +shamelessness/SM +shammed +shammer +shamming +shammy's +shampoo/DRSMZG +shampooer/M +shamrock/SM +shan't +shandy/M +shanghai/SDG +shank/SMDG +shantis +shantung/MS +shanty/SM +shantytown/SM +shape's +shape/AGDSR +shaped/U +shapeless/PY +shapelessness/SM +shapeliness/S +shapely/RPT +shaper/S +sharable/U +shard/SM +share/DSRGZMB +shareable +sharecrop/S +sharecropped +sharecropper/MS +sharecropping +shared/U +shareholder/MS +shareholding/S +sharer/M +shareware/S +sharia/SM +shark/SGMD +sharkskin/SM +sharp/SGTZXPYRDN +sharpen/ASGD +sharpened/U +sharpener/S +sharper/M +sharpie/SM +sharpness/MS +sharpshoot/JRGZ +sharpshooter/M +sharpshooting/M +sharpy's +shat +shatter/DSG +shattering/Y +shatterproof +shave/DSRJGZ +shaved/U +shaver/M +shaving/M +shaw/M +shawl/SDMG +shay/MS +she'd +she'll +she/M +sheaf/MDGS +shear/RDGZS +shearer/M +sheath/GJMDRS +sheathe/UGSD +sheather/M +sheathing/M +sheaths +sheave/SDG +sheaves/M +shebang/MS +shed's +shed/U +shedding +sheds +sheen/MDGS +sheeny/TRSM +sheep/M +sheepdog/SM +sheepfold/MS +sheepherder/MS +sheepish/YP +sheepishness/SM +sheepskin/SM +sheer/PGTYRDS +sheerness/S +sheet/RDMJSG +sheeting/M +sheetlike +sheik/SM +sheikdom/SM +sheikh's +shekel/MS +shelf/MDGS +shell/RDMGS +shellac/S +shellacked +shellacking/MS +shelled/U +shellfire/SM +shellfish/SM +shelter/DRMGS +sheltered/U +shelterer/M +shelve/JRSDG +shelver/M +shelves/M +shelving/M +shenanigan/SM +shepherd/DMSG +shepherdess/S +sherbet/MS +sherd's +sheriff/SM +sherlock/M +sherry/MS +shew/GSD +shewn +shh +shiatsu/S +shibboleth/M +shibboleths +shield/MDRSG +shielded/U +shielder/M +shift/RDGZS +shiftily +shiftiness/SM +shiftless/PY +shiftlessness/S +shifty/TRP +shill/DJSG +shillelagh/M +shillelaghs +shilling/M +shim/SM +shimmed +shimmer/DGS +shimmery +shimming +shimmy/DSMG +shin/SGZDRM +shinbone/SM +shindig/MS +shine/S +shiner/M +shingle/MDRSG +shingler/M +shinguard +shininess/MS +shining/Y +shinned +shinning +shinny/GDSM +shinsplints +shiny/PRT +ship's +ship/SLA +shipboard/MS +shipborne +shipbuild/RGZJ +shipbuilder/M +shipload/SM +shipman/M +shipmate/SM +shipmen +shipment/AMS +shipowner/MS +shippable +shipped/A +shipper/SM +shipping/MS +shipshape +shipwreck/GSMD +shipwright/MS +shipyard/MS +shire/MS +shirk/RDGZS +shirker/M +shirr/GJDS +shirt/JDMSG +shirtfront/S +shirting/M +shirtless +shirtmake/R +shirtmaker/M +shirtsleeve/MS +shirttail/S +shirtwaist/SM +shit/S! +shitting/! +shitty/RT! +shiv/SZRM +shiver/GDR +shiverer/M +shivery +shivved +shivving +shlemiel's +shoal/SRDMGT +shoat/SM +shock/SGZRD +shocker/M +shocking/Y +shockproof +shod/U +shoddily +shoddiness/SM +shoddy/RSTP +shoe/MS +shoehorn/GSMD +shoeing +shoelace/MS +shoemake/RZ +shoemaker/M +shoer's +shoeshine/MS +shoestring/MS +shoetree/MS +shogun/MS +shogunate/SM +shone +shoo/DSG +shoofly +shook/SM +shoot/SJRGZ +shooter/M +shootout/MS +shop/MS +shopkeep/RGZ +shopkeeper/M +shoplift/SRDGZ +shoplifter/M +shoplifting/M +shoppe/RSDGZJ +shopped/M +shopper/M +shopping/M +shoptalk/SM +shopworn +shore/DSRGMJ +shorebird/S +shoreline/SM +shoring/M +short/SGTXYRDNP +shortage/MS +shortbread/MS +shortcake/SM +shortchange/DSG +shortcoming/MS +shortcrust +shortcut/MS +shortcutting +shorten/RDGJ +shortener/M +shortening/M +shortfall/SM +shorthand/DMS +shorthorn/MS +shortie's +shortish +shortlist/GD +shortness/MS +shortsighted/YP +shortsightedness/S +shortstop/MS +shortwave/SM +shorty/SM +shot/MS +shotgun/SM +shotgunned +shotgunner +shotgunning +shotted +shotting +should/TZR +shoulder/GMD +shouldn't +shout/SGZRDM +shove/DSRG +shovel/MDRSZG +shoveler/M +shovelful/MS +shover/M +show/GDRZJS +showbiz +showbizzes +showboat/SGDM +showcase/MGSD +showdown/MS +shower/GDM +showery/TR +showgirl/SM +showily +showiness/MS +showing/M +showman/M +showmanship/SM +showmen +shown +showoff/S +showpiece/SM +showplace/SM +showroom/MS +showy/RTP +shpt +shrank +shrapnel/SM +shred/MS +shredded +shredder/MS +shredding +shrew/GSMD +shrewd/RYTP +shrewdness/SM +shrewish/PY +shrewishness/M +shriek/SGDRMZ +shrieker/M +shrift/SM +shrike/SM +shrill/DRTGPS +shrillness/MS +shrilly +shrimp/MDGS +shrine/SDGM +shrink/SRBG +shrinkage/SM +shrinker/M +shrinking/U +shrive/RSDG +shrivel/GSD +shriven +shroud/GSMD +shrub/SM +shrubbed +shrubbery/SM +shrubbing +shrubby/TR +shrug/S +shrugged +shrugging +shrunk/N +shtick/S +shuck/SGMRD +shucker/M +shucks/S +shudder/DSG +shuddery +shuffle/GDSRZ +shuffleboard/MS +shuffled/A +shuffles/A +shuffling/A +shun/S +shunned +shunning +shunt/GSRD +shunter/M +shush/SDG +shut/S +shutdown/MS +shuteye/SM +shutoff/M +shutout/SM +shutter/DMGS +shutterbug/S +shuttering/M +shutting +shuttle/MGDS +shuttlecock/MDSG +shy/DRSGTZY +shyer +shyest +shyness/SM +shyster/SM +sibilance/M +sibilancy/M +sibilant/SY +sibling/SM +sibyl/SM +sibylline +sic/S +sick/GXTYNDRSP +sickbay/M +sickbed/S +sicken/JRDG +sickener/M +sickening/Y +sicker/Y +sickie/SM +sickish/PY +sickle/SDGM +sickliness/M +sickly/TRSDPG +sickness/MS +sicko/S +sickout/S +sickroom/SM +side/ISRM +sidearm/S +sideband/MS +sidebar/MS +sideboard/SM +sideburns +sidecar/MS +sided/A +sidedness +sidekick/MS +sidelight/SM +sideline/MGDRS +sidelong +sideman/M +sidemen +sidepiece/S +sider/FA +sidereal +sides/A +sidesaddle/MS +sideshow/MS +sidesplitting +sidestep/S +sidestepped +sidestepping +sidestroke/GMSD +sideswipe/GSDM +sidetrack/SDG +sidewalk/MS +sidewall/MS +sidewards +sideway/SM +sidewinder/SM +siding/SM +sidle/DSG +siege/GMDS +sienna/SM +sierra/SM +siesta/MS +sieve/GZMDS +sift/GZJSDR +sifted/UA +sifter/M +sigh/DRG +sigher/M +sighs +sight/ISM +sighted/P +sighter/M +sighting/S +sightless/Y +sightliness/UM +sightly/TURP +sightread +sightsee/RZ +sightseeing/S +sigma/SM +sigmoid +sign's +sign/GARDCS +signal's +signal/A +signaled +signaler/S +signaling +signalization/S +signalize/GSD +signally +signalman/M +signalmen +signals +signatory/SM +signature/MS +signboard/MS +signed/FU +signer/SC +signet/SGMD +significance/IMS +significant/YS +significantly/I +signification/M +signify/DRSGNX +signing/S +signor/SFM +signora/SM +signore/M +signori +signories +signorina/SM +signorine +signpost/DMSG +signs/F +silage/GMSD +siled +silence/MZGRSD +silencer/M +silent/TSPRY +silentness/M +silhouette/GMSD +silica/SM +silicate/SM +siliceous +silicide/M +silicon/MS +silicone/SM +silicoses +silicosis/M +silk/GXNDMS +silken/DG +silkily +silkiness/SM +silkscreen/SM +silkworm/MS +silky/RSPT +sill/MS +silliness/SM +silly/PRST +silo/GSM +silt/MDGS +siltation/M +siltstone/M +silty/RT +silver/RDYMGS +silverer/M +silverfish/MS +silversmith/M +silversmiths +silverware/SM +silvery/RTP +simian/S +similar/EY +similarity/EMS +simile/SM +similitude/SME +simmer/GSD +simonize/SDG +simony/MS +simpatico +simper/GDS +simple/RSDGTP +simpleminded/YP +simpleness/S +simpleton/SM +simplex/S +simplicity/MS +simplified/U +simplify/ZXRSDNG +simplistic +simplistically +simply +simulacrum/M +simulate/XENGSD +simulation/ME +simulative +simulator/SEM +simulcast/GSD +simultaneity/SM +simultaneous/YP +simultaneousness/M +sin/MAGS +since +sincere/IY +sincereness/M +sincerer +sincerest +sincerity/MIS +sine/SM +sinecure/MS +sinecurist/M +sinew/SGMD +sinewy +sinful/YP +sinfulness/SM +sing/BGJZYDR +singe/S +singeing +singer/M +singing/Y +single/PSDG +singlehanded/Y +singleness/SM +singlet/SM +singleton/SM +singletree/SM +singsong/GSMD +singular/SY +singularity/SM +singularization/M +sinister/YP +sinisterness/M +sinistral/Y +sink/GZSDRB +sinkable/U +sinker/M +sinkhole/SM +sinking/M +sinless/YP +sinlessness/M +sinned +sinner/MS +sinning +sinter/DM +sinuosity/MS +sinuous/PY +sinuousities +sinuousness/M +sinus/MS +sinusitis/SM +sinusoid/MS +sinusoidal/Y +sip/S +siphon/DMSG +siphons/U +sipped +sipper/SM +sipping +sir/XGMNDS +sire/MS +sired/C +siren/M +sires/C +siring/C +sirloin/MS +sirocco/MS +sirred +sirring +sirup's +sis/S +sisal/MS +sissified +sissy/TRSM +sister's/A +sister/GDYMS +sisterhood/MS +sisterliness/MS +sisterly/P +sit/AG +sitar/SM +sitarist/SM +sitcom/SM +site/DSJM +sits +sitter/MS +sitting/SM +situ/S +situate/GNSDX +situation/M +situational/Y +situationist +situs/M +six/MRSH +sixfold +sixgun +sixpence/MS +sixpenny +sixshooter +sixteen/HRSM +sixteenths +sixth/Y +sixths +sixtieths +sixty/SMH +sizable/P +sizableness/M +size/GJDRSBMZ +sized/UA +sizer/M +sizes/A +sizing/M +sizzle/RSDG +sizzler/M +ska/S +skat/JMDRGZ +skate/SM +skateboard/SJGZMDR +skater/M +skedaddle/GSD +skeet/RMS +skein/MDGS +skeletal/Y +skeleton/MS +skeptic/SM +skeptical/Y +skepticism/MS +sketch/MRSDZG +sketchbook/SM +sketcher/M +sketchily +sketchiness/MS +sketchpad +sketchy/PRT +skew/DRSPGZ +skewer/GDM +skewing/M +skewness/M +ski/MNJSG +skid/S +skidded +skidding +skiff/GMDS +skiing/M +skilfully +skill/DMSG +skilled/U +skillet/MS +skillful/YUP +skillfulness/MU +skillfulnesses +skilling/M +skim/SM +skimmed +skimmer/MS +skimming/SM +skimp/GDS +skimpily +skimpiness/MS +skimpy/PRT +skin/SM +skincare +skindive/G +skinflint/MS +skinhead/SM +skinless +skinned +skinner/SM +skinniness/MS +skinning +skinny/TRSP +skintight +skip/S +skipped +skipper/SGDM +skipping +skirmish/RSDMZG +skirmisher/M +skirt/RDMGS +skirter/M +skirting/M +skit/GSMD +skitter/SDG +skittish/YP +skittishness/SM +skittle/SM +skivvy/GSDM +skoal/SDG +skulduggery/MS +skulk/SRDGZ +skulker/M +skull/SDM +skullcap/MS +skullduggery's +skunk/GMDS +sky/MDRSGZ +skycap/MS +skydiver/SM +skydiving/MS +skyhook +skyjack/ZSGRDJ +skyjacker/M +skylark/SRDMG +skylarker/M +skylight/MS +skyline/MS +skyrocket/GDMS +skyscrape/RZ +skyscraper/M +skyward/S +skywave +skyway/M +skywriter/MS +skywriting/MS +slab/MS +slabbed +slabbing +slack/SPGTZXYRDN +slacken/DG +slacker/M +slackness/MS +slag/MS +slagged +slagging +slain +slake/DSG +slaked/U +slalom/SGMD +slam/S +slammed +slammer/S +slamming +slander/MDRZSG +slanderous/PY +slanderousness/M +slang/SMGD +slangy/TR +slant/SDG +slanting/Y +slantwise +slap/MS +slapdash/S +slaphappy/TR +slapped +slapper +slapping +slapstick/MS +slash/GZRSD +slashing/Y +slat/MDRSGZ +slate/SM +slater/M +slather/SMDG +slating/M +slatted +slattern/MYS +slatting +slaughter/SJMRDGZ +slaughterer/M +slaughterhouse/SM +slave/DSRGZM +slaveholder/SM +slaver/GDM +slavery/SM +slavish/YP +slavishness/SM +slaw/MS +slay/RGZS +sleaze/S +sleazily +sleaziness/SM +sleazy/RTP +sled/SM +sledded +sledder/S +sledding +sledge/SDGM +sledgehammer/MDGS +sleek/PYRDGTS +sleekness/S +sleep/RMGZS +sleeper/M +sleepily +sleepiness/SM +sleeping/M +sleepless/YP +sleeplessness/SM +sleepover/S +sleepwalk/JGRDZS +sleepwalker/M +sleepwear/M +sleepy/PTR +sleepyhead/MS +sleet/DMSG +sleety/TR +sleeve/SDGM +sleeveless +sleeving/M +sleigh/GMD +sleighs +sleight/SM +sleken/DG +slender/RYTP +slenderize/DSG +slenderness/MS +slept +sleuth/GMD +sleuths +slew/DGS +slice/DSRGZM +sliced/U +slicer/M +slick/PSYRDGTZ +slicker/M +slickness/MS +slid/GZDR +slide/S +slider/M +slight/DRYPSTG +slighter/M +slighting/Y +slightness/S +slim/SPGYD +slime/SM +sliminess/S +slimline +slimmed +slimmer/S +slimmest +slimming/S +slimness/S +slimy/PTR +sling/GMRS +slings/U +slingshot/MS +slink/GS +slinky/RT +slip/SM +slipcase/MS +slipcover/GMDS +slipknot/SM +slippage/SM +slipped +slipper/GSMD +slipperiness/S +slippery/PRT +slipping +slipshod +slipstream/MDGS +slipway/SM +slit/SM +slither/DSG +slithery +slitted +slitter/S +slitting +sliver/GSDM +slivery +slob/MS +slobber/SDG +slobbery +sloe/MS +slog/S +slogan/MS +sloganeer/MG +slogged +slogging +sloop/SM +slop/DRSGZ +slope/S +sloped/U +slopped +sloppily +sloppiness/SM +slopping +sloppy/RTP +slosh/GSDM +slot/MS +sloth/GDM +slothful/PY +slothfulness/MS +sloths +slotted +slotting +slouch/DRSZG +sloucher/M +slouchy/RT +slough/GMD +sloughs +sloven/YMS +slovenliness/SM +slovenly/TRP +slow/PGTYDRS +slowcoaches +slowdown/MS +slowish +slowness/MS +slowpoke/MS +sludge/SDGM +sludgy/TR +slue/MGDS +slug/MS +sluggard/MS +slugged +slugger/SM +slugging +sluggish/YP +sluggishness/SM +sluice/SDGM +slum/MS +slumber/MDRGS +slumberer/M +slumberous +slumlord/MS +slummed +slummer +slumming +slummy/TR +slump/DSG +slung/U +slunk +slur/MS +slurp/GSD +slurred +slurried/M +slurring +slurry/MGDS +slurrying/M +slush/SDMG +slushiness/SM +slushy/RTP +slut/MS +sluttish +slutty/TR +sly/RTY +slyness/MS +smack/SMRDGZ +smacker/M +small/SGTRDP +smallholders +smallholding/MS +smallish +smallness/S +smallpox/SM +smalltalk +smalltime +smarmy/RT +smart/YRDNSGTXP +smarten/GD +smartness/S +smartypants +smash/GZRSD +smasher/M +smashing/Y +smashup/S +smattering/SM +smear/GRDS +smearer/M +smeary/TR +smell/SBRDG +smeller/M +smelliness/MS +smelly/TRP +smelt/SRDGZ +smelter/M +smidgen/MS +smilax/MS +smile/GMDSR +smiley/M +smilies +smiling/UY +smirch/SDG +smirk/GSMD +smite/GSR +smiter/M +smith/DMG +smithereens +smiths +smithy/SM +smitten +smock/SGMDJ +smocking/M +smog/SM +smoggy/TR +smoke/GZMDSRBJ +smokehouse/MS +smokeless +smoker/M +smokescreen/S +smokestack/MS +smokiness/S +smoking/M +smoky/RSPT +smolder/SGD +smoldering/Y +smooch/SDG +smooth/TZGPRDNY +smoothen/DG +smoother/M +smoothie/SM +smoothness/MS +smooths +smote +smother/GSD +smudge/GSD +smudginess/M +smudgy/TRP +smug/YSP +smugged +smugger +smuggest +smugging +smuggle/JZGSRD +smuggler/M +smugness/MS +smut/SM +smutted +smuttiness/SM +smutting +smutty/TRP +smörgåsbord/SM +snack/SGMD +snaffle/GDSM +snafu/DMSG +snag/MS +snagged +snagging +snail/GSDM +snake/DSGM +snakebird/M +snakebite/MS +snakelike +snakeroot/M +snaky/TR +snap/US +snapback/M +snapdragon/MS +snapped/U +snapper/SM +snappily +snappiness/SM +snapping/U +snappish/PY +snappishness/SM +snappy/PTR +snapshot/MS +snapshotted +snapshotting +snare/DSRGM +snarer/M +snarf/JSGD +snarl/UGSD +snarler/M +snarling/Y +snarly/RT +snatch/DRSZG +snatcher/M +snazzily +snazzy/TR +sneak/RDGZS +sneaker/MD +sneakily +sneakiness/SM +sneaking/Y +sneaky/PRT +sneer/GMRDJS +sneerer/M +sneering/Y +sneeze/SRDG +snick/MRZ +snicker/GMRD +snide/YTSRP +snideness/M +sniff/GZSRD +sniffer/M +sniffle/GDRS +sniffler/M +sniffles/M +snifter/MDSG +snigger's +snip/SGDRZ +snipe/SM +sniper/M +snipped +snipper/SM +snippet/SM +snipping +snippy/RT +snit/SM +snitch/GDS +snivel/JSZGDR +sniveler/M +snob/MS +snobbery/SM +snobbish/YP +snobbishness/S +snobby/RT +snood/SGDM +snook/SMRZ +snooker/GMD +snoop/SRDGZ +snooper/M +snoopy/RT +snoot/SDMG +snootily +snootiness/MS +snooty/TRP +snooze/GSD +snore/DSRGZ +snorkel/ZGSRDM +snort/GSZRD +snorter/M +snot/MS +snotted +snottily +snottiness/SM +snotting +snotty/TRP +snout/SGDM +snow/GDMS +snowball/SDMG +snowbank/SM +snowbird/SM +snowblower/S +snowboard/GZDRJS +snowbound +snowcapped +snowdrift/MS +snowdrop/MS +snowfall/MS +snowfield/MS +snowflake/MS +snowily +snowiness/MS +snowman/M +snowmen +snowmobile/GMDRS +snowplough/M +snowploughs +snowplow/SMGD +snowshed +snowshoe/MRS +snowshoeing +snowshoer/M +snowstorm/MS +snowsuit/S +snowy/RTP +snub/SP +snubbed +snubber +snubbing +snuff/GZSYRD +snuffbox/SM +snuffer/M +snuffle/GDSR +snuffler/M +snuffly/RT +snug/SYP +snugged +snugger +snuggest +snugging +snuggle/GDS +snuggly +snugness/MS +so +soak/GDRSJ +soaker/M +soap/MDRGS +soapbox/DSMG +soapiness/S +soapstone/MS +soapsud/S +soapy/RPT +soar/DRJSG +soarer/M +soaring/Y +sob/SZR +sobbed +sobbing/Y +sober/PGTYRD +soberer/M +soberness/SM +sobriety/SIM +sobriquet/MS +soc/S +soccer/MS +sociabilities +sociability/IM +sociable/S +sociably/IU +social/SY +socialism/SM +socialist/SM +socialistic +socialite/SM +sociality/M +socialization/SM +socialize/RSDG +socialized/U +socializer/M +socially/U +societal/Y +society/MS +socio +sociobiology/M +sociocultural/Y +sociodemographic +socioeconomic/S +socioeconomically +sociolinguistics/M +sociological/MY +sociologist/SM +sociology/SM +sociometric +sociometry/M +sociopath/M +sociopaths +sock/GDMS +socket/SMDG +sod/MS +soda/SM +sodded +sodden/DYPSG +soddenness/M +sodding +sodium/MS +sodomite/MS +sodomize/GDS +sodomy/SM +soever +sofa/SM +soft/SPXTYNR +softball/MS +softbound +soften/ZGRD +softener/M +softhearted +softie's +softness/MS +software/MS +softwood/SM +softy/SM +soggily +sogginess/S +soggy/RPT +soigné +soil/SGMD +soiled/U +soirée/SM +sojourn/RDZGSM +sol/GSMDR +solace/GMSRD +solacer/M +solar/S +solaria +solarium/M +sold/RU +solder/RDMSZG +soldier/MDYSG +soldiery/MS +sole/YSP +solecism/MS +soled/FA +solemn/PTRY +solemness +solemnify/GSD +solemnity/MS +solemnization/SM +solemnize/GSD +solemnness/SM +solenoid/MS +soler/F +soles/IFA +solicit/SDG +solicitation/S +solicited/U +solicitor/MS +solicitous/YP +solicitousness/S +solicitude/MS +solid/STYRP +solidarity/MS +solidi +solidification/M +solidify/NXSDG +solidity/S +solidness/SM +solidus/M +soliloquies +soliloquize/DSG +soliloquy/M +soling/NM +solipsism/MS +solipsist/S +solitaire/SM +solitary/SP +solitude/SM +solo/DMSG +soloist/SM +solstice/SM +solubility/IMS +soluble/SI +solute's +solute/ENAXS +solution/AME +solvable/UI +solvating +solve/ABSRDZG +solved/EU +solvency/IMS +solvent's +solvent/IS +solvently +solver/MEA +solves/E +solving/E +soma/M +somatic +somber/PY +somberness/SM +sombre +sombrero/SM +some/Z +somebody'll +somebody/SM +someday +somehow +someone'll +someone/SM +someplace/M +somersault/DSGM +somerset/S +somersetted +somersetting +something/S +sometime/S +someway/S +somewhat/S +somewhere/S +sommelier/SM +somnambulism/SM +somnambulist/SM +somnolence/MS +somnolent/Y +son/SMY +sonar/SM +sonata/MS +sonatina/SM +song/MS +songbag +songbird/SM +songbook/S +songfest/MS +songful/YP +songfulness/M +songster/MS +songstress/SM +songwriter/SM +songwriting +sonic/S +sonnet/MDSG +sonny/SM +sonority/S +sonorous/PY +sonorousness/SM +sonuvabitch +soon/TR +soonish +soot/MGDS +sooth/GZTYSRDMJ +soothe +soother/M +soothing/YP +soothingness/M +sooths +soothsay/JGZR +soothsayer/M +sooty/RT +sop/SM +sophism/SM +sophist/RMS +sophister/M +sophistic/S +sophistical +sophisticate/XNGDS +sophisticated/U +sophisticatedly +sophistication/MU +sophistry/SM +sophomore/SM +sophomoric +soporific/SM +soporifically +sopped +sopping/S +soppy/RT +soprano/SM +sorbet/SM +sorcerer/MS +sorceress/S +sorcery/MS +sordid/PY +sordidness/SM +sore/PYTGDRS +sorehead/SM +soreness/S +sorghum/MS +sorority/MS +sorrel/SM +sorrily +sorriness/SM +sorrow/GRDMS +sorrower/M +sorrowful/YP +sorrowfulness/SM +sorry/PTSR +sort's +sort/FSAGD +sorta +sortable +sorted/U +sorter/MS +sortie/MSD +sortieing +sos +sot/SM +sottish +sou'wester +sou/SMH +soubriquet's +soufflé/MS +sough/DG +soughs +sought/U +soul/MDS +soulful/YP +soulfulness/MS +soulless/Y +sound's +sound/AUD +soundboard/MS +sounder's +sounder/U +sounders +soundest +sounding's +sounding/AY +soundings +soundless/Y +soundly/U +soundness/UMS +soundproof/GSD +soundproofing/M +sounds/A +soundtrack/MS +soup/GMDS +soupy/RT +soupçon/SM +sour/TYDRPSG +source/ASDMG +sourceless +sourdough +sourdoughs +sourish +sourness/MS +sourpuss/MS +sous/DSG +sousaphone/SM +souse +south/RDMG +southbound +southeast/RZMS +southeaster/YM +southeastern +southeastward/S +souther/MY +southerly/S +southern/PZSYR +southerner/M +southernisms +southernmost +southing/M +southland/M +southpaw/MS +souths +southward/S +southwest/RMSZ +southwester/YM +southwestern +southwestward/S +souvenir/SM +sovereign/YMS +sovereignty/MS +soviet/MS +sow/ADGS +sowbelly/M +sowens/M +sower/DS +sown/A +sox's +soy/MS +soybean/MS +spa/MS +space/DSRGZMJ +spacecraft/MS +spaceflight/S +spaceman/M +spacemen +spaceport/SM +spacer/M +spaceship/MS +spacesuit/MS +spacewalk/GSMD +spacewoman +spacewomen +spacey +spacial +spacier +spaciest +spaciness +spacing/M +spacious/PY +spaciousness/SM +spade/DSRGM +spadeful/SM +spader/M +spadework/SM +spadices +spadix/M +spaghetti/SM +spake +span/MS +spandex/MS +spandrels +spangle/GMDS +spaniel/SM +spanielled +spanielling +spank/SRDJG +spanker/M +spanking/M +spanned/U +spanner/SM +spanning +spar/DRMGTS +spare/PSY +spareness/MS +sparer/M +spareribs +sparing/UY +spark/SGMRD +sparker/M +sparkle/DRSGZ +sparkler/M +sparky/RT +sparling/SM +sparred +sparrer +sparring/U +sparrow/MS +spars/TR +sparse/YP +sparseness/S +sparsity/S +spartan +spasm/GSDM +spasmodic +spasmodically +spastic/S +spat/MS +spate/SM +spathe/MS +spatial/Y +spatiality/M +spatted +spatter/DGS +spatterdock/M +spatting +spatula/SM +spavin/DMS +spawn/MRDSG +spawner/M +spay/DGS +speak/RBGZJS +speakable/U +speakeasy/SM +speaker/M +speakership/M +speaking/U +spear/MRDGS +spearer/M +spearfish/SDMG +spearhead/GSDM +spearmint/MS +spec'd +spec'ing +spec/SM +special/SRYP +specialism/MS +specialist/MS +specialization/SM +specialize/GZDSR +specialized/U +specializing/U +specialty/MS +specie/MS +specif +specifiability +specifiable +specifiably +specific/SP +specifically +specification/SM +specificity/S +specified/U +specifier/SM +specifies +specify/AD +specifying +specimen/SM +specious/YP +speciousness/SM +speck/GMDS +speckle/GMDS +spectacle/MSD +spectacular/SY +spectator/SM +specter's/A +specter/DMS +spectra/M +spectral/YP +spectralness/M +spectrogram/MS +spectrograph/M +spectrographically +spectrography/M +spectrometer/MS +spectrometric +spectrometry/M +spectrophotometer/SM +spectrophotometric +spectrophotometry/M +spectroscope/SM +spectroscopic +spectroscopically +spectroscopy/SM +spectrum/M +specular/Y +specularity +speculate/VNGSDX +speculation/M +speculative/Y +speculator/SM +sped +speech/GMDS +speechless/YP +speechlessness/SM +speed/RMJGZS +speedboat/GSRM +speedboating/M +speeder/M +speedily +speediness/SM +speedometer/MS +speedster/SM +speedup/MS +speedway/SM +speedwell/MS +speedy/PTR +speer/M +speleological +speleologist/S +speleology/MS +spell/RDSJGZ +spellbind/SRGZ +spellbinder/M +spellbound +spelldown/MS +spelled/A +speller/M +spelling/M +spells/A +spelunker/MS +spelunking/S +spend/SBJRGZ +spender/M +spendthrift/MS +spent/U +sperm/SM +spermatophyte/M +spermatozoa +spermatozoon/M +spermicidal +spermicide/MS +spew/DRGZJS +spewer/M +sphagnum/SM +sphere/SDGM +spheric/S +spherical/Y +spherics/M +spheroid/SM +spheroidal/Y +spherule/MS +sphincter/SM +sphinx/MS +spic/DGM +spice/SM +spicebush/M +spicily +spiciness/SM +spicule/MS +spicy/PTR +spider/SM +spiderweb/S +spiderwort/M +spidery/TR +spiel/GDMS +spier/M +spiffy/TDRSG +spigot/MS +spike/GMDSR +spiker/M +spikiness/SM +spiky/PTR +spill/RDSG +spillage/SM +spillover/SM +spillway/SM +spin/S +spinach/MS +spinal/YS +spindle/JGMDRS +spindly/RT +spine/MS +spineless/YP +spinelessness/M +spinet/SM +spininess/M +spinnability/M +spinnaker/SM +spinner/SM +spinneret/MS +spinning/SM +spinster/MS +spinsterhood/SM +spinsterish +spiny/PRT +spiracle/SM +spiraea's +spiral/YDSG +spire's +spire/AIDSGF +spirea/MS +spirit/GMDS +spirited/PY +spiritedness/M +spiritless +spirits/I +spiritual/SYP +spiritualism/SM +spiritualist/SM +spiritualistic +spirituality/SM +spirituous +spirochete/SM +spiry/TR +spit/SGD +spitball/SM +spite's/A +spite/CSDAG +spiteful/PY +spitefuller +spitefullest +spitefulness/MS +spitfire/SM +spitted +spitting +spittle/SM +spittoon/SM +splash/GZDRS +splashdown/MS +splasher/M +splashily +splashiness/MS +splashy/RTP +splat/SM +splatted +splatter/DSG +splatting +splay/SDG +splayfeet +splayfoot/MD +spleen/SM +splendid/YRPT +splendidness/M +splendor/SM +splendorous +splenetic/S +splice/RSDGZJ +splicer/M +spline/MSD +splint/SGZMDR +splinter/GMD +splintery +split/SM +splits/M +splittable +splitter/MS +splitting/S +splodge/SM +splotch/MSDG +splotchy/RT +splurge/GMDS +splutter/RDSG +splutterer/M +spoil/CSZGDR +spoilables +spoilage/SM +spoiled/U +spoiler/MC +spoilsport/SM +spoke/DSG +spoken/U +spokeshave/MS +spokesman/M +spokesmen +spokespeople +spokesperson/S +spokeswoman/M +spokeswomen +spoliation/MCS +sponge/GMZRSD +spongecake +sponger/M +sponginess/S +spongy/TRP +sponsor/DGMS +sponsorship/S +spontaneity/SM +spontaneous/PY +spontaneousness/M +spoof/SMDG +spook/SMDG +spookiness/MS +spooky/PRT +spool/SRDMGZ +spoon/GSMD +spoonbill/SM +spoonerism/SM +spoonful/MS +spoor/GSMD +sporadic/Y +sporadically +spore/DSGM +sporran/MS +sport/VGSRDM +sportiness/SM +sporting/Y +sportive/PY +sportiveness/M +sportscast/RSGZM +sportsman/MY +sportsmanlike/U +sportsmanship/MS +sportsmen +sportswear/M +sportswoman/M +sportswomen +sportswriter/S +sporty/PRT +spot/MSC +spotless/YP +spotlessness/MS +spotlight/GDMS +spotlit +spotted/U +spotter/MS +spottily +spottiness/SM +spotting/M +spotty/RTP +spousal/MS +spouse/GMSD +spout/SGRD +spouter/M +sprain/SGD +sprang/S +sprat/SM +sprawl/GSD +spray/GZSRDM +sprayed/UA +sprayer/M +sprays/A +spread/RSJGZB +spreadeagled +spreader/M +spreadsheet/S +spree/MDS +spreeing +sprig/MS +sprigged +sprigging +sprightliness/MS +sprightly/PRT +spring/SGZR +springboard/MS +springbok/MS +springeing +springer/M +springily +springiness/SM +springing/M +springlike +springtime/MS +springy/TRP +sprinkle/DRSJZG +sprinkler/DM +sprinkling/M +sprint/SGZMDR +sprite/SM +spritz/GZDSR +sprocket/DMGS +sprocketed/U +sprout/GSD +spruce/GMTYRSDP +spruceness/SM +sprue/M +sprung/U +spry/TRY +spryness/S +spud/MS +spudded +spudding +spume/DSGM +spumone's +spumoni/S +spumy/TR +spun +spunk/GSMD +spunky/SRT +spur/MS +spurge/MS +spurious/PY +spuriousness/SM +spurn/RDSG +spurred +spurring +spurt/SGD +sputa +sputnik/MS +sputter/DRGS +sputum/M +spy/DRSGM +spyglass/MS +sq +sqq +sqrt +squab/SM +squabbed +squabber +squabbest +squabbing +squabble/ZGDRS +squabbler/M +squad/SM +squadded +squadding +squadron/MDGS +squalid/PRYT +squalidness/SM +squall/GMRDS +squaller/M +squally/RT +squalor/SM +squamous/Y +squander/GSRD +square/GMTYRSDP +squareness/SM +squarer/M +squarish +squash/GSRD +squashiness/M +squashy/RTP +squat/SPY +squatness/MS +squatted +squatter/SMDG +squattest +squatting +squaw/SM +squawk/GRDMZS +squawker/M +squeak/RDMGZS +squeaker/M +squeakily +squeakiness/S +squeaky/RPT +squeal/MRDSGZ +squealer/M +squeamish/YP +squeamishness/SM +squeegee/DSM +squeegeeing +squeeze/GZSRDB +squeezer/M +squelch/GDRS +squelcher/M +squelchy/RT +squib/SM +squibbed +squibbing +squid/SM +squidded +squidding +squiggle/MGDS +squiggly/RT +squint/GTSRD +squinter/M +squinting/Y +squire/SDGM +squirehood +squirm/SGD +squirmy/TR +squirrel/SGYDM +squirt/GSRD +squirter/M +squish/GSD +squishy/RTP +ssh +st/GBJ +stab/YS +stabbed +stabber/S +stabbing/S +stability/ISM +stabilizability +stabilization's +stabilization/CS +stabilize/CGSD +stabilizer/MS +stable's/F +stable/RSDGMTP +stableman/M +stablemate +stablemen +stableness/UM +stabler/U +stables/F +stablest/U +stabling/M +stably/U +staccato/S +stack's +stack/USDG +stackable +stacker/M +stadia's +stadias +stadium/MS +staff's +staff/ADSG +staffer/MS +staffroom +stag/DRMJSGZ +stage/SM +stagecoach/MS +stagecraft/MS +stagehand/MS +stager/M +stagestruck +stagflation/SM +stagged +stagger/GSJDR +staggerer/M +staggering/Y +staggers/M +stagging +staginess/M +staging/M +stagnancy/SM +stagnant/Y +stagnate/NGDSX +stagnation/M +stagy/PTR +staid/YRTP +staidness/MS +stain/SGRD +stained/U +stainer/M +stainless/YS +stair/MS +staircase/SM +stairway/SM +stairwell/MS +stake/DSGM +stakeholder/S +stakeout/SM +stalactite/SM +stalag/M +stalagmite/SM +stale/PGYTDSR +stalemate/SDMG +staleness/MS +stalk/MRDSGZJ +stalker/M +stall/DMSJG +stalled/I +stallholders +stallion/SM +stalls/I +stalwart/PYS +stalwartness/M +stamen/MS +stamina/SM +staminate +stammer/DRSZG +stammerer/M +stammering/Y +stamp/RDSGZJ +stamped/U +stampede/MGDRS +stampeder/M +stamper/M +stance/MIS +stanch/GDRST +stancher/M +stanchion/SGMD +stand/SJGZR +standalone +standard/YMS +standardization/AMS +standardize/GZDSR +standardized/U +standardizer/M +standardizes/A +standby +standbys +standee/MS +standing/M +standoff/SM +standoffish +standout/MS +standpipe/MS +standpoint/SM +standstill/SM +stank/S +stannic +stannous +stanza/MS +staph/M +staphs +staphylococcal +staphylococci +staphylococcus/M +staple/ZRSDGM +stapled/U +stapler/M +star/DRMGZS +starboard/SDMG +starch/MDSG +starchily +starchiness/MS +starchy/TRP +stardom/MS +stardust/MS +stare/S +starfish/SM +stargaze/ZGDRS +staring/U +stark/SPGTYRD +starkness/MS +starless +starlet/MS +starlight/MS +starling/MS +starlit +starred +starring +starry/TR +starship +starstruck +start/ASGDR +starter/MS +startle/GDS +startling/PY +startup/SM +starvation/MS +starve/RSDG +starveling/M +starver/M +stash/GSD +stasis/M +stat/DRSGV +state's/K +state/IGASD +statecraft/MS +stated/U +statehood/MS +statehouse/S +stateless/P +statelessness/MS +stateliness/MS +stately/PRT +statement/MSA +stater/M +stateroom/SM +states/K +stateside +statesman/MY +statesmanlike +statesmanship/SM +statesmen +stateswoman +stateswomen +statewide +static/S +statical/Y +statics/M +station/SZGMDR +stationarity +stationary/S +stationer/M +stationery/MS +stationmaster/M +statistic/MS +statistical/Y +statistician/MS +stator/SM +statuary/SM +statue/MSD +statuesque/YP +statuette/MS +stature/MS +status/SM +statute/SM +statutorily +statutory/P +staunch/PDRSYTG +staunchness/S +stave/DGM +stay/DRGZS +stayer/M +std +stdio +stead/SGDM +steadfast/PY +steadfastness/MS +steadily/U +steadiness's +steadiness/US +steading/M +steady/DRSUTGP +steak/SM +steakhouse/SM +steal/SRHG +stealer/M +stealing/M +stealth/M +stealthily +stealthiness/MS +stealths +stealthy/PTR +steam/SGZRDMJ +steamboat/MS +steamer/MDG +steamfitter/S +steamfitting/S +steamily +steaminess/SM +steamroll/GZRDS +steamroller/DMG +steamship/SM +steamy/RSTP +steed/SM +steel/SDMGZ +steeliness/SM +steelmaker/M +steelwork/ZSMR +steelworker/M +steely/TPRS +steelyard/MS +steep/SYRNDPGTX +steepen/GD +steeper/M +steeple/MS +steeplebush/M +steeplechase/GMSD +steeplejack/MS +steepness/S +steer/SGBRDJ +steerage/MS +steerer/M +steersman/M +steersmen +steeves +stegosauri +stegosaurus/S +stein/SGZMRD +stellar +stellated +stem/MS +stemless +stemmed/U +stemming +stemware/MS +stench/GMDS +stencil/GDRMSZ +stenciler/M +stencillings +steno/SM +stenographer/SM +stenographic +stenography/SM +stenotype/M +stentorian +step/MIS +stepbrother/MS +stepchild/M +stepchildren +stepdaughter/MS +stepfather/SM +stepladder/SM +stepmother/SM +stepparent/SM +steppe/RSDGMZ +stepper/M +steppingstone/S +stepsister/SM +stepson/SM +stepwise +stereo/GSDM +stereographic +stereography/M +stereophonic +stereoscope/MS +stereoscopic +stereoscopically +stereoscopy/M +stereotype/GMZDRS +stereotypic +stereotypical/Y +sterile +sterility/SM +sterilization/SM +sterilize/RSDGZ +sterilized/U +sterilizes/A +sterling/MPYS +sterlingness/M +stern/SYRDPGT +sternal +sternness/S +sternum/SM +steroid/MS +steroidal +stertorous +stet/MS +stethoscope/SM +stetson/MS +stetted +stetting +stevedore/GMSD +stew/GDMS +steward/DMSG +stewardess/SM +stewardship/MS +stick/MRDSGZ +sticker/M +stickily +stickiness/SM +stickle/GZDR +stickleback/MS +stickler/M +stickpin/SM +stickup/SM +sticky/GPTDRS +stiff/GTXPSYRND +stiffen/JZRDG +stiffness/MS +stifle/GJRSD +stifler/M +stifling/Y +stigma/MS +stigmata +stigmatic/S +stigmatization's +stigmatization/C +stigmatizations +stigmatize/DSG +stigmatized/U +stile/GMDS +stiletto/MDSG +still/RDIGS +stillbirth/M +stillbirths +stillborn/S +stiller/MI +stillest +stillness/MS +stilt/GDMS +stilted/PY +stimulant/MS +stimulate/SDVGNX +stimulated/U +stimulation/M +stimulative/S +stimulator/M +stimulatory +stimuli/M +stimulus/MS +sting/GZR +stinger/M +stingily +stinginess/MS +stinging/Y +stingray/MS +stingy/RTP +stink/GZRJS +stinkbug/S +stinker/M +stinking/Y +stinkpot/M +stinky/RT +stint/JGRDMS +stinter/M +stinting/U +stipend/MS +stipendiary +stipple/JDRSG +stippler/M +stipulate/XNGSD +stipulation/M +stir/S +stirred/U +stirrer/SM +stirring/YS +stirrup/SM +stitch's +stitch/ASDG +stitcher/M +stitchery/S +stitching/MS +stoat/SM +stochastic +stochastically +stochasticity +stock's +stock/SGAD +stockade/SDMG +stockbreeder/SM +stockbroker/MS +stockbroking/S +stocker/SM +stockholder/SM +stockily +stockiness/SM +stockinet's +stockinette/S +stocking/MDS +stockist/MS +stockpile/GRSD +stockpiler/M +stockpot/MS +stockroom/MS +stocktaking/MS +stocky/PRT +stockyard/SM +stodge/M +stodgily +stodginess/S +stodgy/TRP +stogy/SM +stoic/MS +stoical/Y +stoichiometric +stoichiometry/M +stoicism/SM +stoke/DSRGZ +stoker/M +stokes/M +stole/MDS +stolen +stolid/PTYR +stolidity/S +stolidness/S +stolon/SM +stomach/RSDMZG +stomachache/MS +stomacher/M +stomachs +stomp/DSG +stone/DSRGM +stonecutter/SM +stoneless +stonemason/MS +stoner/M +stonewall/GDS +stoneware/MS +stonewashed +stonework/SM +stonewort/M +stonily +stoniness/MS +stony/TPR +stood +stooge/SDGM +stool/SDMG +stoop/SDG +stop's +stop/US +stopcock/MS +stopgap/SM +stoplight/SM +stopover/MS +stoppable/U +stoppage/MS +stopped/U +stopper/GMDS +stopping/M +stopple/GDSM +stops/M +stopwatch/SM +storage/SM +store's +store/ADSRG +storefront/SM +storehouse/MS +storekeep/ZR +storekeeper/M +storeroom/SM +stork/SM +storm/SRDMGZ +stormbound +stormer/M +stormily +storminess/S +stormtroopers +stormy/PTR +story/GSDM +storyboard/MDSG +storybook/MS +storyline +storyteller/SM +storytelling/MS +stoup/SM +stout/STYRNP +stouten/DG +stouthearted +stoutness/MS +stove/DSRGM +stovepipe/SM +stover/M +stow/GDS +stowage/SM +stowaway/MS +straddle/ZDRSG +straddler/M +strafe/GRSD +strafer/M +straggle/GDRSZ +straggly/RT +straight/RNDYSTXGP +straightaway/S +straightedge/MS +straighten/ZGDR +straightener/M +straightforward/SYP +straightforwardness/MS +straightjacket's +straightness/MS +straightway/S +strain/ASGZDR +strained/UF +strainer/MA +straining/F +strains/F +strait/XTPSMGYDNR +straiten/DG +straitjacket/GDMS +straitlaced +straitness/M +strand/SDRG +stranded/P +strange/PYZTR +strangeness/SM +stranger/GMD +strangle/JDRSZG +stranglehold/MS +strangles/M +strangulate/NGSDX +strangulation/M +strap's +strap/US +strapless/S +strapped/U +strapping/S +strata/MS +stratagem/SM +strategic/S +strategical/Y +strategics/M +strategist/SM +strategy/SM +strati +stratification/M +stratified/U +stratify/NSDGX +stratigraphic +stratigraphical +stratigraphy/M +stratosphere/SM +stratospheric +stratospherically +stratum/M +stratus/M +straw/SMDG +strawberry/SM +strawflower/SM +stray/GSRDM +strayer/M +streak/DRMSGZ +streaker/M +streaky/TR +stream/GZSMDR +streamed/U +streamer/M +streaming/M +streamline/SRDGM +street/SMZ +streetcar/MS +streetlight/SM +streetwalker/MS +streetwise +strength/NMX +strengthen/AGDS +strengthener/MS +strengths +strenuous/PY +strenuousness/SM +strep/MS +streptococcal +streptococci +streptococcus/M +streptomycin/SM +stress/DSMG +stressed/U +stressful/YP +stretch/BDRSZG +stretchability/M +stretchable/U +stretcher/DMG +stretchy/TRP +strew/GDHS +strewn +stria/M +striae +striate/DSXGN +striated/U +striation/M +stricken +strict/AF +stricter +strictest +strictly +strictness/S +stricture/SM +stridden +stride/RSGM +stridency/S +strident/Y +strider/M +strife/SM +strike/RSGZJ +strikebreak/ZGR +strikebreaker/M +strikebreaking/M +strikeout/S +striker/M +striking/Y +string's +string/SAG +stringed +stringency/S +stringent/Y +stringer/MS +stringiness/SM +stringing/M +stringy/RTP +strip/GRDMS +stripe/SM +striper/M +stripling/M +stripped/U +stripper/MS +stripping +striptease/SRDGZM +stripteaser/M +stripy/RT +strive/JRSG +striven +striver/M +strobe/SDGM +stroboscope/SM +stroboscopic +strode +stroke/ZRSDGM +stroking/M +stroll/GZSDR +stroller/M +strong/YRT +strongbow +strongbox/MS +stronghold/SM +strongish +strongman/M +strongmen +strongroom/MS +strontium/SM +strop/SM +strophe/MS +strophic +stropped +stropping +strove +struck +structural/Y +structuralism/M +structuralist/SM +structure/SRDMG +structured/AU +structureless +structures/A +structuring/A +strudel/MS +struggle/GDRS +struggler/M +strum/S +strummed +strumming +strumpet/GSDM +strung/UA +strut/S +strutted +strutter/M +strutting +strychnine/MS +stub/MS +stubbed/M +stubbing +stubble/SM +stubbly/RT +stubborn/SGTYRDP +stubbornness/SM +stubby/SRT +stucco/GDM +stuccoes +stuck/U +stud/MS +studbook/SM +studded +studding/SM +student/SM +studentship/MS +studied/PY +studiedness/M +studier/SM +studio/MS +studious/PY +studiousness/SM +study/AGDS +stuff/JGSRD +stuffily +stuffiness/SM +stuffing/M +stuffy/TRP +stultify/NXGSD +stumble/GZDSR +stumbling/Y +stump/RDMSG +stumpage/M +stumper/M +stumpy/RT +stun/S +stung +stunk +stunned +stunner/M +stunning/Y +stunt/GSDM +stunted/P +stupefaction/SM +stupefy/DSG +stupendous/PY +stupendousness/M +stupid/PTYRS +stupidity/SM +stupidness/M +stupor/MS +sturdily +sturdiness/SM +sturdy/SRPT +sturgeon/SM +stutter/DRSZG +sty/DSGM +style/GZMDSR +styled/A +styles/A +styli +styling/A +stylish/PY +stylishness/S +stylist/MS +stylistic/S +stylistically +stylites +stylization/MS +stylize/DSG +stylos +stylus/SM +stymie/SD +stymieing +stymy's +styptic/S +styrene/MS +suable +suasion/EMS +suave/PRYT +suaveness/S +suavity/SM +sub/MS +subaltern/SM +subarctic/S +subareas +subassembly/M +subatomic/S +subbasement/SM +subbed +subbing +subbranch/S +subcaste/M +subcategorizing +subcategory/SM +subchain +subclass/MS +subclassifications +subclauses +subcommand/S +subcommittee/SM +subcompact/S +subcomponent/MS +subcomputation/MS +subconcept +subconscious/PSY +subconsciousness/SM +subconstituent +subcontinent/MS +subcontinental +subcontract/SMDG +subcontractor/SM +subcultural +subculture/GMDS +subcutaneous/Y +subdirectory/S +subdistrict/M +subdivide/SRDG +subdivision/SM +subdue/GRSD +subdued/Y +subduer/M +subexpression/MS +subfamily/SM +subfield/MS +subfile/SM +subfreezing +subgoal/SM +subgraph +subgraphs +subgroup/SGM +subharmonic/S +subhead/MGJS +subheading/M +subhuman/S +subindex/M +subinterval/MS +subj +subject/GVDMS +subjection/SM +subjective/PSY +subjectiveness/M +subjectivist/S +subjectivity/SM +subjoin/DSG +subjugate/NGXSD +subjugation/M +subjunctive/S +sublayer +sublease/DSMG +sublet/S +subletting +sublimate/GNSDX +sublimation/M +sublime/GRSDTYP +sublimeness/M +sublimer/M +subliminal/Y +sublimity/SM +sublist/SM +subliterary +sublunary +submachine +submarginal +submarine/MZGSRD +submariner/M +submerge/DSG +submergence/SM +submerse/XNGDS +submersible/S +submersion/M +submicroscopic +submission/SAM +submissive/PY +submissiveness/MS +submit/SA +submittable +submittal +submitted/A +submitter/S +submitting/A +submode/S +submodule/MS +subnational +subnet/SM +subnetwork/SM +subnormal/SY +suboptimal +suborbital +suborder/MS +subordinate/YVNGXPSD +subordinately/I +subordinates/I +subordination/IMS +subordinator +suborn/GSD +subornation/SM +subpage +subparagraph/M +subpart/MS +subplot/MS +subpoena/GSDM +subpopulation/MS +subproblem/SM +subprocess/SM +subprofessional/S +subprogram/SM +subproject +subproof/SM +subquestion/MS +subrange/SM +subregion/MS +subregional/Y +subrogation/M +subroutine/SM +subsample/MS +subschema/MS +subscribe/ASDG +subscriber/SM +subscript/SGD +subscripted/U +subscription/MS +subsection/SM +subsegment/SM +subsentence +subsequence/MS +subsequent/SYP +subservience/SM +subservient/SY +subset/MS +subside/SDG +subsidence/MS +subsidiarity +subsidiary/MS +subsidization/MS +subsidize/ZRSDG +subsidized/U +subsidizer/M +subsidy/MS +subsist/SGD +subsistence/MS +subsistent +subsocietal +subsoil/DRMSG +subsonic +subspace/MS +subspecies/M +substance/MS +substandard +substantial/PYS +substantially/IU +substantialness/M +substantiate/VGNSDX +substantiated/U +substantiation/MFS +substantive/PSYM +substantiveness/M +substantivity +substation/MS +substerilization +substitutability +substitute/NGVBXDRS +substituted/U +substitution/M +substitutionary +substitutive/Y +substrata +substrate/MS +substratum/M +substring/S +substructure/SM +subsume/SDG +subsurface/S +subsystem/MS +subtable/S +subtask/SM +subteen/SM +subtenancy/MS +subtenant/SM +subtend/DS +subterfuge/SM +subterranean/SY +subtest +subtext/SM +subtitle/DSMG +subtle/RPT +subtleness/M +subtlety/MS +subtly/U +subtopic/SM +subtotal/GSDM +subtract/SRDZVG +subtracter/M +subtraction/MS +subtrahend/SM +subtree/SM +subtropic/S +subtropical +subtype/MS +subunit/SM +suburb/MS +suburban/S +suburbanite/MS +suburbanization/MS +suburbanized +suburbanizing +suburbia/SM +subvention/MS +subversion/SM +subversive/SPY +subversiveness/MS +subvert/SGDR +subverter/M +subway/MDGS +subzero +succeed/GDRS +succeeder/M +success/MSV +successful/UY +successfulness/M +succession/SM +successive/YP +successiveness/M +successor/MS +successorship +succinct/RYPT +succinctness/SM +succor/SGZRDM +succored/U +succorer/M +succotash/SM +succubus/M +succulence/SM +succulency/MS +succulent/S +succumb/SDG +such +suchlike +suck/GZSDRB +sucker/DMG +suckle/SDJG +suckling/M +sucrose/MS +suction/SMGD +sud/S +sudden/YPS +suddenness/SM +suds/DSRG +sudsy/TR +sue/ZGDRS +sued/DG +suede/SM +suer/M +suet/MS +suety +suffer/SJRDGZ +sufferance/SM +sufferer/M +suffering/M +suffice/GRSD +sufficiency/SIM +sufficient/IY +suffix/GMRSD +suffixation/S +suffixed/U +suffocate/XSDVGN +suffocating/Y +suffragan/S +suffrage/MS +suffragette/MS +suffragist/SM +suffuse/VNGSDX +suffusion/M +sugar/SJGMD +sugarcane/S +sugarcoat/GDS +sugarless +sugarplum/MS +sugary/TR +suggest/DRZGVS +suggester/M +suggestibility/SM +suggestible +suggestion/MS +suggestive/PY +suggestiveness/MS +sugillate +suicidal/Y +suicide/GSDM +suit/MDGZBJS +suitability/SU +suitable/P +suitableness/S +suitably/U +suitcase/MS +suite/SM +suited/U +suiting/M +suitor/SM +sukiyaki/SM +sulfa/S +sulfaquinoxaline +sulfate/MSDG +sulfide/S +sulfite/M +sulfonamide/SM +sulfur/DMSG +sulfuric +sulfurous/YP +sulfurousness/M +sulk/GDS +sulkily +sulkiness/S +sulky/RSPT +sullen/TYRP +sullenness/MS +sullied/U +sully/GSD +sulphate/SM +sulphide/MS +sulphuric +sultan/SM +sultana/SM +sultanate/MS +sultrily +sultriness/SM +sultry/PRT +sum/MRS +sumac/SM +sumach's +sumer/F +summability/M +summable +summand/MS +summarily +summarization/MS +summarize/GSRDZ +summarized/U +summarizer/M +summary/MS +summation/FMS +summed +summer/SGDM +summerhouse/MS +summertime/MS +summery/TR +summing +summit/GMDS +summitry/MS +summon/JSRDGZ +summoner/M +summons/MSDG +sumo/SM +sump/SM +sumptuous/PY +sumptuousness/SM +sun/MS +sunbaked +sunbath/ZRSDG +sunbathe +sunbather/M +sunbathing/M +sunbaths +sunbeam/MS +sunblock/S +sunbonnet/MS +sunburn/GSMD +sunburst/MS +suncream +sundae/MS +sunder/SDG +sundial/MS +sundown/MRDSZG +sundowner/M +sundris +sundry/S +sunfish/SM +sunflower/MS +sung/U +sunglass/MS +sunk/SN +sunlamp/S +sunless +sunlight/MS +sunlit +sunned +sunniness/SM +sunning +sunny/RSTP +sunrise/GMS +sunroof/S +sunscreen/S +sunset/MS +sunsetting +sunshade/MS +sunshine/MS +sunshiny +sunspot/SM +sunstroke/MS +suntan/SM +suntanned +suntanning +sunup/MS +sup/RSZ +super/DG +superabundance/MS +superabundant +superannuate/GNXSD +superannuation/M +superb/YRPT +superbness/M +supercargo/M +supercargoes +supercharge/SRDZG +supercharger/M +supercilious/PY +superciliousness/SM +supercity/S +superclass/M +supercomputer/MS +supercomputing +superconcept +superconducting +superconductivity/SM +superconductor/SM +supercooled +supercooling +supercritical +superdense +superego/SM +supererogation/MS +supererogatory +superficial/SPY +superficiality/S +superfine +superfix/M +superfluity/MS +superfluous/YP +superfluousness/S +superheat/D +superhero/SM +superheroes +superhighway/MS +superhuman/YP +superhumanness/M +superimpose/SDG +superimposition/MS +superintend/GSD +superintendence/S +superintendency/SM +superintendent/SM +superior/SMY +superiority/MS +superlative/PYS +superlativeness/M +superlunary +supermachine +superman/M +supermarket/SM +supermen +supermodel +supermom/S +supernal +supernatant +supernatural/SPY +supernaturalism/M +supernaturalness/M +supernormal/Y +supernova/MS +supernovae +supernumerary/S +superordinate +superpose/BSDG +superposition/MS +superpower/MS +superpredicate +supersaturate/XNGDS +supersaturation/M +superscribe/GSD +superscript/DGS +superscription/SM +supersede/SRDG +superseder/M +supersensitive/P +supersensitiveness/M +superset/MS +supersonic/S +supersonically +supersonics/M +superstar/SM +superstition/SM +superstitious/YP +superstore/S +superstructural +superstructure/SM +supertanker/SM +supertitle/MSDG +superuser/MS +supervene/GSD +supervention/S +supervise/SDGNX +supervised/U +supervision/M +supervisor/SM +supervisory +superwoman/M +superwomen +supine/PSY +supineness/M +supp/YDRGZ +supper/DMG +suppl/RDGT +supplant/SGRD +supplanter/M +supple/SPLY +supplement/SMDRG +supplemental/S +supplementary/S +supplementation/S +supplementer/M +suppleness/SM +suppliant/S +supplicant/MS +supplicate/NGXSD +supplication/M +supplier/AM +supply/MAZGSRD +support/ZGVSBDR +supportability/M +supportable/UI +supported/U +supporter/M +supporting/Y +supportive/Y +suppose/SRDBJG +supposed/Y +supposition/MS +suppository/MS +suppress/VGSD +suppressant/S +suppressed/U +suppressible/I +suppression/SM +suppressive/P +suppressor/S +suppurate/NGXSD +suppuration/M +supra +supranational +supranationalism/M +suprasegmental +supremacist/SM +supremacy/SM +supremal +supreme/PSRTY +supremeness/M +supremo/M +supt +surcease/DSMG +surcharge/MGSD +surcingle/MGSD +surd/M +sure/PU +sured/I +surefire +surefooted +surely +sureness's/U +sureness/MS +surer/I +surest +surety/SM +surf/SJDRGMZ +surface/GSRDPZM +surfaced/UA +surfacer/AMS +surfaces/A +surfacing/A +surfactant/SM +surfboard/MDSG +surfeit/SDRMG +surfer/M +surfing/M +surge/GYMDS +surged/A +surgeon/MS +surgery/MS +surges/A +surgical/Y +surliness/SM +surly/TPR +surmise/SRDG +surmiser/M +surmount/DBSG +surmountable/IU +surname/GSDM +surpass/GDS +surpassed/U +surpassing/Y +surplice/SM +surplus/MS +surplussed +surplussing +surprise/MGDRSJ +surprised/U +surpriser/M +surprising/YU +surreal/S +surrealism/MS +surrealist/S +surrealistic +surrealistically +surreality +surrender/DRSG +surrenderer/M +surreptitious/PY +surreptitiousness/S +surrey/SM +surrogacy/S +surrogate/SDMNG +surrogation/M +surround/JGSD +surrounding/M +surtax/SDGM +surveillance/SM +surveillant +survey/JDSG +surveyed/A +surveying/M +surveyor/MS +surveys/A +survivability/M +survivable/U +survival/MS +survivalist/S +survive/SRDBG +survivor/MS +survivorship/M +susceptibilities +susceptibility/IM +susceptible/I +sushi/SM +suspect/GSDR +suspected/U +suspecter/M +suspecting/U +suspend/DRZGS +suspended/UA +suspender/M +suspense/MXNVS +suspenseful +suspension/AM +suspensive/Y +suspensor/M +suspicion/GSMD +suspicious/YP +suspiciousness/M +sustain/DRGLBS +sustainability +sustainable/U +sustainer/M +sustainment/M +sustenance/MS +sutler/MS +suture/GMSD +suzerain/SM +suzerainty/MS +svelte/RPTY +swab/MS +swabbed +swabbing +swabby/S +swaddle/SDG +swag/GMS +swagged +swagger/GSDR +swagging +swain/SM +swallow/GDRS +swallower/M +swallowtail/SM +swam +swami/SM +swamp/SRDMG +swamper/M +swampland/MS +swampy/RPT +swan/MS +swank/RDSGT +swankily +swankiness/MS +swanky/PTRS +swanlike +swanned +swanning +swap/S +swappable/U +swapped +swapper/SM +swapping +sward/MSGD +swarm/GSRDM +swarmer/M +swart/P +swarthiness/M +swarthy/RTP +swash/GSRD +swashbuckler/SM +swashbuckling/S +swastika/SM +swat/S +swatch/MS +swath/SRDMGJ +swathe +swather/M +swaths +swatted +swatter/MDSG +swatting +sway/DRGS +swayback/SD +swayer/M +swear/SGZR +swearer/M +swearword/SM +sweat/SGZRM +sweatband/MS +sweater/M +sweatily +sweatiness/M +sweatpants +sweatshirt/S +sweatshop/MS +sweaty/TRP +swede/SM +sweep/SBRJGZ +sweeper/M +sweeping/PY +sweepingness/M +sweeps/M +sweepstake's +sweepstakes +sweet/TXSYRNPG +sweetbread/SM +sweetbrier/SM +sweetcorn +sweeten/ZDRGJ +sweetened/U +sweetener/M +sweetening/M +sweetheart/MS +sweetie/MS +sweeting/M +sweetish/Y +sweetmeat/MS +sweetness/MS +sweetshop +swell/SJRDGT +swellhead/DS +swelling/M +swelter/DJGS +sweltering/Y +swept +sweptback +swerve/GSD +swerving/U +swift/GTYRDPS +swifter/M +swiftness/MS +swig/SM +swigged +swigging +swill/SDG +swim/S +swimmer/MS +swimming/MYS +swimsuit/MS +swindle/GZRSD +swindler/M +swine/SM +swineherd/MS +swing/SGRZJB +swingeing +swinger/M +swinging/Y +swingy/R +swinish/PY +swinishness/M +swipe/DSG +swirl/SGRD +swirling/Y +swirly/TR +swish/GSRD +swishy/R +swiss +switch/GBZMRSDJ +switchback/GDMS +switchblade/SM +switchboard/MS +switcher/M +switchgear +switchman/M +switchmen/M +switchover/M +swivel/GMDS +swizzle/RDGM +swob's +swollen +swoon/GSRD +swooning/Y +swoop/RDSG +swoosh/GSD +swop's +sword/DMSG +swordfish/SM +swordplay/RMS +swordplayer/M +swordsman/M +swordsmanship/SM +swordsmen +swordtail/M +swore +sworn +swot/S +swum +swung +sybarite/MS +sybaritic +sycamore/SM +sycophancy/S +sycophant/SYM +sycophantic +sycophantically +syllabi's +syllabic/S +syllabicate/GNDSX +syllabication/M +syllabicity +syllabification/M +syllabify/GSDXN +syllable/SDMG +syllabub/M +syllabus/MS +syllabusss +syllogism/MS +syllogistic +sylph/M +sylphic +sylphlike +sylphs +sylvan/S +symbiont/M +symbioses +symbiosis/M +symbiotic +symbol/GMDS +symbolic/SM +symbolical/Y +symbolics/M +symbolism/MS +symbolist/MS +symbolization/MAS +symbolize/GZRSD +symbolized/U +symbolizes/A +symmetric +symmetrical/PY +symmetrically/U +symmetricalness/M +symmetrization/M +symmetrizing +symmetry/MS +sympathetic/S +sympathetically/U +sympathize/SRDJGZ +sympathized/U +sympathizer/M +sympathizing/MYUS +sympathy/MS +symphonic +symphonists +symphony/MS +symposium/MS +symptom/MS +symptomatic +symptomatically +symptomatology/M +syn +synagogal +synagogue/SM +synapse/SDGM +synaptic +sync/SGD +synchronism/M +synchronization's +synchronization/SA +synchronize/AGCDS +synchronized/U +synchronizer/MS +synchronous/YP +synchronousness/M +synchrony +synchrotron/M +syncopate/VNGXSD +syncopation/M +syncope/MS +syndic/SM +syndicalist +syndicate/XSDGNM +syndrome/SM +synergism/SM +synergistic +synergy/MS +synfuel/S +synod/SM +synonym/SM +synonymic +synonymous/Y +synonymy/MS +synopses +synopsis/M +synopsized +synopsizes +synopsizing +synoptic/S +syntactic/SY +syntactical/Y +syntactics/M +syntax/MS +syntheses +synthesis/M +synthesize/GZSRD +synthesized/U +synthesizer/M +synthesizes/A +synthetic/S +synthetically +syphilis/MS +syphilitic/S +syphilized +syphilizing +syringe/GMSD +syrup/DMSG +syrupy +sys +system/MS +systematic/SP +systematical/Y +systematics/M +systematization/SM +systematize/ZDRSG +systematized/U +systematizer/M +systematizing/U +systemic/S +systemically +systemization/SM +systole/MS +systolic +séance/SM +t/XTJBG +tab/SM +tabbed +tabbing +tabbouleh +tabboulehs +tabby/GSD +tabernacle/SDGM +tabla/MS +table/GMSD +tableau/M +tableaux +tablecloth/M +tablecloths +tableland/SM +tablespoon/SM +tablespoonful/MS +tablet/MDGS +tabletop/MS +tableware/SM +tabling/M +tabloid/MS +taboo/GSMD +tabor/MDGS +tabula +tabular/Y +tabulate/XNGDS +tabulation/M +tabulator/MS +tachometer/SM +tachometry +tachycardia/MS +tachyon/SM +tacit/YP +tacitness/MS +taciturn/Y +taciturnity/MS +tack/GZRDMS +tacker/M +tackiness/MS +tackle/RSDMZG +tackler/M +tackling/M +tacky/RSTP +taco/MS +tact/FSM +tactful/YP +tactfulness/S +tactic/SM +tactical/Y +tactician/MS +tactile/Y +tactility/S +tactless/PY +tactlessness/SM +tactual/Y +tad/SM +tadpole/MS +taffeta/MS +taffrail/SM +taffy/SM +tag/SM +tagged/U +tagger/S +tagging +taiga/MS +tail/CMRDGAS +tailback/MS +tailcoat/S +tailer/AM +tailgate/MGRSD +tailgater/M +tailing/MS +tailless/P +taillessness/M +taillight/MS +tailor/DMJSGB +tailpipe/SM +tailspin/MS +tailwind/SM +taint/DGS +tainted/U +take/RSHZGJ +takeaway/S +taken/A +takeoff/SM +takeout/S +takeover/SM +taker/M +takes/IA +taking/IA +talc/SM +talcked +talcking +talcum/S +tale/RSMN +talebearer/SM +talent/SMD +talented/M +talentless +taler/M +tali +talion/M +talisman/SM +talismanic +talk/GZSRD +talkative/YP +talkativeness/MS +talker/M +talkie/M +talky/RST +tall/TPR +tallboy/MS +tallish +tallness/MS +tallow/DMSG +tallowy +tally/GRSDZ +tallyho/DMSG +talon/SMD +talus/MS +tam/MDRSTZGB +tamable/M +tamale/SM +tamarack/SM +tamarind/MS +tambourine/MS +tame/SYP +tamed/U +tameness/S +tamp/SGZRD +tamper/ZGRD +tampered/U +tamperer/M +tampon/DMSG +tan/MS +tanager/MS +tanbark/SM +tandem/SM +tandoori/S +tang/GSYDM +tangelo/SM +tangency/M +tangent/SM +tangential/Y +tangerine/MS +tangibility/MIS +tangible/IPS +tangibleness's/I +tangibleness/SM +tangibly/I +tangle's +tangle/UDSG +tango/MDSG +tangy/RST +tank/GZSRDM +tankard/MS +tanker/M +tankful/MS +tanned/U +tanner/SM +tannery/MS +tannest +tannin/SM +tanning/SM +tansy/SM +tantalization/SM +tantalize/GZSRD +tantalized/U +tantalizing/YP +tantalizingly/S +tantalizingness/S +tantalum/MS +tantamount +tantra/S +tantrum/SM +tao/S +taoism +taoist/S +tap/MSDRJZG +tape/SM +taped/U +tapeline/S +taper/GRD +taperer/M +tapestry/GMSD +tapeworm/MS +tapioca/MS +tapir/MS +tapped/U +tapper/MS +tappet/MS +tapping/M +taproom/MS +taproot/SM +taps/M +tar/GSMD +tarantella/MS +tarantula/MS +tardily +tardiness/S +tardy/TPRS +tare/MS +target/GSMD +tariff/DMSG +tarmac/S +tarmacked +tarmacking +tarn/MS +tarnish/GDS +tarnished/U +taro/MS +tarot/MS +tarp/MS +tarpapered +tarpaulin/MS +tarpon/MS +tarragon/SM +tarred/M +tarring/M +tarry/TGRSD +tarsal/S +tarsi +tarsus/M +tart/PMYRDGTS +tartan/MS +tartar/SM +tartaric +tartness/MS +task/GSDM +taskmaster/SM +taskmistress/MS +tassel/MDGS +tassellings +taste's/E +taste/GZMJSRD +tasted/EU +tasteful/PEY +tastefulness/SME +tasteless/YP +tastelessness/SM +taster/M +tastes/E +tastily +tastiness/MS +tasting/E +tasty/RTP +tat/SRZ +tatami/MS +tater/M +tatted +tatter/GDS +tatterdemalion/SM +tattered/M +tatting/SM +tattle/RSDZG +tattler/M +tattletale/SM +tattoo/ZRDMGS +tattooer/M +tattooist/MS +tatty/R +tau/SM +taught/AU +taunt/ZGRDS +taunter/M +taunting/Y +taupe/SM +taut/PGTXYRDNS +tauten/GD +tautness/S +tautological/Y +tautologous +tautology/SM +tavern/RMS +taverner/M +tawdrily +tawdriness/SM +tawdry/SRTP +tawny/RSMPT +tax/ZGJMDRSB +taxable/S +taxably +taxation/MS +taxed/U +taxi/MDGS +taxicab/MS +taxidermist/SM +taxidermy/MS +taximeter/SM +taxing/Y +taxiway/MS +taxonomic +taxonomically +taxonomist/SM +taxonomy/SM +taxpayer/MS +taxpaying/M +tbs +tbsp +tea/MDGS +teabag/S +teacake/MS +teacart/M +teach/AGS +teachable/P +teacher/MS +teaching/SM +teacloth +teacup/MS +teacupful/MS +teahouse/SM +teak/SM +teakettle/SM +teakwood/M +teal/MS +tealeaves +team/MRDGS +teammate/MS +teamster/MS +teamwork/SM +teapot/MS +tear/RDMSG +tearaway +teardrop/MS +tearer/M +tearful/YP +tearfulness/M +teargas/S +teargassed +teargassing +tearjerker/S +tearoom/MS +teary/RT +teas/SRDGZ +tease/KS +teasel/DGSM +teaser/M +teashop/SM +teasing/Y +teaspoon/MS +teaspoonful/MS +teat/MDS +teatime/MS +tech/D +technetium/SM +technical/YSP +technicality/MS +technicalness/M +technician/MS +technique/SM +technocracy/MS +technocrat/S +technocratic +technological/Y +technologist/MS +technology/MS +technophobia +technophobic +techs +tectonic/S +tectonically +tectonics/M +teddy/SM +tedious/YP +tediousness/SM +tedium/MS +tee/DRSMH +teeing +teem/GSD +teeming/PY +teemingness/M +teen/SR +teenage/RZ +teenager/M +teeny/RT +teenybopper/SM +teepee's +teeshirt/S +teeter/GDS +teeth/RSDJMG +teethe +teether/M +teething/M +teethmarks +teetotal/SRDGZ +teetotaler/M +teetotalism/MS +tektite/SM +tel/SY +telecast/SRGZ +telecommunicate/NX +telecommunication/M +telecommute/SRDZGJ +telecoms +teleconference/GMJSD +telegenic +telegram/MS +telegrammed +telegramming +telegraph/MRDGZ +telegraphic +telegraphically +telegraphist/MS +telegraphs +telegraphy/MS +telekineses +telekinesis/M +telekinetic +telemarketer/S +telemarketing/S +telemeter/DMSG +telemetric +telemetry/MS +teleological/Y +teleology/M +telepathic +telepathically +telepathy/SM +telephone/SRDGMZ +telephonic +telephonist/SM +telephony/MS +telephoto/S +telephotography/MS +teleprinter/MS +teleprocessing/S +teleprompter +telescope/GSDM +telescopic +telescopically +teletext/S +telethon/MS +teletype/SM +teletypewriter/SM +televangelism/S +televangelist/S +televise/SDXNG +television/M +televisor/MS +televisual +telex/GSDM +tell/AGS +teller/SDMG +telling/YS +telltale/MS +tellurium/SM +telly/SM +telnet/S +telomeric +temblor/SM +temerity/MS +temp/SGZTMRD +temper's/E +temper/GRDM +tempera/SLM +temperament/SM +temperamental/Y +temperance/IMS +temperate/SDGPY +temperately/I +temperateness's/I +temperateness/SM +temperature/MS +tempered/UE +tempering/E +tempers/E +tempest/DMSG +tempestuous/PY +tempestuousness/SM +template's +template/FS +temple/SDM +tempo/MS +tempoes +temporal/YS +temporarily +temporariness/FM +temporarinesses +temporary/SFP +temporize/GJZRSD +temporizer/M +temporizing/YM +temporizings/U +tempt/FS +temptation/MS +tempted +tempter/S +tempting/YS +temptress/MS +tempura/SM +ten/MHB +tenabilities +tenability/UM +tenable/P +tenableness/M +tenably +tenacious/YP +tenaciousness/S +tenacity/S +tenancy/MS +tenant/MDSG +tenanted/U +tenantry/MS +tench/M +tend/ISFRDG +tended/UE +tendency/MS +tendentious/PY +tendentiousness/SM +tender/FS +tendered +tenderer +tenderest +tenderfoot/MS +tenderhearted/YP +tenderheartedness/MS +tendering +tenderize/SRDGZ +tenderizer/M +tenderloin/SM +tenderly +tenderness/SM +tending/E +tendinitis/S +tendon/MS +tendril/SM +tends/E +tenebrous +tenement/MS +tenet/SM +tenfold/S +tenner +tennis/SM +tenon/GSMD +tenor/MS +tenpin/SM +tens/SRDVGT +tense/IPYTNVR +tenseness's/I +tenseness/SM +tensile +tension's/I +tension/GMRDS +tensional/I +tensionless +tensions/E +tensity/IMS +tensor/MS +tensorial +tenspot +tent/FSIM +tentacle/MSD +tentative/SPY +tentativeness/S +tented/UF +tenter/M +tenterhook/MS +tenth/SY +tenths +tenting/F +tenuity/S +tenuous/YP +tenuousness/SM +tenure/SDM +tepee/MS +tepid/YP +tepidity/S +tepidness/S +tequila/SM +teratogenic +teratology/MS +terbium/SM +tercel/M +tercentenary/S +tercentennial/S +term/MYRDGS +termagant/SM +termcap +termer/M +terminable/CPI +terminableness/IMC +terminal/SYM +terminate/CXNV +terminated/U +terminates +terminating +termination/MC +terminative/YC +terminator/SM +termini +terminological/Y +terminology/MS +terminus/M +termite/SM +tern's +tern/GIDS +ternary/S +terpsichorean +terr/S +terrace/MGSD +terracing/M +terracotta +terrain/MS +terramycin +terrapin/MS +terrarium/MS +terrazzo/SM +terrestrial/YMS +terrible/P +terribleness/SM +terribly +terrier/M +terrific/Y +terrifically +terrify/GDS +terrifying/Y +terrine/M +territorial/SY +territoriality/M +territory/SM +terror/MS +terrorism/MS +terrorist/MS +terroristic +terrorize/RSDZG +terrorized/U +terrorizer/M +terry/ZMRS +terrycloth +terse/RTYP +terseness/SM +tertian +tertiary/S +tessellate/XDSNG +tessellation/M +tesseral +test's/AKF +test/RDBFZGSC +testability/M +testable/U +testament/SM +testamentary +testate/IS +testator/MS +testatrices +testatrix +testbed/S +testcard +tested/AKU +tester/MFCKS +testes/M +testicle/SM +testicular +testifier/M +testify/GZDRS +testily +testimonial/SM +testimony/SM +testiness/S +testing/S +testis/M +testosterone/SM +tests/AK +testy/RTP +tetanus/MS +tetchy/TR +tether/DMSG +tethered/U +tetra/MS +tetrachloride/M +tetracycline/SM +tetrafluoride +tetragonal/Y +tetrahalides +tetrahedral/Y +tetrahedron/SM +tetrameron +tetrameter/SM +tetrasodium +tetravalent +text/FSM +textbook/SM +textile/SM +textual/FY +textural/Y +texture/MGSD +textured/U +th/GNJX +thalami +thalamus/M +thalidomide/MS +thallium/SM +thallophyte/M +than +thane/SM +thank/SRDG +thanker/M +thankful/YP +thankfuller +thankfullest +thankfulness/SM +thankless/PY +thanklessness/SM +thanksgiving/MS +that'd +that'll +that/MS +thatch/JMDRSZG +thatching/M +thaumaturge/M +thaw/DGS +the +theater/SM +theatergoer/MS +theatergoing/MS +theatric/S +theatrical/YS +theatricality/SM +theatrics/M +thee/DS +theeing +theft/MS +their/MS +theism/SM +theist/SM +theistic +them/GD +themas +thematic/U +thematically +thematics +theme/MS +themselves +thence +thenceforth +thenceforward/S +theocracy/SM +theocratic +theodolite/MS +theologian/SM +theological/Y +theologists +theology/MS +theorem/MS +theoretic/S +theoretical/Y +theoretician/MS +theoretics/M +theorist/SM +theorization/SM +theorize/ZGDRS +theory/MS +theosophic +theosophical +theosophist/MS +theosophy/SM +therapeutic/S +therapeutically +therapeutics/M +therapist/MS +therapy/MS +there'd +there'll +there/MS +thereabout/S +thereafter +thereat +thereby +therefor +therefore +therefrom +therein +thereof +thereon +thereto +theretofore +thereunder +thereunto +thereupon +therewith +therm/MS +thermal/YS +thermionic/S +thermionics/M +thermistor/MS +thermo/S +thermocouple/MS +thermodynamic/S +thermodynamical/Y +thermodynamics/M +thermoelastic +thermoelectric +thermoformed +thermoforming +thermogravimetric +thermoluminescence/M +thermometer/MS +thermometric +thermometry/M +thermonuclear +thermopile/M +thermoplastic/S +thermopower +thermos/S +thermosetting +thermostable +thermostat/SM +thermostatic/S +thermostatically +thermostatics/M +thermostatted +thermostatting +thesauri +thesaurus/MS +these/S +thesis/M +thespian/S +theta/MS +thew/SM +they +they'd +they'll +they're +they've +thiamine/MS +thick/TXPSRNY +thicken/RDJZG +thickener/M +thickening/M +thicket/SMD +thickheaded/M +thickish +thickness/MS +thickset/S +thief/M +thieve/SDJG +thievery/MS +thievish/P +thievishness/M +thigh/DM +thighbone/SM +thighs +thimble/DSMG +thimbleful/MS +thin/STPYR +thine +thing/MP +thingamabob/MS +thingamajig/SM +think/AGRS +thinkable/U +thinkableness/M +thinkably/U +thinker/MS +thinking/SMYP +thinkingly/U +thinned +thinner/MS +thinness/MS +thinnest +thinning +thinnish +thiocyanate/M +thiouracil/M +third/DYGS +thirst/GSMDR +thirster/M +thirstily +thirstiness/S +thirsty/TPR +thirteen/MHS +thirteenths +thirtieths +thirty/HMS +this +this'll +thistle/SM +thistledown/MS +thither +tho +thole/GMSD +thong/SMD +thoracic +thorax/MS +thoriate/D +thorium/MS +thorn/SMDG +thorniness/S +thorny/PTR +thorough/PTYR +thoroughbred/S +thoroughfare/MS +thoroughgoing +thoroughness/SM +those +thou/DSG +though +thought/MS +thoughtful/U +thoughtfully +thoughtfulness/S +thoughtless/YP +thoughtlessness/MS +thousand/SHM +thousandfold +thousandths +thrall/GSMD +thralldom/S +thrash/DSRZGJ +thrasher/M +thrashing/M +thread/MZDRGS +threadbare/P +threader/M +threading/A +threadlike +thready/RT +threat/MDNSXG +threaten/GJRD +threatener/M +threatening/Y +three/MS +threefold +threepence/M +threepenny +threescore/S +threesome/SM +threnody/SM +thresh/DSRZG +thresher/M +threshold/MDGS +threw +thrice +thrift/SM +thriftily +thriftiness/S +thriftless +thrifty/PTR +thrill/ZMGDRS +thriller/M +thrilling/Y +thrive/RSDJG +thriver/M +thriving/Y +throat/MDSG +throatily +throatiness/MS +throaty/PRT +throb/S +throbbed +throbbing +throe/SDM +throeing +thrombi +thromboses +thrombosis/M +thrombotic +thrombus/M +throne's +throne/CGSD +throng/GDSM +throttle/DRSZMG +throttler/M +through/Y +throughout +throughput/SM +throughway's +throw/SZGR +throwaway/SM +throwback/MS +thrower/M +thrown +throwout +thrum/S +thrummed +thrumming +thrush/MS +thrust/ZGSR +thruster/M +thruway/SM +thud/MS +thudded +thudding +thug/MS +thuggee/M +thuggery/SM +thuggish +thulium/SM +thumb/SMDG +thumbnail/MS +thumbscrew/SM +thumbtack/GMDS +thump/RDMSG +thunder/ZGJDRMS +thunderbolt/MS +thunderclap/SM +thundercloud/SM +thunderer/M +thunderhead/SM +thundering/Y +thunderous/Y +thundershower/MS +thunderstorm/MS +thunderstruck +thundery +thunk +thus/Y +thwack/DRSZG +thwacker/M +thwart/GSDRY +thwarter/M +thy +thyme/SM +thymine/MS +thymus/SM +thyratron/M +thyristor/MS +thyroglobulin +thyroid/S +thyroidal +thyronine +thyrotoxic +thyrotrophic +thyrotrophin +thyrotropic +thyrotropin/M +thyroxine/M +thyself +ti/MDRZ +tiara/MS +tibia/M +tibiae +tibial +tic/MS +tick/GZJRDMS +ticker/M +ticket/SGMD +ticking/M +tickle/RSDZG +tickler/M +ticklish/PY +ticklishness/MS +ticktacktoe/S +ticktock/SMDG +tidal/Y +tidbit/MS +tiddlywinks/M +tide/GJDS +tideland/MS +tidewater/SM +tideway/SM +tidily/U +tidiness/USM +tidy/UGDSRPT +tidying/M +tie/AUDS +tieback/MS +tiebreaker/SM +tier/DGM +tiff/GDMS +tiffany/M +tiger/SM +tigerish +tight/STXPRNY +tighten/JZGDR +tightener/M +tightfisted +tightness/MS +tightrope/SM +tightwad/MS +tigress/SM +tike's +tilde/MS +tile/DRSJMZG +tiled/UE +tiles/U +tiling/M +till/EGSZDR +tillable +tillage/SM +tiller's/E +tiller/GDM +tilt/RDSGZ +tilth/M +timber/DMSG +timbering/M +timberland/SM +timberline/S +timbre/MS +timbrel/SM +time/DRSJMYZG +timebase +timekeeper/MS +timekeeping/SM +timeless/PY +timelessness/S +timeliness/SMU +timely/UTRP +timeout/S +timepiece/MS +timer/M +timescale/S +timeserver/MS +timeserving/S +timeshare/SDG +timespan +timestamped +timestamps +timetable/GMSD +timeworn +timezone/S +timid/RYTP +timidity/SM +timidness/MS +timing/M +timorous/YP +timorousness/MS +timothy/MS +timpani +timpanist/S +tin/MDGS +tincture/SDMG +tinder/MS +tinderbox/MS +tine/SM +tinfoil/MS +ting/GYDM +tinge/S +tingeing +tingle/SDG +tingling/Y +tingly/TR +tinily +tininess/MS +tinker/SRDMZG +tinkle/SDG +tinkling/M +tinkly +tinned +tinner/M +tinnily +tinniness/SM +tinning/M +tinnitus/MS +tinny/RSTP +tinplate/S +tinsel/GMDYS +tinsmith/M +tinsmiths +tint/SGMRDB +tinter/M +tintinnabulation/MS +tintype/SM +tinware/MS +tiny/RPT +tip/MS +tipi's +tipoff +tipped +tipper/MS +tippet/MS +tipping +tipple/ZGRSD +tippler/M +tippy/R +tipsily +tipsiness/SM +tipster/SM +tipsy/TPR +tiptoe/SD +tiptoeing +tiptop/S +tirade/SM +tire/MGDSJ +tired/AYP +tireder +tiredest +tiredness/S +tireless/PY +tirelessness/SM +tires/A +tiresome/PY +tiresomeness/S +tiring/AU +tiro's +tis +tissue/MGSD +tit/MRZS +titan/SM +titanate/M +titanic +titanically +titanium/SM +titbit's +titer/M +tithe/SRDGZM +tither/M +tithing/M +titian/S +titillate/XSDVNG +titillating/Y +titillation/M +titivate/NGDSX +titivation/M +title/GMSRD +titled/AU +titleholder/SM +titling/A +titmice +titmouse/M +titrate/SDGN +titration/M +titted +titter/GDS +titting +tittle/SDMG +titular/SY +tizzy/SM +tn +tnpk +to/D +toad/SM +toadstool/SM +toady/GSDM +toadyism/M +toast/SZGRDM +toaster/M +toastmaster/MS +toastmistress/S +toasty/TRS +tobacco/SM +tobacconist/SM +tobaggon/SM +toboggan/MRDSZG +toccata/M +tocsin/MS +today'll +today/SM +toddle/ZGSRD +toddler/M +toddy/SM +toe/MS +toecap/SM +toeclip/S +toehold/MS +toeing +toenail/DMGS +toffee/SM +tofu/S +tog/SMG +toga/SMD +toge +together/P +togetherness/MS +togged +togging +toggle/SDMG +toil/SGZMRD +toilet/GMDS +toiletry/MS +toilette/SM +toilsome/PY +toilsomeness/M +tokamak +toke/GDS +token/SMDG +tokenism/SM +tokenized +told/AU +tole/MGDS +tolerability/IM +tolerable/I +tolerably/I +tolerance/SIM +tolerant/IY +tolerate/XVNGSD +toleration/M +toll/DGS +tollbooth/M +tollbooths +tollgate/MS +tollhouse/M +tollway/S +toluene/MS +tom/SM +tomahawk/SGMD +tomato/M +tomatoes +tomb/GSDM +tomblike +tombola/M +tomboy/MS +tomboyish +tombstone/MS +tomcat/SM +tomcatted +tomcatting +tome/SM +tomfool/M +tomfoolery/MS +tommed +tomming +tommy/M +tomographic +tomography/MS +tomorrow/MS +tomtit/SM +ton/SKM +tonal/Y +tonality/MS +tone's +tone/ISRDZG +tonearm/S +toneless/YP +tonelessness/M +toner/IM +tong/GRDS +tongue/SDMG +tongueless +tonguing/M +tonic/SM +tonight/MS +tonk/MS +tonnage/SM +tonne/MS +tonsil/SM +tonsillectomy/MS +tonsillitis/SM +tonsorial +tonsure/SDGM +tony/RT +too/H +toodle +took/A +tool's +tool/AGDS +toolbox/SM +tooler/SM +tooling/M +toolkit/SM +toolmake/ZRG +toolmaker/M +toolmaking/M +toolsmith +toot/GRDZS +tooter/M +tooth/DMG +toothache/SM +toothbrush/MSG +toothily +toothless +toothmarks +toothpaste/SM +toothpick/MS +tooths +toothsome +toothy/TR +tootle/SRDG +toots/M +tootsie +tootsy/MS +top/SMDRG +topaz/MS +topcoat/MS +topdressing/S +toper/M +topflight +topgallant/M +topiary/S +topic/MS +topical/Y +topicality/MS +topknot/MS +topless +topmast/MS +topmost +topnotch/R +topocentric +topographer/SM +topographic +topographical/Y +topography/MS +topological/Y +topologist/MS +topology/MS +topped +topper/MS +topping/MS +topple/GSD +topsail/MS +topside/SRM +topsoil/GDMS +topspin/MS +toque/MS +tor/SLM +torch/SDMG +torchbearer/SM +torchlight/S +tore/S +toreador/SM +tori/M +torment/GSD +tormenting/Y +tormentor/MS +torn +tornado/M +tornadoes +toroid/MS +toroidal/Y +torpedo/GMD +torpedoes +torpid/SY +torpidity/S +torpor/MS +torque/MZGSRD +torrence +torrent/MS +torrential +torrid/RYTP +torridity/SM +torridness/SM +tors/S +torsi's +torsion/IAM +torsional/Y +torsions +torso/SM +tort's +tort/ASFE +torte/MS +tortellini/MS +torten +tortilla/MS +tortoise/SM +tortoiseshell/SM +tortoni/MS +tortuous/PY +tortuousness/MS +torture/ZGSRD +torturous +torus/MS +toss/SRDGZ +tossup/MS +tot/MDRSG +total/ZGSRDYM +totaler/M +totalistic +totalitarian/S +totalitarianism/SM +totality/MS +totalizator/S +totalizing +tote/S +totem/MS +totemic +toter/M +toting/M +totted +totter/ZGRDS +totterer/M +tottering/Y +totting +toucan/MS +touch/ASDG +touchable/U +touchdown/SM +touched/U +toucher/M +touchily +touchiness/SM +touching/SY +touchline/M +touchscreen +touchstone/SM +touchy/TPR +touché +tough/TXGRDNYP +toughen/DRZG +toughener/M +toughness/SM +toughs +toupee/SM +tour's/CF +tour/GZSRDM +toured/CF +tourer/M +touring/F +tourism/SM +tourist/SM +touristic +touristy +tourmaline/SM +tournament/MS +tourney/GDMS +tourniquet/MS +tours/CF +tousle/GSD +tout/SGRD +touter/M +tow/DRSZG +toward/YU +towardliness/M +towardly/P +towards +towboat/MS +towel/GJDMS +towelette/S +toweling/M +tower/GMD +towering/Y +towhead/MSD +towhee/SM +towline/MS +town/SRM +towner/M +townhouse/S +townie/S +townsfolk +township/MS +townsman/M +townsmen +townspeople/M +townswoman/M +townswomen +towpath/M +towpaths +towrope/MS +toxemia/MS +toxic/S +toxicity/MS +toxicological +toxicologist/SM +toxicology/MS +toxin/MS +toy/MDRSG +toyer/M +toymaker +toyshop +tr +trace's +trace/ASDG +traceability/M +traceable/P +traceableness/M +traceback/MS +traced/U +traceless/Y +tracepoint/SM +tracer/MS +tracery/MDS +trachea/M +tracheae +tracheal/M +tracheotomy/SM +tracing/SM +track/SZGMRD +trackage +trackball/S +trackbed +tracked/U +tracker/M +trackless +tracksuit/SM +tract's +tract/ABS +tractability/SI +tractable/I +tractably/I +traction/KSCEMAF +tractive/KFE +tractor/FKMASC +tracts/CEFK +trade/SRDGZM +trademark/GSMD +trader/M +tradesman/M +tradesmen +tradespeople +tradespersons +tradeswoman/M +tradeswomen +tradition/SM +traditional/U +traditionalism/MS +traditionalist/MS +traditionalistic +traditionalized +traditionally +traduce/DRSGZ +traffic/SM +trafficked +trafficker/MS +trafficking/S +tragedian/SM +tragedienne/MS +tragedy/MS +tragic/S +tragically +tragicomedy/SM +tragicomic +trail/SZGJRD +trailblazer/MS +trailblazing/S +trailer/GDM +trails/F +trailside +train/ASDG +trainable +trained/U +trainee/MS +traineeships +trainer/MS +training/SM +trainman/M +trainmen +trainspotter/S +traipse/DSG +trait/MS +traitor/SM +traitorous/Y +trajectory/MS +tram/MS +trammed +trammel/GSD +trammeled/U +tramming +tramp/RDSZG +trample/DGRSZ +trampler/M +trampoline/GMSD +tramway/M +trance/MGSD +tranche/SM +tranquil/PTRY +tranquility/S +tranquilize/JGZDSR +tranquilized/U +tranquilizer/M +tranquilizes/A +tranquilizing/YM +tranquillize/GRSDZ +tranquillizer/M +tranquilness/M +trans/I +transact/GSD +transaction/MS +transactional +transactor/SM +transalpine +transaminase +transatlantic +transceiver/SM +transcend/SDG +transcendence/MS +transcendent/Y +transcendental/YS +transcendentalism/SM +transcendentalist/SM +transconductance +transcontinental +transcribe/DSRGZ +transcriber/M +transcript/SM +transcription/SM +transcultural +transducer/SM +transduction/M +transect/DSG +transept/SM +transfer/BSMD +transferability/M +transferal/MS +transferee/M +transference/SM +transferor/MS +transferral/SM +transferred +transferrer/SM +transferring +transfiguration/SM +transfigure/SDG +transfinite/Y +transfix/SDG +transform/DRZBSG +transformation/MS +transformational +transformed/U +transformer/M +transfuse/XSDGNB +transfusion/M +transgress/VGSD +transgression/SM +transgressor/S +transience/SM +transiency/S +transient/YS +transistor/SM +transistorize/GDS +transit/SGVMD +transition/MDGS +transitional/Y +transitive/PIY +transitiveness/IM +transitivenesses +transitivity/MS +transitoriness/M +transitory/P +transl +translatability/M +translatable/U +translate/VGNXSDB +translated/AU +translation/M +translational +translator/SM +transliterate/XNGSD +translucence/SM +translucency/MS +translucent/Y +transmigrate/XNGSD +transmissible +transmission/MSA +transmissive +transmit/AS +transmittable +transmittal/SM +transmittance/MS +transmitted/A +transmitter/SM +transmitting/A +transmogrification/M +transmogrify/GXDSN +transmutation/SM +transmute/GBSD +transnational/S +transoceanic +transom/SM +transonic +transpacific +transparency/MS +transparent/YP +transparentness/M +transpiration/SM +transpire/GSD +transplant/GRDBS +transplantation/S +transpolar +transponder/MS +transport/BGZSDR +transportability +transportable/U +transportation/SM +transpose/BGSD +transposed/U +transposition/SM +transsexual/SM +transsexualism/MS +transship/LS +transshipment/SM +transshipped +transshipping +transubstantiation/MS +transversal/YM +transverse/GYDS +transvestism/SM +transvestite/SM +transvestitism +trap/MS +trapdoor/S +trapeze/DSGM +trapezium/MS +trapezoid/MS +trapezoidal +trappable/U +trapped +trapper/SM +trapping/S +trapshooting/SM +trash/SRDMG +trashcan/SM +trashiness/SM +trashy/TRP +trauma/MS +traumatic +traumatically +traumatize/SDG +travail/SMDG +travel/SDRGZJ +traveled/U +traveler/M +travelog's +travelogue/S +traversal/SM +traverse/GBDRS +traverser/M +travertine/M +travesty/SDGM +trawl/RDMSZG +trawler/M +tray/SM +treacherous/PY +treacherousness/SM +treachery/SM +treacle/DSGM +treacly +tread/SAGD +treader/M +treadle/GDSM +treadmill/MS +treas +treason/BMS +treasonous +treasure/DRSZMG +treasurer/M +treasurership +treasury/SM +treat's +treat/SAGDR +treatable +treated/U +treater/S +treatise/MS +treatment/MS +treaty/MS +treble/SDG +tree/MDS +treeing +treeless +treelike +treetop/SM +trefoil/SM +trek/MS +trekked +trekker/MS +trekking +trellis/GDSM +trematode/SM +tremble/JDRSG +trembler/M +trembles/M +trembly +tremendous/YP +tremendousness/M +tremolo/MS +tremor/MS +tremulous/YP +tremulousness/SM +trench's +trench/GASD +trenchancy/MS +trenchant/Y +trencher/SM +trencherman/M +trenchermen +trend/SDMG +trendily +trendiness/S +trendy/PTRS +trepanned +trepidation/MS +trespass/ZRSDG +trespasser/M +tress/MSDG +tressed/E +tresses/E +tressing/E +trestle/MS +trey/MS +triable/P +triableness/M +triad/MS +triadic +triage/SDMG +trial/ASM +trialization +trialled +trialling +triamcinolone +triangle/SM +triangulable +triangular/Y +triangularization/S +triangulate/YGNXSD +triangulation/M +triathlon/S +triatomic +tribal/Y +tribalism/MS +tribe/MS +tribesman/M +tribesmen +tribeswoman +tribeswomen +tribulate/NX +tribulation/M +tribunal/MS +tribune/SM +tributary/MS +tribute's +tribute/EGSF +trice/GSDM +tricentennial/S +triceps/SM +triceratops/M +trichina/M +trichinae +trichinoses +trichinosis/M +trichloroacetic +trichloroethane +trichotomy/M +trichromatic +trick/GMSRD +trickery/MS +trickily +trickiness/SM +trickle/DSG +trickster/MS +tricky/RPT +tricolor/SMD +tricycle/SDMG +trident/SM +tridiagonal +tried/UA +triennial/SY +trier's +trier/AS +tries/A +triffid/S +trifle/MZGJSRD +trifler/M +trifluoride/M +trifocals +trig/S +trigged +trigger/GSDM +triggest +trigging +triglyceride/MS +trigonal/Y +trigonometric +trigonometrical +trigonometry/MS +trigram/S +trihedral +trike/GMSD +trilateral/S +trilby/SM +trilingual +trill/RDMGS +trillion/SMH +trillionth/M +trillionths +trillium/SM +trilobite/MS +trilogy/MS +trim/PSYR +trimaran/MS +trimer/M +trimester/MS +trimmed/U +trimmer/MS +trimmest +trimming/MS +trimness/S +trimodal +trimonthly +trinitarian/S +trinitrotoluene/SM +trinity/MS +trinket/MRDSG +trinketer/M +trio/SM +triode/MS +trioxide/M +trip/SMY +tripartite/N +tripartition/M +tripe/MS +triphenylarsine +triphenylphosphine +triphenylstibine +triphosphopyridine +triple/GSD +triplet/SM +triplex/S +triplicate/SDG +triplication/M +triply/GDSN +tripod/MS +tripodal +tripoli/M +tripolyphosphate +tripos/SM +tripped +tripper/MS +tripping/Y +triptych/M +triptychs +tripwire/MS +trireme/SM +trisect/GSD +trisection/S +trisector +trisodium +tristate +trisyllable/M +trite/SRPTY +tritely/F +triteness/SF +tritium/MS +triton/M +triumph/GMD +triumphal +triumphalism +triumphant/Y +triumphs +triumvir/MS +triumvirate/MS +triune +trivalent +trivet/SM +trivia +trivial/Y +triviality/MS +trivialization/MS +trivialize/DSG +trivium/M +trochaic/S +trochee/SM +trod/AU +trodden/UA +trodes +troff/MR +troglodyte/MS +troika/SM +troll/DMSG +trolled/F +trolley/SGMD +trolleybus/S +trolling/F +trollish +trollop/GSMD +trolly's +trombone/MS +trombonist/SM +tromp/DSG +troop/SRDMZG +trooper/M +troopship/SM +trope/SM +trophic +trophy/MGDS +tropic/MS +tropical/SY +tropism/SM +tropocollagen +troposphere/MS +tropospheric +trot/S +troth/GDM +troths +trotted +trotter/SM +trotting +troubadour/SM +trouble/GDRSM +troubled/U +troublemaker/MS +troubler/M +troubleshoot/SRDZG +troubleshooter/M +troubleshot +troublesome/YP +troublesomeness/M +trough/M +troughs +trounce/GZDRS +trouncer/M +troupe/MZGSRD +trouper/M +trouser/DMGS +trousseau/M +trousseaux +trout/SM +trove/SM +trow/SGD +trowel/SMDRGZ +troweler/M +troy/S +truancy/MS +truant/SMDG +truce/SDGM +truck/SZGMRDJ +trucker/M +trucking/M +truckle/GDS +truckload/MS +truculence/SM +truculent/Y +trudge/SRDG +true/DRSPTG +truelove/MS +trueness/M +truer/U +truest/U +truffle/MS +truism/SM +truly/U +trump/DMSG +trumpery/SM +trumpet/MDRZGS +trumpeter/M +truncate/NGDSX +truncation/M +truncheon/MDSG +trundle/GZDSR +trundler/M +trunk/GSMD +trunnion/SM +truss/SRDG +trusser/M +trussing/M +trust/RDMSG +trusted/EU +trustee/MDS +trusteeing +trusteeship/SM +truster/M +trustful/EY +trustfulness/SM +trustiness/M +trusting/Y +trusts/E +trustworthier +trustworthiest +trustworthiness/MS +trustworthy/UP +trusty/PTMSR +truth/UM +truthful/UYP +truthfulness/US +truths/U +try/JGDRSZ +trying/Y +tryout/MS +trypsin/M +tryst/GDMS +ts +tsarevich +tsarina's +tsarism/M +tsarist +tsetse/S +tsp +tsunami/MS +tty/M +ttys +tub/JMDRSZG +tuba/SM +tubae +tubal +tubbed +tubbing +tubby/TR +tube/SM +tubeless +tuber/M +tubercle/MS +tubercular/S +tuberculin/MS +tuberculoses +tuberculosis/M +tuberculous +tuberose/SM +tuberous +tubing/M +tubular/Y +tubule/SM +tuck/GZSRD +tucker/GDM +tuft/GZSMRD +tufter/M +tufting/M +tug/S +tugboat/MS +tugged +tugging +tuition/ISM +tularemia/S +tulip/SM +tulle/SM +tum +tumble/ZGRSDJ +tumbledown +tumbler/M +tumbleweed/MS +tumbrel/SM +tumescence/S +tumescent +tumid/Y +tumidity/MS +tummy/SM +tumor/MDS +tumorous +tumult/SGMD +tumultuous/PY +tumultuousness/M +tumulus/M +tun/DRJZGBS +tuna/SM +tunable/P +tunableness/M +tundra/SM +tune's +tune/CSDG +tuneful/YP +tunefulness/MS +tuneless/Y +tuner/M +tuneup/S +tung +tungstate/M +tungsten/SM +tunic/MS +tuning's +tuning/A +tunned +tunnel/MRDSJGZ +tunneler/M +tunning +tunny/SM +tupelo/M +tuple/SM +tuppence/M +turban/SDM +turbid +turbidity/SM +turbinate/SD +turbine/SM +turbo/SM +turbocharged +turbocharger/SM +turbofan/MS +turbojet/MS +turboprop/MS +turbot/MS +turbulence/SM +turbulent/Y +turd/MS +tureen/MS +turf/DGSM +turfy/RT +turgid/PY +turgidity/SM +turgidness/M +turk/S +turkey/SM +turmeric/MS +turmoil/SDMG +turn/AZGRDBS +turnabout/SM +turnaround/MS +turnbuckle/SM +turncoat/SM +turned/U +turner/M +turning/MS +turnip/SMDG +turnkey/MS +turnoff/MS +turnout/MS +turnover/SM +turnpike/MS +turnround/MS +turnstile/SM +turnstone/M +turntable/SM +turpentine/GMSD +turpitude/SM +turquoise/SM +turret/SMD +turtle/SDMG +turtleback/MS +turtledove/MS +turtleneck/SDM +turves's +turvy +tush/SDG +tusk/GZRDMS +tusker/M +tussle/GSD +tussock/MS +tussocky +tut/S +tutelage/MS +tutelary/S +tutor/MDGS +tutored/U +tutorial/MS +tutorship/S +tutted +tutti/S +tutting +tutu/SM +tux/S +tuxedo/SDM +twaddle/GZMRSD +twaddler/M +twain/S +twang/MDSG +twangy/TR +twas +tweak/SGRD +twee/DP +tweed/SM +tweediness/M +tweedy/PTR +tween +tweet/ZSGRD +tweeter/M +tweeze/ZGRD +tweezer/M +twelfth +twelfths +twelve/MS +twelvemonth/M +twelvemonths +twentieths +twenty/MSH +twerp/MS +twice/R +twiddle/GRSD +twiddler/M +twiddly/RT +twig/SM +twigged +twigging +twiggy/RT +twilight/MS +twilit +twill/SGD +twin/RDMGZS +twine/SM +twiner/M +twinge/SDMG +twinkle/RSDG +twinkler/M +twinkling/M +twinkly +twinned +twinning +twirl/SZGRD +twirler/M +twirling/Y +twirly/TR +twist/SZGRD +twisted/U +twister/M +twists/U +twisty +twit/S +twitch/GRSD +twitchy/TR +twitted +twitter/SGRD +twitterer/M +twittery +twitting +twixt +two/MS +twofer/MS +twofold/S +twopence/SM +twopenny/S +twosome/MS +twp +tycoon/MS +tyeing +tying/UA +tyke/SM +tympani +tympanist/SM +tympanum/SM +type/MGDRSJ +typeahead +typecast/SG +typed/AU +typedef/S +typeface/MS +typeless +types/A +typescript/SM +typeset/S +typesetter/MS +typesetting/SM +typewrite/SRJZG +typewriter/M +typewriting/M +typewritten +typewrote +typhoid/SM +typhoon/SM +typhus/SM +typical/U +typicality/MS +typically +typicalness/M +typification/M +typify/SDNXG +typing/A +typist/MS +typo/MS +typographer/SM +typographic +typographical/Y +typography/MS +typological/Y +typology/MS +tyrannic +tyrannical/PY +tyrannicalness/M +tyrannicide/M +tyrannize/ZGJRSD +tyrannizer/M +tyrannizing/YM +tyrannosaur/MS +tyrannosaurus/S +tyrannous +tyranny/MS +tyrant/MS +tyreo +tyro/SM +tyrosine/M +tzar's +tzarina's +u +ubiquitous/YP +ubiquity/S +udder/SM +ufologist/S +ufology/MS +ugh +ughs +uglification +ugliness/MS +uglis +ugly/PTGSRD +uh +ukase/SM +ukulele/SM +ulcer/MDGS +ulcerate/NGVXDS +ulceration/M +ulcerous +ulna/M +ulnae +ulnar +ulster/MS +ult +ulterior/Y +ultimas +ultimate/DSYPG +ultimateness/M +ultimatum/MS +ultimo +ultra/S +ultracentrifugally +ultracentrifugation +ultracentrifuge/M +ultraconservative/S +ultrafast +ultrahigh +ultralight/S +ultramarine/SM +ultramodern +ultramontane +ultrashort +ultrasonic/S +ultrasonically +ultrasonics/M +ultrasound/SM +ultrastructure/M +ultraviolet/SM +ululate/DSXGN +ululation/M +um +umbel/MS +umber/GMDS +umbilical/S +umbilici +umbilicus/M +umbra/MS +umbrage/MGSD +umbrageous +umbrella/GDMS +umiak/MS +umlaut/GMDS +ump/MDSG +umpire/MGSD +umpteen/H +unabated/Y +unabridged/S +unacceptability +unacceptable +unaccepted +unaccommodating +unaccountability +unaccustomed/Y +unadapted +unadulterated/Y +unadventurous +unalienability +unalterable/P +unalterableness/M +unalterably +unambiguity +unambiguous +unambitious +unamused +unanimity/SM +unanimous/Y +unanticipated/Y +unapologetic +unapologizing/M +unappeasable +unappeasably +unappreciative +unary +unassailable/P +unassailableness/M +unassertive +unassuming/PY +unassumingness/M +unauthorized/PY +unavailing/PY +unaware/SPY +unbalanced/P +unbar +unbarring +unbecoming/P +unbeknown +unbelieving/Y +unbiased/P +unbid +unbind/G +unblessed +unblinking/Y +unbodied +unbolt/G +unbreakability +unbred +unbroken +unbuckle +unbudging/Y +unburnt +uncap +uncapping +uncatalogued +uncauterized/MS +unceasing/Y +uncelebrated +uncertain/P +unchallengeable +unchanging/PY +unchangingness/M +uncharacteristic +uncharismatic +unchastity +unchristian +uncial/S +uncivilized/Y +unclassified +uncle/MSD +unclouded/Y +uncodable +uncollected +uncolored/PY +uncoloredness/M +uncombable +uncommunicative +uncompetitive +uncomplicated +uncomprehending/Y +uncompromisable +unconcern/M +unconcerned/P +unconfirmed +unconfused +unconscionable/P +unconscionableness/M +unconscionably +unconstitutional +unconsumed +uncontentious +uncontrollability +unconvertible +uncool +uncooperative +uncork/G +uncouple/G +uncouth/YP +uncouthness/M +uncreate/V +uncritical +uncross/GB +uncrowded +unction/IM +unctions +unctuous/PY +unctuousness/MS +uncustomary +uncut +undated/I +undaunted/Y +undeceive +undecided/S +undedicated +undefinability +undefined/P +undefinedness/M +undelete +undeliverability +undeniable/P +undeniableness/M +undeniably +undependable +under/Y +underachieve/SRDGZ +underachiever/M +underact/GDS +underadjusting +underage/S +underarm/DGS +underbedding +underbelly/MS +underbid/S +underbidding +underbracing +underbrush/MSDG +undercarriage/MS +undercharge/GSD +underclass/S +underclassman +underclassmen +underclothes +underclothing/MS +undercoat/JMDGS +undercoating/M +underconsumption/M +undercooked +undercount/S +undercover +undercurrent/SM +undercut/S +undercutting +underdeveloped +underdevelopment/MS +underdog/MS +underdone +undereducated +underemphasis +underemployed +underemployment/SM +underenumerated +underenumeration +underestimate/NGXSD +underexploited +underexpose/SDG +underexposure/SM +underfed +underfeed/SG +underfloor +underflow/GDMS +underfoot +underfund/DG +underfur/MS +undergarment/SM +undergirding +undergo/G +undergoes +undergone +undergrad/MS +undergraduate/MS +underground/RMS +undergrowth/M +undergrowths +underhand/D +underhanded/YP +underhandedness/MS +underheat +underinvestment +underlaid +underlain/S +underlay/GS +underlie +underline/GSDJ +underling/MS +underlip/SM +underloaded +underly/GS +undermanned +undermentioned +undermine/SDG +undermost +underneath +underneaths +undernourished +undernourishment/SM +underpaid +underpants +underpart/MS +underpass/SM +underpay/GSL +underpayment/SM +underperformed +underpin/S +underpinned +underpinning/MS +underplay/SGD +underpopulated +underpopulation/M +underpowered +underpricing +underprivileged +underproduction/MS +underrate/GSD +underregistration/M +underreported +underreporting +underrepresentation/M +underrepresented +underscore/SDG +undersea/S +undersealed +undersecretary/SM +undersell/SG +undersexed +undershirt/SM +undershoot/SG +undershorts +undershot +underside/SM +undersign/SGD +undersigned/M +undersized +undersizes +undersizing +underskirt/MS +undersold +underspecification +underspecified +underspend/G +understaffed +understand/RGSJB +understandability/M +understandably +understanding/YM +understate/GSDL +understatement/MS +understocked +understood +understrength +understructure/SM +understudy/GMSD +undertake/SRGZJ +undertaken +undertaker/M +undertaking/M +underthings +undertone/SM +undertook +undertow/MS +underused +underusing +underutilization/M +underutilized +undervaluation/S +undervalue/SDG +underwater/S +underway +underwear/M +underweight/S +underwent +underwhelm/DGS +underwood/M +underworld/MS +underwrite/GZSR +underwriter/M +underwritten +underwrote +undeserving +undesigned +undeviating/Y +undialyzed/SM +undiplomatic +undiscerning +undiscriminating +undo/GJ +undoubted/Y +undramatic +undramatized/SM +undress/G +undrinkability +undrinkable +undroppable +undue +undulant +undulate/XDSNG +undulation/M +unearth/YG +unearthliness/S +unearthly/P +unease +uneconomic +uneducated +unemployed/S +unencroachable +unending/Y +unendurable/P +unenergized/MS +unenforced +unenterprising +unethical +uneulogized/SM +unexacting +unexceptionably +unexcited +unexpectedness/MS +unfading/Y +unfailing/P +unfailingness/M +unfamiliar +unfashionable +unfathomably +unfavored +unfeeling +unfeigned/Y +unfelt +unfeminine +unfertile +unfetchable +unflagging +unflappability/S +unflappable +unflappably +unflinching/Y +unfold/LG +unfoldment/M +unforced +unforgeable +unfossilized/MS +unfraternizing/SM +unfrozen +unfulfillable +unfunny +unfussy +ungainliness/MS +ungainly/PRT +ungenerous +ungentle +unglamorous +ungrammaticality +ungrudging +unguent/MS +ungulate/MS +unharmonious +unharness/G +unhistorical +unholy/TP +unhook/DG +unhydrolyzed/SM +unhygienic +unicameral +unicellular +unicorn/SM +unicycle/MGSD +unicyclist/MS +unideal +unidimensional +unidiomatic +unidirectional/Y +unidirectionality +unidolized/MS +unifiable +unification/MA +unifier/MS +unifilar +uniform/TGSRDYMP +uniformity/MS +uniformness/M +unify/AXDSNG +unilateral/Y +unilateralism/M +unilateralist +unimodal +unimpeachably +unimportance +unimportant +unimpressive +unindustrialized/MS +uninhibited/YP +uninominal +uninsured +unintellectual +unintended +uninteresting +uninterrupted/YP +uninterruptedness/M +unintuitive +uninviting +union/AEMS +unionism/SM +unionist/SM +unionize +unipolar +uniprocessor/SM +unique/TYSRP +uniqueness/S +unisex/S +unison/MS +unit/VGRD +unitarian/MS +unitarianism/M +unitary +unite/AEDSG +united/Y +uniter/M +unitize/GDS +unity/SEM +univ +univalent/S +univalve/MS +univariate +universal/YSP +universalism/M +universalistic +universality/SM +universalize/DSRZG +universalizer/M +universe/MS +university/MS +unjam +unkempt +unkind/TP +unkink +unknightly +unknowable/S +unknowing +unlabored +unlace/G +unlearn/G +unlikeable +unlikeliness/S +unlimber/G +unlimited +unlit +unliterary +unloose/G +unlucky/TP +unmagnetized/MS +unmanageably +unmannered/Y +unmask/G +unmeaning +unmeasured +unmeetable +unmelodious +unmemorable +unmemorialized/MS +unmentionable/S +unmerciful +unmeritorious +unmethodical +unmineralized/MS +unmissable +unmistakably +unmitigated/YP +unmnemonic +unmobilized/SM +unmoral +unmount/B +unmovable +unmoving +unnaturalness/M +unnavigable +unnerving/Y +unobliging +unoffensive +unofficial +unorganized/YP +unorthodox +unpack/G +unpaintable +unpalatability +unpalatable +unpartizan +unpatronizing +unpeople +unperceptive +unperson +unperturbed/Y +unphysical +unpick/G +unpicturesque +unpinning +unpleasing +unploughed +unpolarized/SM +unpopular +unpractical +unprecedented/Y +unpredictable/S +unpreemphasized +unpremeditated +unpretentiousness/M +unprincipled/P +unproblematic +unproductive +unpropitious +unprovable +unproven +unprovocative +unpunctual +unquestionable +unraisable +unravellings +unread/B +unreadability +unreal +unrealizable +unreasoning/Y +unreceptive +unrecordable +unreflective +unrelenting/Y +unremitting/Y +unrepeatability +unrepeated +unrepentant +unreported +unrepresentative +unreproducible +unrest/G +unrestrained/P +unrewarding +unriddle +unripe/P +unromantic +unruliness/SM +unruly/PTR +unsaleable +unsanitary +unsavored/YP +unsavoriness/M +unseal/GB +unsearchable +unseasonal +unseeing/Y +unseen/S +unselfconscious/P +unselfconsciousness/M +unselfishness/M +unsellable +unsentimental +unset +unsettled/P +unsettledness/M +unsettling/Y +unshapely +unshaven +unshorn +unsighted +unsightliness/S +unskilful +unsociability +unsociable/P +unsocial +unsound/PT +unspeakably +unspecific +unspectacular +unspoilt +unspoke +unsporting +unstable/P +unstigmatized/SM +unstilted +unstinting/Y +unstopping +unstrapping +unstudied +unstuffy +unsubdued +unsubstantial +unsubtle +unsuitable +unsuspecting/Y +unswerving/Y +unsymmetrical +unsympathetic +unsystematic +unsystematized/Y +untactful +untalented +untaxing +unteach/B +untellable +untenable +unthinking +until/G +untiring/Y +unto +untouchable/MS +untoward/P +untowardness/M +untraceable +untrue +untruthfulness/M +untwist/G +unusualness/M +unutterable +unutterably +unvocalized/MS +unvulcanized/SM +unwaivering +unwarrantable +unwarrantably +unwashed/PS +unwearable +unwearied/Y +unwed +unwedge +unwelcome +unwell/M +unwieldiness/MS +unwieldy/TPR +unwind/B +unwomanly +unworkable/S +unworried +unwrap +unwrapping +unyielding/Y +unyoke +unzip +up +uparrow +upbeat/SM +upbraid/GDRS +upbring/JG +upbringing/M +upchuck/SDG +upcome/G +upcountry/S +updatability +update/RSDG +updater/M +updraft/SM +upend/SDG +upfield +upfront +upgrade/DSJG +upgradeable +upheaval/MS +upheld +uphill/S +uphold/RSGZ +upholder/M +upholster/ADGS +upholsterer/SM +upholstery/MS +upkeep/SM +upland/MRS +uplander/M +uplift/SJDRG +uplifter/M +upload/GSD +upmarket +upon +upped +upper/S +uppercase/GSD +upperclassman/M +upperclassmen +uppercut/S +uppercutting +uppermost +upping +uppish +uppity +upraise/GDS +uprated +uprating +uprear/DSG +upright/DYGSP +uprightness/S +uprise/RGJ +uprising/M +upriver/S +uproar/MS +uproarious/PY +uproariousness/M +uproot/DRGS +uprooter/M +ups +upscale/GDS +upset/S +upsetting/MS +upshot/SM +upside/MS +upsilon/MS +upslope +upstage/DSRG +upstairs +upstanding/P +upstandingness/M +upstart/MDGS +upstate/SR +upstream/DSG +upstroke/MS +upsurge/DSG +upswing/GMS +upswung +uptake/SM +upthrust/GMS +uptight +uptime +uptown/RS +uptrend/M +upturn/GDS +upward/SYP +upwardness/M +upwelling +upwind/S +uracil/MS +uranium/MS +uranyl/M +urban/RT +urbane/Y +urbanism/M +urbanite/SM +urbanity/SM +urbanization/MS +urbanize/DSG +urbanologist/S +urbanology/S +urchin/SM +urea/SM +uremia/MS +uremic +ureter/MS +urethane/MS +urethra/M +urethrae +urethral +urethritis/M +urge/GDRSJ +urgency/SM +urgent/Y +urger/M +uric +urinal/MS +urinalyses +urinalysis/M +urinary/MS +urinate/XDSNG +urination/M +urine/MS +urn/MDGS +urning/M +urogenital +urological +urologist/S +urology/MS +ursine +urticaria/MS +us/DRSBZG +usability/S +usable/U +usably/U +usage/SM +use/ESDAG +used/U +useful/YP +usefulness/SM +useless/PY +uselessness/MS +user/M +usher/SGMD +usherette/SM +usu +usual/UPY +usuals +usurer/SM +usurious/PY +usuriousness/M +usurp/RDZSG +usurpation/MS +usurper/M +usury/SM +utensil/SM +uteri +uterine +uterus/M +utile/I +utilitarian/S +utilitarianism/MS +utility/MS +utilization's/A +utilization/MS +utilize/GZDRS +utilizer/M +utilizes/A +utmost/S +utopia/S +utopian's +utopianism/M +utter/TRDYGS +utterance/MS +uttered/U +utterer/M +uttermost/S +uucp/M +uvula/MS +uvular/S +uxorious +v/ASV +vacancy/MS +vacant/PY +vacantness/M +vacate/NGXSD +vacation/MRDZG +vacationist/SM +vacationland +vaccinate/NGSDX +vaccination/M +vaccine/SM +vaccinia/M +vaccinial +vacillate/XNGSD +vacillating/Y +vacillation/M +vacillator/SM +vacua's +vacuity/MS +vacuo +vacuolate/SDGN +vacuolated/U +vacuole/SM +vacuolization/SM +vacuous/PY +vacuousness/MS +vacuum/GSMD +vagabond/DMSG +vagabondage/MS +vagarious +vagary/MS +vagina/M +vaginae +vaginal/Y +vagrancy/MS +vagrant/SMY +vague/TYSRDP +vagueing +vagueness/MS +vain/TYRP +vainglorious/YP +vaingloriousness/M +vainglory/MS +val +valance/SDMG +vale/SM +valediction/MS +valedictorian/MS +valedictory/MS +valence/SM +valency/MS +valentine/SM +valet/GDMS +valetudinarian/MS +valetudinarianism/MS +valiance/S +valiant/SPY +valiantness/M +valid/PIY +validate/INGSDX +validated/AU +validates/A +validation/AMI +validity/IMS +validness/MI +validnesses +valise/MS +valley/SM +valor/MS +valorous/Y +valuable/IP +valuableness/IM +valuables +valuably/I +valuate/NGXSD +valuation/CSAM +valuator/SM +value's +value/CGASD +valued/U +valueless/P +valuelessness/M +valuer/SM +values/E +valve/GMSD +valveless +valvular +vamoose/GSD +vamp's +vamp/ADSG +vamper +vampire/MGSD +van/SMD +vanadium/MS +vandal/MS +vandalism/MS +vandalize/GSD +vane/MS +vanguard/MS +vanilla/MS +vanish/GRSDJ +vanisher/M +vanishing/Y +vanity/SM +vanned +vanning +vanquish/RSDGZ +vanquisher/M +vantage/MS +vapid/PY +vapidity/MS +vapidness/SM +vapor/MRDJGZS +vaporer/M +vaporing/MY +vaporisation +vaporise/DSG +vaporization/AMS +vaporize/DRSZG +vaporizer/M +vaporous +vapory +vaquero/SM +var/S +variability/IMS +variable/PMS +variableness/IM +variables/I +variably/I +variance's +variance/I +variances +variant/ISY +variate/MGNSDX +variation/M +variational +varicolored/MS +varicose/S +varied/U +variedly +variegate/NGXSD +variegation/M +varier/M +varietal/S +variety/MS +various/PY +varistor/M +varlet/MS +varmint/SM +varnish/ZGMDRS +varnished/U +varnisher/M +varsity/MS +vary/SRDJG +varying/UY +vascular +vase/SM +vasectomy/SM +vasomotor +vassal/GSMD +vassalage/MS +vast/PTSYR +vastness/MS +vat/SM +vatted +vatting +vaudeville/SM +vaudevillian/SM +vault/ZSRDMGJ +vaulter/M +vaulting/M +vaunt/GRDS +vaunter/M +vb +veal/MRDGS +vealed/A +vealer/MA +veals/A +vector's/F +vector/SGDM +vectorial +vectorization +vectorized +vectorizing +veejay/S +veep/S +veer/DSG +veering/Y +veg/M +vegan/SM +veges +vegetable/MS +vegetarian/SM +vegetarianism/MS +vegetate/DSNGVX +vegetation/M +vegetative/PY +vegged +veggie/S +vegging +vehemence/MS +vehemency/S +vehement/Y +vehicle/SM +vehicular +veil's +veil/UGSD +veiling/MU +vein/GSRDM +veining/M +vela/M +velar/S +velarize/SDG +veld/SM +veldt's +vellum/MS +velocipede/SM +velocity/SM +velor/S +velour's +velum/M +velvet/GSMD +velveteen/MS +velvety/RT +venal/Y +venality/MS +venation/SM +vend/DSG +vender's/K +vendetta/MS +vendible/S +vendor/MS +veneer/GSRDM +veneerer/M +veneering/M +venerability/S +venerable/P +venerate/XNGSD +veneration/M +venereal +venetian +vengeance/MS +vengeful/APY +vengefulness/AM +venial/YP +venialness/M +venireman/M +veniremen +venison/SM +venom/SGDM +venomous/YP +venomousness/M +venous/Y +vent's/F +vent/ISGFD +venter/M +ventilate/XSDVGN +ventilated/U +ventilation/M +ventilator/MS +ventral/YS +ventricle/MS +ventricular +ventriloquies +ventriloquism/MS +ventriloquist/MS +ventriloquy +venture/RSDJZG +venturesome/YP +venturesomeness/SM +venturi/S +venturous/YP +venturousness/MS +venue/MAS +veracious/YP +veraciousness/M +veracities +veracity/IM +veranda/SDM +verandahed +verb/KSM +verbal/SY +verbalization/MS +verbalize/ZGRSD +verbalized/U +verbalizer/M +verballed +verballing +verbatim +verbena/MS +verbiage/SM +verbose/YP +verbosity/SM +verboten +verdant/Y +verdict/SM +verdigris/GSDM +verdure/SDM +verge's +verge/FGSD +verger/SM +veridical/Y +verifiability/M +verifiable/U +verifiableness/M +verification/S +verified/U +verifier/MS +verify/GASD +verily +verisimilitude/SM +veritable/P +veritableness/M +veritably +verity/MS +vermicelli/MS +vermiculite/MS +vermiform +vermilion/MS +vermin/M +verminous +vermouth/M +vermouths +vernacular/YS +vernal/Y +vernier/SM +veronica/SM +verruca/MS +verrucae +versa +versatile/YP +versatileness/M +versatility/SM +verse's +verse/XSRDAGNF +versed/UI +verses/I +versicle/M +versification/M +versifier/M +versify/GDRSZXN +versing/I +version/MFISA +verso/SM +versus +vertebra/M +vertebrae +vertebral/Y +vertebrate/IMS +vertebration/M +vertex/SM +vertical/YPS +vertices's +vertiginous +vertigo/M +vertigoes +verve/SM +very/RT +vesicle/SM +vesicular/Y +vesiculate/GSD +vesper/SM +vessel/MS +vest's +vest/DIGSL +vestal/YS +vestibular +vestibule/SDM +vestige/SM +vestigial/Y +vesting/SM +vestment/ISM +vestry/MS +vestryman/M +vestrymen +vesture/SDMG +vet/SMR +vetch/SM +veter/M +veteran/SM +veterinarian/MS +veterinary/S +veto/DMG +vetoes +vetted +vetting/A +vex/GFSD +vexation/SM +vexatious/PY +vexatiousness/M +vexed/Y +vhf +vi/MDR +via +viability/SM +viable/I +viably +viaduct/MS +vial/MDGS +viand/SM +vibe/S +vibraharp/MS +vibrancy/MS +vibrant/YS +vibraphone/MS +vibraphonist/SM +vibrate/XNGSD +vibration/M +vibrational/Y +vibrato/MS +vibrator/SM +vibratory +vibrio/M +vibrionic +viburnum/SM +vicar/SM +vicarage/SM +vicarious/YP +vicariousness/MS +vice/CMS +viced +vicegerent/MS +vicennial +viceregal +viceroy/SM +vichyssoise/MS +vicing +vicinity/MS +vicious/YP +viciousness/S +vicissitude/MS +victim/SM +victimization/SM +victimize/SRDZG +victimized/U +victimizer/M +victor/SM +victorious/YP +victoriousness/M +victory/MS +victual/ZGSDR +victualer/M +vicuña/S +videlicet +video/GSMD +videocassette/S +videoconferencing +videodisc/S +videodisk/SM +videophone/SM +videotape/SDGM +vie/S +vier/M +view/MBGZJSRD +viewed/A +viewer's +viewer/AS +viewfinder/MS +viewgraph/SM +viewing/M +viewless/Y +viewpoint/SM +views/A +vigesimal +vigil/SM +vigilance/MS +vigilant/Y +vigilante/SM +vigilantism/MS +vigilantist +vignette/MGDRS +vignetter/M +vignetting/M +vignettist/MS +vigor/MS +vigorous/YP +vigorousness/M +vii +viii +viking/S +vile/AR +vilely +vileness/MS +vilest +vilification/M +vilifier/M +vilify/GNXRSD +villa/MS +village/RSMZ +villager/M +villain/SM +villainous/YP +villainousness/M +villainy/MS +ville +villein/MS +villeinage/SM +villi +villus/M +vim/MS +vinaigrette/MS +vincible/I +vindicate/XSDVGN +vindication/M +vindicator/SM +vindictive/PY +vindictiveness/MS +vine/MGDS +vinegar/DMSG +vinegary +vineyard/SM +vino/MS +vinous +vintage/MRSDG +vintager/M +vintner/MS +vinyl/SM +viol/MSB +viola/SM +violable/I +violate/VNGXSD +violator/MS +violence/SM +violent/Y +violet/SM +violin/MS +violinist/SM +violist/MS +violoncellist/S +violoncello/MS +viper/MS +viperous +virago/M +viragoes +viral/Y +vireo/SM +virgin/SM +virginal/YS +virginity/SM +virgule/MS +virile +virility/MS +virologist/S +virology/SM +virtual/Y +virtue/SM +virtuosity/MS +virtuoso/MS +virtuosoes +virtuous/PY +virtuousness/SM +virulence/SM +virulent/Y +virus/MS +vis/MDSGV +visa/SGMD +visage/MSD +viscera +visceral/Y +viscid/Y +viscoelastic +viscoelasticity +viscometer/SM +viscose/MS +viscosity/MS +viscount/MS +viscountcy/MS +viscountess/SM +viscous/PY +viscousness/M +viscus/M +vise's +vise/CAXNGSD +viselike +visibility/ISM +visible/PI +visibly/I +vision's/A +vision/KMDGS +visionariness/M +visionary/PS +visit/GASD +visitable/U +visitant/SM +visitation/SM +visited/U +visitor/MS +visor/SMDG +vista/GSDM +visual/SY +visualization/AMS +visualize/SRDZG +visualized/U +visualizer/M +visualizes/A +vita/M +vitae +vital/SY +vitality/MS +vitalization/AMS +vitalize/ASDGC +vitamin/SM +vitiate/XGNSD +vitiation/M +viticulture/SM +viticulturist/S +vitreous/YSP +vitrifaction/S +vitrification/M +vitrify/XDSNG +vitrine/SM +vitriol/MDSG +vitriolic +vitro +vittles +vituperate/SDXVGN +vituperation/M +vituperative/Y +viva/DGS +vivace/S +vivacious/YP +vivaciousness/MS +vivacity/SM +vivaria +vivarium/MS +vivaxes +vive/Z +vivid/PTYR +vividness/SM +vivifier +vivify/NGASD +viviparous +vivisect/DGS +vivisection/MS +vivisectional +vivisectionist/SM +vivo +vixen/SM +vixenish/Y +viz +vizier/MS +vizor's +vocab/S +vocable/SM +vocabularian +vocabularianism +vocabulary/MS +vocal/SY +vocalic/S +vocalise's +vocalism/M +vocalist/MS +vocalization/SM +vocalize/ZGDRS +vocalized/U +vocalizer/M +vocation/AKMISF +vocational/Y +vocative/KYS +vociferate/NGXSD +vociferation/M +vociferous/YP +vociferousness/MS +vocoded +vocoder +vodka/MS +voe/S +vogue/GMSRD +vogueing +voguish +voice/IMGDS +voiceband +voiced/CU +voiceless/YP +voicelessness/SM +voicer/S +voices/C +voicing/C +void/C +voidable +voided +voider/M +voiding +voidness/M +voids +voile/MS +voilà +vol/GSD +volar +volatile/PS +volatileness/M +volatility/MS +volatilization/MS +volatilize/SDG +volcanic/S +volcanically +volcanism/M +volcano/M +volcanoes +vole/MS +volition/MS +volitional/Y +volitionality +volley/SMRDG +volleyball/MS +volleyer/M +volt/AMS +voltage/SM +voltaic +voltmeter/MS +volubility/S +voluble/P +volubly +volume/SDGM +volumetric +volumetrically +voluminous/PY +voluminousness/MS +voluntarily/I +voluntariness/MI +voluntarism/MS +voluntary/PS +volunteer/DMSG +voluptuary/SM +voluptuous/YP +voluptuousness/S +volute/S +vomit/GRDS +voodoo/GDMS +voodooism/S +voracious/YP +voraciousness/MS +voracity/MS +vortex/SM +vortices's +vorticity/M +votary/MS +vote's +vote/CSDG +voter/SM +votive/YP +vouch/SRDGZ +voucher/GMD +vouchsafe/SDG +vow/SMDRG +vowel/MS +vowelled +vowelling +vower/M +voyage/GMZJSRD +voyager/M +voyageur/SM +voyeur/MS +voyeurism/MS +voyeuristic +vs +vulcanization/SM +vulcanize/SDG +vulcanized/U +vulgar/TSYR +vulgarian/MS +vulgarism/MS +vulgarity/MS +vulgarization/S +vulgarize/GZSRD +vulnerability/SI +vulnerable/IP +vulnerably/I +vulpine +vulture/SM +vulturelike +vulturous +vulva/M +vulvae +vying +w/XTJGV +wackes +wackiness/MS +wacko/MS +wacky/RTP +wad/MDRZGS +wadded +wadding/SM +waddle/GRSD +wade/S +wader/M +wadi/SM +wafer/GSMD +waffle/GMZRSD +waft/SGRD +wafter/M +wag/DRZGS +wage/SM +waged/U +wager/GZMRD +wagged +waggery/MS +wagging +waggish/YP +waggishness/SM +waggle/SDG +waggly +wagon/SGZMRD +wagoner/M +wagtail/SM +waif/SGDM +wail/SGZRD +wailer/M +wain/GSDM +wainscot/SGJD +wainwright/SM +waist/GSRDM +waistband/MS +waistcoat/GDMS +waister/M +waistline/MS +wait/GSZJRD +waiter/DMG +waitpeople +waitperson/S +waitress/GMSD +waive/SRDGZ +waiver/MB +wake/MGDRSJ +wakeful/PY +wakefulness/MS +waken/SMRDG +waker/M +wakeup +wale/DRSMG +waling/M +walk/GZSBJRD +walkabout/M +walkaway/SM +walker/M +walkie +walkout/SM +walkover/SM +walkway/MS +wall/SGMRD +wallaby/MS +wallah/M +wallboard/MS +wallet/SM +walleye/MSD +wallflower/MS +wallop/RDSJG +walloper/M +walloping/M +wallow/RDSG +wallower/M +wallpaper/DMGS +wally/S +walnut/SM +walrus/SM +waltz/MRSDGZ +waltzer/M +wampum/SM +wan/PGSDY +wand/MRSZ +wander/JZGRD +wanderer/M +wanderlust/SM +wane/S +wangle/RSDGZ +wangler/M +wanna +wannabe/S +wanned +wanner +wanness/S +wannest +wanning +want/GRDSJ +wanted/U +wanter/M +wanton/PGSRDY +wantonness/S +wapiti/MS +war/GSMD +warble/GZRSD +warbler/M +warbonnet/S +ward/AGMRDS +warden/DMGS +warder/DMGS +wardrobe/MDSG +wardroom/MS +wards/I +wardship/M +ware/MS +warehouse/MGSRD +warehouseman/M +warfare/SM +warhead/MS +warhorse/SM +warily/U +wariness/MS +warinesses/U +warless +warlike +warlock/SM +warlord/MS +warm/YRDHPGZTS +warmblooded +warmed/A +warmer/M +warmhearted/PY +warmheartedness/SM +warmish +warmness/MS +warmonger/JGSM +warmongering/M +warms/A +warmth/M +warmths +warn/GRDJS +warned/U +warner/M +warning/YM +warp/MRDGS +warpaint +warpath/M +warpaths +warper/M +warplane/MS +warrant/GSMDR +warranted/U +warranter/M +warranty/SDGM +warred/M +warren/SZRM +warrener/M +warring/M +warrior/MS +wars/C +warship/MS +wart/MDS +warthog/S +wartime/SM +warty/RT +wary/URPT +was/S +wash/AGSD +washable/S +washbasin/SM +washboard/SM +washbowl/SM +washcloth/M +washcloths +washday/M +washed/U +washer/GDMS +washerwoman/M +washerwomen +washing/SM +washout/SM +washrag/SM +washroom/MS +washstand/SM +washtub/MS +washy/RT +wasn't +wasp/SM +waspish/PY +waspishness/SM +wassail/GMDS +wast/GZSRD +wastage/SM +waste/S +wastebasket/SM +wasteful/YP +wastefulness/S +wasteland/MS +wastepaper/MS +waster/DG +wastewater +wasting/Y +wastrel/MS +watch/JRSDGZB +watchable/U +watchband/SM +watchdog/SM +watchdogged +watchdogging +watched/U +watcher/M +watchful/PY +watchfulness/MS +watchmake/JRGZ +watchmaker/M +watchman/M +watchmen +watchpoints +watchtower/MS +watchword/MS +water/JGSMRD +waterbird/S +waterborne +watercolor/DMGS +watercolorist/SM +watercourse/SM +watercraft/M +watercress/SM +waterer/M +waterfall/SM +waterfowl/M +waterfront/SM +waterhole/S +wateriness/SM +watering/M +waterless +waterlily/S +waterline/S +waterlogged +waterloo +waterman/M +watermark/GSDM +watermelon/SM +watermill/S +waterproof/PGRDSJ +watershed/SM +waterside/MSR +watersider/M +waterspout/MS +watertight/P +watertightness/M +waterway/MS +waterwheel/S +waterworks/M +watery/PRT +watt/TMRS +wattage/SM +wattle/SDGM +wave/ZGDRS +waveband/MS +waveform/SM +wavefront/MS +waveguide/MS +wavelength/M +wavelengths +wavelet/SM +wavelike +wavenumber +waver/GZRD +wavering/YU +wavily +waviness/MS +wavy/SRTP +wax/MNDRSZG +waxer/M +waxiness/MS +waxwing/MS +waxwork/MS +waxy/PRT +way/MS +wayfarer/MS +wayfaring/S +waylaid +waylay/GRSZ +waylayer/M +wayleave/MS +waymarked +wayside/MS +wayward/YP +waywardness/S +we +we'd +we'll +we're +we've +weak/TXPYRN +weaken/ZGRD +weakener/M +weakfish/SM +weakish +weakliness/M +weakling/SM +weakly/RTP +weakness/MS +weal/MHS +wealth/M +wealthiness/MS +wealths +wealthy/PTR +wean/RDGS +weaner/M +weanling/M +weapon/GDMS +weaponless +weaponry/MS +wear/RBSJGZ +wearable/S +wearer/M +wearied/U +wearily +weariness/MS +wearing/Y +wearisome/YP +wearisomeness/M +weary/TGPRSD +wearying/Y +weasel/SGMDY +weather/MDRYJGS +weatherbeaten +weathercock/SDMG +weatherer/M +weathering/M +weatherize/GSD +weatherman/M +weathermen +weatherperson/S +weatherproof/SGPD +weatherstrip/S +weatherstripped +weatherstripping/S +weave/SRDGZ +weaver/M +weaves/A +weaving/A +web/SMR +webbed +webbing/MS +weber/M +webfeet +webfoot/M +website/S +wed/SA +wedded/A +wedder +wedding/SM +wedge/SDGM +wedgie/RST +wedlock/SM +wee/DRST +weed/SGMRDZ +weeder/M +weediness/M +weedkiller/M +weedless +weedy/TRP +weeing +week/SYM +weekday/MS +weekend/SDRMG +weekender/M +weekly/S +weeknight/SM +ween/SGD +weenie/M +weeny/RSMT +weep/SGZJRD +weeper/M +weepy/RST +weevil/MS +weft/SGMD +weigh/RDJG +weighed/UA +weigher/M +weighs/A +weight/JMSRDG +weighted/U +weighter/M +weightily +weightiness/SM +weighting/M +weightless/YP +weightlessness/SM +weightlifter/S +weightlifting/MS +weighty/TPR +weir/SDMG +weird/YRDPGTS +weirdie/SM +weirdness/MS +weirdo/SM +welcome/PRSDYG +welcomeness/M +welcoming/U +weld/SBJGZRD +welder/M +welfare/SM +welkin/SM +well/SGPD +wellbeing/M +wellhead/SM +wellington/S +wellness/MS +wellspring/SM +welsh/RSDGZ +welsher/M +welt/GZSMRD +welter/GD +welterweight/MS +wen/M +wench/GRSDM +wencher/M +wend/DSG +went +wept/U +were +weren't +werewolf/M +werewolves +werwolf's +west/RDGSM +westbound +wester/DYG +westerly/S +western/ZSR +westerner/M +westernization/MS +westernize/GSD +westernmost +westing/M +westward/S +wet/SPY +wetback/MS +wetland/S +wetness/MS +wettable +wetter/S +wettest +wetting +whack/GZRDS +whacker/M +whale/GSRDZM +whaleboat/MS +whalebone/SM +whaler/M +whaling/M +wham/MS +whammed +whamming/M +whammy/S +wharf/SGMD +wharves +what'd +what're +what/MS +whatchamacallit/MS +whatever +whatnot/MS +whatsoever +wheal/MS +wheat/NMXS +wheatgerm +whee/S +wheedle/ZDRSG +wheel/RDMJSGZ +wheelbarrow/GSDM +wheelbase/MS +wheelchair/MS +wheeler/M +wheelhouse/SM +wheelie/MS +wheeling/M +wheelwright/MS +wheeze/SDG +wheezily +wheeziness/SM +wheezy/PRT +whelk/MDS +whelm/DGS +whelp/DMGS +when/S +whence/S +whenever +whensoever +where'd +where're +where/MS +whereabout/S +whereas/S +whereat +whereby +wherefore/MS +wherein +whereof +whereon +wheresoever +whereto +whereupon +wherever +wherewith +wherewithal/SM +wherry/DSGM +whet/S +whether +whetstone/MS +whetted +whetting +whew/GSD +whey/MS +which +whichever +whiff/GSMD +whiffle/DRSG +whiffler/M +whiffletree/SM +whig/S +while/GSD +whilom +whilst +whim/SM +whimmed +whimming +whimper/DSG +whimsey's +whimsical/YP +whimsicality/MS +whimsy/TMDRS +whine/GZMSRD +whining/Y +whinny/GTDRS +whiny/RT +whip/SM +whipcord/SM +whiplash/SDMG +whipped +whipper/MS +whippersnapper/MS +whippet/MS +whipping/SM +whippletree/SM +whippoorwill/SM +whips/M +whipsaw/GDMS +whir/SY +whirl/RDGS +whirligig/MS +whirlpool/MS +whirlwind/MS +whirly/MS +whirlybird/MS +whirred +whirring +whisk/GZRDS +whisker/DM +whiskery +whiskey/SM +whisper/GRDJZS +whisperer/M +whispering/YM +whist/GDMS +whistle/DRSZG +whistleable +whistler/M +whistling/M +whit/SJGTXMRND +white/PYS +whitebait/M +whitecap/MS +whiteface/M +whitefish/SM +whitehead/S +whiten/JZDRG +whitener/M +whiteness/MS +whitening/M +whiteout/S +whitespace +whitetail/S +whitewall/SM +whitewash/GRSDM +whitewater +whitey/MS +whither/DGS +whitier +whitiest +whiting/M +whitish +whitter +whittle/JDRSZG +whittler/M +whiz +whizkid +whizzbang/S +whizzed +whizzes +whizzing +who'd +who'll +who're +who've +who/M +whoa/S +whodunit/SM +whoever +whole/SP +wholegrain +wholehearted/PY +wholeheartedness/MS +wholemeal +wholeness/S +wholesale/GZMSRD +wholesaler/M +wholesome/UYP +wholesomeness/USM +wholewheat +wholly +whom +whomever +whomsoever +whoop/SRDGZ +whoopee/S +whooper/M +whoosh/DSGM +whop +whopper/MS +whopping/S +whore/SDGM +whorehouse/SM +whoreish +whorish +whorl/SDM +whose +whoso +whosoever +why +whys +wick/GZRDMS +wicked/RYPT +wickedness/MS +wicker/M +wickerwork/MS +wicket/SM +wicketkeeper/SM +wicking/M +wide/RSYTP +widemouthed +widen/SGZRD +widener/M +wideness/S +widespread +widgeon's +widget/SM +widow/MRDSGZ +widower/M +widowhood/S +width/M +widths +widthwise +wield/GZRDS +wielder/M +wiener/SM +wienie/SM +wife/DSMYG +wifeless +wifely/RPT +wig/MS +wigeon/MS +wigged +wigging/M +wiggle/RSDGZ +wiggler/M +wiggly/RT +wight/SGDM +wiglet/S +wigmaker +wigwag/S +wigwagged +wigwagging +wigwam/MS +wild/SPGTYRD +wildcat/SM +wildcatted +wildcatter/MS +wildcatting +wildebeest/SM +wilder/P +wilderness/SM +wildfire/MS +wildflower/S +wildfowl/M +wilding/M +wildlife/M +wildness/MS +wile/DSMG +wilfulness's +wilily +wiliness/MS +will/SGJRD +willed/U +willer/M +willful/YP +willfulness/S +willies +willing/UYP +willinger +willingest +willingness's +willingness/US +williwaw/MS +willow/RDMSG +willower/M +willowy/TR +willpower/MS +wilt/DGS +wily/PTR +wimp/GSMD +wimpish +wimple/SDGM +wimpy/RT +win/ZGDRS +wince/SDG +winch/GRSDM +wincher/M +winchester/M +wind's +wind/USRZG +windbag/SM +windblown +windbreak/MZSR +windburn/GSMD +winded +winder/UM +windfall/SM +windflower/MS +windily +windiness/SM +winding/MS +windjammer/SM +windlass/GMSD +windless/YP +windmill/GDMS +window/DMGS +windowless +windowpane/SM +windowsill/SM +windpipe/SM +windproof +windrow/GDMS +winds/A +windscreen/MS +windshield/SM +windsock/MS +windstorm/MS +windsurf/GZJSRD +windswept +windup/MS +windward/SY +windy/TPR +wine/MS +wineglass/SM +winegrower/SM +winemake +winemaster +winery/MS +wineskin/M +wing/GZRDM +wingback/M +wingding/MS +wingeing +winger/M +wingless +winglike +wingman +wingmen +wingspan/SM +wingspread/MS +wingtip/S +wink/GZRDS +winker/M +winking/U +winkle/SDGM +winless +winnable +winner/MS +winning/SY +winnow/SZGRD +wino/MS +winsome/PRTY +winsomeness/SM +winter/SGRDYM +winterer/M +wintergreen/SM +winterize/GSD +wintertime/MS +wintriness/M +wintry/TPR +winy/RT +wipe/DRSZG +wiper/M +wire's +wire/UDA +wirehair/MS +wireless/MSDG +wireman/M +wiremen +wirer/M +wires/A +wiretap/MS +wiretapped +wiretapper/SM +wiretapping +wiriness/S +wiring/SM +wiry/RTP +wisdom/UM +wisdoms +wise/URTY +wiseacre/MS +wisecrack/GMRDS +wised +wisely/TR +wiseness +wisenheimer/M +wises +wish/GZSRD +wishbone/MS +wishful/PY +wishfulness/M +wishy +wising +wisp/MDGS +wispy/RT +wist/DGS +wisteria/SM +wistful/PY +wistfulness/MS +wit/PSM +witch/SDMG +witchcraft/SM +witchdoctor/S +witchery/MS +with/GSRDZ +withal +withdraw/RGS +withdrawal/MS +withdrawer/M +withdrawn/P +withdrawnness/M +withdrew +withe/M +wither/GDJ +withering/Y +withheld +withhold/SJGZR +withholder/M +within/S +without/S +withs +withstand/SG +withstood +witless/PY +witlessness/MS +witness/DSMG +witnessed/U +witted +witter/G +witticism/MS +wittily +wittiness/SM +witting/UY +wittings +witty/RTP +wive/GDS +wives/M +wiz's +wizard/MYS +wizardry/MS +wizen/D +wk/Y +woad/MS +wobble/GSRD +wobbler/M +wobbliness/S +wobbly/PRST +woe/PSM +woebegone/P +woeful/PY +woefuller +woefullest +woefulness/SM +wok/SMN +woke +wold/MS +wolf/RDMGS +wolfer/M +wolfhound/MS +wolfish/YP +wolfishness/M +wolfram/MS +wolverine/SM +wolves/M +woman/GSMYD +womanhood/MS +womanish +womanize/RSDZG +womanized/U +womanizer/M +womanizes/U +womankind/M +womanlike +womanliness/SM +womanly/PRT +womb/SDM +wombat/MS +women/MS +womenfolk/MS +won't +won/SG +wonder/GLRDMS +wonderer/M +wonderful/PY +wonderfulness/SM +wondering/Y +wonderland/SM +wonderment/SM +wondrous/YP +wondrousness/M +wonk/S +wonky/RT +wonned +wonning +wont/SGMD +wonted/PUY +wontedness/MU +woo/DRZGS +wood/SMNDG +woodbine/SM +woodblock/S +woodcarver/S +woodcarving/MS +woodchopper/SM +woodchuck/MS +woodcock/MS +woodcraft/MS +woodcut/SM +woodcutter/MS +woodcutting/MS +wooden/TPRY +woodenness/SM +woodgrain/G +woodhen +woodiness/MS +woodland/SRM +woodlice +woodlot/S +woodlouse/M +woodman/M +woodmen +woodpecker/SM +woodpile/SM +woodruff/M +woods/R +woodshed/SM +woodshedded +woodshedding +woodside +woodsman/M +woodsmen +woodsmoke +woodsy/TRP +woodwind/S +woodwork/SMRGZJ +woodworker/M +woodworking/M +woodworm/M +woody/TPSR +woodyard +woof/SRDMGZ +woofer/M +wool/SMYNDX +woolgather/RGJ +woolgatherer/M +woolgathering/M +woolliness/MS +woolly/RSPT +woozily +wooziness/MS +woozy/RTP +wop/MS! +word's +word/AGSJD +wordage/SM +wordbook/MS +wordily +wordiness/SM +wording/AM +wordless/Y +wordplay/SM +wordy/TPR +wore +work/GZJSRDMB +workability's +workability/U +workable/U +workableness/M +workably +workaday +workaholic/S +workaround/SM +workbench/MS +workbook/SM +workday/SM +worked/A +worker/M +workfare/S +workforce/S +workhorse/MS +workhouse/SM +working/M +workingman/M +workingmen +workingwoman/M +workingwomen +workload/SM +workman/MY +workmanlike +workmanship/MS +workmate/S +workmen/M +workout/SM +workpiece/SM +workplace/SM +workroom/MS +works/A +worksheet/S +workshop/MS +workspace/S +workstation/MS +worktable/SM +worktop/S +workup/S +workweek/SM +world/ZSYM +worldlier +worldliest +worldliness/USM +worldly/UP +worldwide +worm/SGMRD +wormer/M +wormhole/SM +wormwood/SM +wormy/RT +worn/U +worried/Y +worrier/M +worriment/MS +worrisome/YP +worry/ZGSRD +worrying/Y +worrywart/SM +worse/SR +worsen/GSD +worship/ZDRGS +worshiper/M +worshipful/YP +worshipfulness/M +worst/SGD +worsted/MS +wort/SM +worth/DG +worthily/U +worthiness/SM +worthinesses/U +worthless/PY +worthlessness/SM +worths +worthwhile/P +worthy/UTSRP +wost +wot +would've +would/S +wouldn't +wouldst +wound's +wound/AU +wounded/U +wounder +wounding +wounds +wove/A +woven/AU +wovens +wow/SDG +wpm +wrack/SGMD +wraith/M +wraiths +wrangle/GZDRS +wrangler/M +wrap/MS +wraparound/S +wrapped/U +wrapper/MS +wrapping/SM +wraps/U +wrasse/SM +wrath/GDM +wrathful/YP +wraths +wreak/SDG +wreath/GMDS +wreathe +wreaths +wreck/GZRDS +wreckage/MS +wrecker/M +wren/MS +wrench/MDSG +wrenching/Y +wrest/SRDG +wrester/M +wrestle/JGZDRS +wrestler/M +wrestling/M +wretch/MDS +wretched/TPYR +wretchedness/SM +wriggle/DRSGZ +wriggler/M +wriggly/RT +wright/MS +wring/GZRS +wringer/M +wrinkle/GMDS +wrinkled/U +wrinkly/RST +wrist/MS +wristband/SM +wristwatch/MS +writ/MRSBJGZ +writable/U +write/ASBRJG +writer/MA +writeup +writhe/SDG +writing/M +written/UA +wrong/PSGTYRD +wrongdoer/MS +wrongdoing/MS +wronger/M +wrongful/PY +wrongfulness/MS +wrongheaded/PY +wrongheadedness/MS +wrongness/MS +wrote/A +wroth +wrought/I +wrung +wry/DSGY +wryer +wryest +wryness/SM +wt +wurst/SM +wuss/S +wussy/TRS +x +xenon/SM +xenophobe/MS +xenophobia/SM +xenophobic +xerographic +xerography/MS +xerox/GSD +xi/M +xii +xiii +xis +xiv +xix +xterm/M +xv +xvi +xvii +xviii +xx +xylem/SM +xylene/M +xylophone/MS +xylophonist/S +y'all +y/F +ya +yacc/M +yacht/ZGJSDM +yachting/M +yachtsman +yachtsmen +yachtswoman/M +yachtswomen +yack's +yahoo/MS +yak/SM +yakked +yakking +yam/SM +yammer/RDZGS +yang/S +yank/GDS +yap/S +yapped +yapping +yard/SMDG +yardage/SM +yardarm/SM +yardman/M +yardmaster/S +yardmen +yardstick/SM +yarmulke/SM +yarn/SGDM +yarrow/MS +yaw/DSG +yawl/SGMD +yawn/GZSDR +yawner/M +yawning/Y +yd +ye/T +yea/S +yeah +yeahs +year/YMS +yearbook/SM +yearling/M +yearlong +yearly/S +yearn/JSGRD +yearner/M +yearning/MY +yeast/SGDM +yeastiness/M +yeasty/PTR +yecch +yegg/MS +yell/GSDR +yellow/TGPSRDM +yellowhammers +yellowish +yellowness/MS +yellowy +yelp/GSDR +yelper/M +yen/SM +yenned +yenning +yeoman/YM +yeomanry/MS +yeomen +yep/S +yes/S +yeshiva/SM +yessed +yessing +yesterday/MS +yesteryear/SM +yet +yeti/SM +yew/SM +yield/JGRDS +yielded/U +yielding/U +yikes +yin/S +yip/S +yipe/S +yipped +yippee/S +yipping +yo +yodel/SZRDG +yodeler/M +yoga/MS +yoghurt's +yogi/MS +yogurt/SM +yoke/DSMG +yoked/U +yokel/SM +yokes/U +yoking/U +yolk/DMS +yon +yonder +yore/MS +yorker/SM +you'd +you'll +you're +you've +you/SH +young/TRYP +youngish +youngster/MS +your/MS +yourself +yourselves +youth/SM +youthful/YP +youthfulness/SM +youths +yow +yowl/GSD +yr +yrs +ytterbium/MS +yttrium/SM +yuan/M +yucca/MS +yuck/GSD +yucky/RT +yuk/S +yukked +yukking +yule/MS +yuletide/MS +yum +yummy/TRS +yup/S +yuppie/SM +yurt/SM +z/TGJ +zag/S +zagging +zaniness/MS +zany/PDSRTG +zap/S +zapped +zapper/S +zapping +zeal/MS +zealot/MS +zealotry/MS +zealous/YP +zealousness/SM +zebra/MS +zebu/SM +zed/SM +zeitgeist/S +zenith/M +zeniths +zephyr/MS +zeppelin/SM +zero/SDHMG +zeroed/M +zeroing/M +zest/MDSG +zestful/YP +zestfulness/MS +zesty/RT +zeta/SM +zeugma/M +zig +zigged +zigging +zigzag/MS +zigzagged +zigzagger +zigzagging +zilch/S +zillion/MS +zinc/MS +zincked +zincking +zing/GZDRM +zingy/RT +zinnia/SM +zip/MS +zipped/U +zipper/GSDM +zipping/U +zippy/RT +zips/U +zircon/SM +zirconium/MS +zit/S +zither/SM +zloty/SM +zodiac/SM +zodiacal +zombi's +zombie/SM +zonal/Y +zone/MYDSRJG +zoned/A +zones/A +zoning/A +zonked +zoo/SM +zookeepers +zoological/Y +zoologist/SM +zoology/MS +zoom/DGS +zoophyte/SM +zoophytic +zounds/S +zucchini/SM +zwieback/MS +zydeco/S +zygote/SM +zygotic +zymurgy/S +Ångström/M +éclair/MS +éclat/MS +élan/M +émigré/S +épée/S +étude/MS diff --git a/docs/hunspell/libtorrent.dic b/docs/hunspell/libtorrent.dic new file mode 100644 index 0000000..55231cb --- /dev/null +++ b/docs/hunspell/libtorrent.dic @@ -0,0 +1,638 @@ +' +'s +libtorrent +API +APIs +ABI +SHA-1 +ed25519 +const +BEP +BEPs +bdecode +bdecoded +bencode +bencoding +bencoded +int64 +uint64 +enum +enums +struct +structs +bool +realloc +merkle +hpp +bittorrent +bitmask +bitmasks +SSL +asio +uTP +TCP +UDP +udp +IP +IPv4 +IPv6 +QoS +TOS +unchoke +unchoked +dict +kiB +MiB +GiB +DHT +LSD +adler32 +LRU +LRUs +UPnP +NAT +NATs +PMP +arvid +Arvid +Norberg +RTT +internet +TODO +UNC +plugin +plugins +symlink +symlinks +CRC32 +CRC +UTF +bitfield +RSS +rss +socks5 +socks4 +metadata +posix +downloaders +downloader +bitset +kB +hostname +indices +dht +lsd +noseed +BFpe +BFsd +i2p +async +uTorrent +pred +sha1 +sha512 +pread +preadv +pwrite +pwritev +queueing +readv +writev +ftruncate +iovec +uint8 +addr +iov +reannounce +PEM +pem +dh_params +outform +pex +trackerless +sig +ip +HTTP +URL +URLs +username +auth +idx +num +passphrase +UUID +UUIDs +uuid +performant +preformatted +SHA +buf +bufs +sizeof +params +ptr +msvc +mutex +eventfd +uint32 +HWND +IPs +CIDR +kademlia +userdata +dont +OR +ORed +Diffie +OpenSSL +openssl +libtorrent's +filesystem +filesystems +url +fs +io +ssl +errc +dh +dhparam +dhparams +0x01 +0x02 +0x04 +0x08 +http +failcount +superseeding +foo +baz +JSON +HTTPS +v4 +v6 +upnp +x509 +process' +crc32 +mtime +fallback +accessor +utf +str +bw +trackerid +timestamp +prioritisation +filehash +len +partfile +prepended +vec +dir +ut +ih +ec +cb +cid +mj +prio +src +'put' +'mtime' +'fingerprints' +'query' +'ro' +pre-partfile +pre +GCC +prioritization +nullptr +nothrow +precompute +recomputation +RPC +unchoking +ep +nid +crypto +URI +URIs +uri +infohashes +rw +holepunch +TLS +RC4 +Hellman +html +namespace +dn +pe +lt +tex +natpmp +cancelled +bitcoin +Jamfile +Jamfiles +Jamroot +NDEBUG +Solaris +BitTorrent +BitTyrant +macOS +int8 +int16 +int32 +int64 +uint8 +uint16 +uint32 +uint64 +Castagnoli +CRC32C +multicast +Linux +kqueue +epoll +LEDBAT +DNS +Nagle's +ACK +ACKed +getaddrinfo +ethernet +gnuplot +peerlist +MTU +ICMP +VPN +DSL +CAS +cas +IRC +PPPoE +cwnd +fullscreen +screenshot +prepend +RSA +DSA +curve25519 +nodes6 +nodes4 +serializer +github +Flattr +Wallin +Reimond +Retz +BEP5 +toolset +Wojciechowski +GTK +cmake +libsodium +nightcracker's +Siloti +Magnus +Jonsson +UmeÃ¥ +freenode +irc +hydri +BBv2 +DLL +gettime +toolsets +libgcrypt +LibTomCrypt +CommonCrypto +cygwin +gcc +iOS +memalign +valloc +malloc + +libcrypto +libssl +libeay32 +ssleay32 +libc +MinGW +config +xbt +tarball +cd +b2 +utp +stlport +IETF +BitSlug +BitCo +Tampere +CXX +gui +Multiprecision +gcrypt +peers' +wchar +ccmake +fPIC +Unix +ipv4 +ipv6 +reqq +homebrew +endian +gzip +KTorrent +Azureus +btih +der +Spek +darwin +dllimport +dllexport +stdlib +Wyzo +bjam +messages' +utorrent +zlib +MooPolice +LeechCraft +FDM +Folx +Tonido +Dumez +qBittorrent +sledgehammer999 +ntx86 +Tonidoplug +nodes2 +NAS +Tonido +localhost +yourip +declspec +gtkmm +btg +ncurses +Tvitty +Bubba +TVBlob +ISPs +ACKs +uplink's +Lince +hrktorrent +Trolltech +msg +donthave +png +DelCo +Torrent2Exe +ZyXEL +NSA +NSA220 +tcp +rlimit +screensaver +GPL +GPLv2 +routable +L1 +L2 +rst +org +DF +TVblob +FatRat +DAAP +fno +BLOBbox +GetRight +py +pyd +cmd +Strigeus +ludde +fpic +programmatically +unchokes +ethernet's +BitTorrent's +IPTPS10 +loopback +DLNA +EXE +Skype +lru +dll +exe +foobar +256ths +leechers +printability +podcasts +todo +0x10 +0x41727101980 +0x7fffffffffffffff +2410d4554d5ed856d69f426c38791673c59f4418 +E4F0B674 +0DFC +48BB +98A5 +2AA730BDB6D6 +0x0 +0x20 +2e +373ZDeQgQSQNuxdinNAPnQ63CRNn4iEXzg +BT +ID's +aio +btfd +d1 +d11 +de +e1 +impl +md11 +metadatai0ee +metadatai1e6 +passwd +pexi2ee1 +pi6881e1 +pimpl +pre1 +recv +requester's +seqi +seqi1e1 +txt +un +v12 +v2 +fuzzers +fuzzer +libFuzzer +clang's +prev +mmap +hash2 +infohash +v1 +Dreik's +ctx +unicode +peers6 +DNSName +SubjectAltName +SNI +httpseeds +Base16 +lsd +xt +netsh +GUID +NIC +tun0 +eth0 +eth1 +lan +NOATIME +INADDR +supportcrypt +setsockopt +OS +portmap +QBone +SNDBUFFER +RCVBUF +QBSS +DDoS +DoS +anonymization +Tribler +gzipped +processes' +versioning +cstdint +cloneable +inline +chrono +hunspell +online +dic +fallocate +strdup +istream +ostream +nonrouters +backoff +atime +wildcard +sk +OutIt +OutputIterator +nat +pmp +https +RemoteHost +ExternalPort +ret +pos +os +bt +cpp +tos +BP +qB +LT20B0 +iocontrol +getname +getpeername +fastresume +InternetGatewayDevice +netmask +fe80 +vcpkg +leecher +6881l +NOTSENT +LOWAT +Dstatic +DOPENSSL +DBOOST +CANCELIO +fd +socketpair +fileno +SSRF +IDNA +toolchain +distutils +virtualenv +virtualenvs +pyenv +tox +args +destructors +fclose +fopen +cxxstd +uri +https +wolfSSL +wolfssl +sni +nginx +SIGINT +GNUTLS +libgnutls +0x200000 +golang +img +SetFileValidData +Qt5 +DownZemAll +LGPL +OSS +ccache +compileflags +cflags +cxxflags +linkflags +lto +SetEndOfFile +IDNA +MongoDB +SSRF +NoSQL +SafeCurl +OWASP +AWS +CryptoAPI +FuzzGen +archive +alloc +bt1 +ff45 +F1 +F2 +F3 +F4 +F5 +F6 +F7 +I1 +I2 +I3 +SSD +DAX +WebDAV +transmissionbt +ouinet diff --git a/docs/img/bitcoin.png b/docs/img/bitcoin.png new file mode 100644 index 0000000000000000000000000000000000000000..25e9f15ceb2a616525cdb6f08091b494f8908e81 GIT binary patch literal 2611 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
    #R#X0fJ7b{AkTJn== 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 gesbs>QD*ylh literal 0 HcmV?d00001 diff --git a/docs/img/hacking.diagram b/docs/img/hacking.diagram new file mode 100644 index 0000000..d9604ef --- /dev/null +++ b/docs/img/hacking.diagram @@ -0,0 +1,30 @@ ++--------------+ "pimpl" +----------------+ +| "session" +--------------------------->| "session_impl" | ++--------------+ +------+-----+---+ + "m_torrents[]" | | ++---------------------+ | | +| "torrent_handle" +--------+ | | ++---------------------+ "weak" | +--------------+ | + | | | "m_connections[]" + | | +-------------+ +--+ + | | | | | + "m_picker" v v | v v "peers we are connected to" + +----------------+ +----------++ +-------------------+ + | "piece_picker" |<---+-+ "torrent" ++ +--+ "peer_connection" ++ + +----------------+ | ++----------+| | ++------------------+| + "m_torrent_file" | +-----------+ | +-------------------+ + +-------------------+ | | + | "torrent_info" |<---+ | "m_socket" + +-------------------+ | | +----------------------------+ + | +->| "socket_type (variant)" | + "m_peer_list" v | | "(TCP/uTP/SSL/socks5/...)" | + +--------------+ | +----------------------------+ + | "peer_list" | | + +------------+-+ | "m_peer_info" + "list of all" | "m_peers[]" | "contains contact information" + "peers we" | | "for peers we're not necessarily" + "know of" | v "connected to" + | +----------------+ + +---->| "torrent_peer" ++ + ++---------------+| + +----------------+ diff --git a/docs/img/hash_distribution.png b/docs/img/hash_distribution.png new file mode 100644 index 0000000000000000000000000000000000000000..7bc1be79e5adb6fd1c53182f67b2e971573404e7 GIT binary patch literal 9035 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 literal 0 HcmV?d00001 diff --git a/docs/img/ip_id_v4.png b/docs/img/ip_id_v4.png new file mode 100644 index 0000000000000000000000000000000000000000..55d02390e9b2965af7d83816bb50bea2e778236f GIT binary patch literal 5825 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 D8#_6^ literal 0 HcmV?d00001 diff --git a/docs/img/logo-bw.svg b/docs/img/logo-bw.svg new file mode 100644 index 0000000..9bc0f2d --- /dev/null +++ b/docs/img/logo-bw.svg @@ -0,0 +1,112 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/docs/img/logo-color-text-vertical.svg b/docs/img/logo-color-text-vertical.svg new file mode 100644 index 0000000..2184ffa --- /dev/null +++ b/docs/img/logo-color-text-vertical.svg @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/img/logo-color-text.png b/docs/img/logo-color-text.png new file mode 100644 index 0000000000000000000000000000000000000000..edffe3703395815b5976cc8ebbff6c0d0422eb85 GIT binary patch literal 17420 zcmX7v1ymK?7J%ulOP6#b-QC@d(yap0-6h?PbaO#Mx-T6f-JQ~1(r^6VtOWye7pytw z?B3swR9BTlMeES`>{+e zu6>Vj2Rv2KtHnzhTucNC9V=C*Y_V1X zFUSS57$79$CLSZfX-^zPGVXmYZBiW{A3w1|mN}gKc8vNK0g{mBgy};^=KLW2htRSs zBGXp7l!T)(n30Q01BAm)Rc*R^;Soy=!sAeo*p@9GLcv~Q8O8^Bsq}YzN%$c$|GSFfbo0`*vJ)J0VkIFh1j}RLhgsUz* z4KBXCnC37T~Qh!LJLR!&l@pGRR;DPfXE zK_z*fn=A@k_~FuNcR=54J4y=D5~>oBK~h4aE*(ii{YMr#lR`Fzo~m6UOJ-LhL^!Ng zkJJ40XwXCD#_tli1oi~BV0NQ_2c+s~2n;e}TegC(C8(VnO^>kTw>UOw4`%qC*d)HF z>s!KwkxKD}qYz0T5vG^p6bS1p>eOamnG$L6#ekCGmKZKvKrVR^cDU+T)n7dK6*}-l zfy;qSwH)fdz z1Lw19s#w*=7{bZhWu>;!kT2!- z&d!bt_l~GXz-y+M6x2e?4BSHu=-LcaAzy?hpX9HSH2D4CnCx32A=+M#W+?%WCV2(z zNhp**8`T@#u%CEMgyYigF}(R!GhC2`JTQX<^kgEGqM$X) zvN!pYBqTKPz?&!ApJ!#kPO)4R!))TOpc@{Oh`+$4KPVIUyp-HbO-Q<Ay;eAhn&z1V5Wl|53!F{G1Z15Wnr{1z4efZeOZlb4y&#wl76IM@NilQ`3;j|N0w+p?1njcM@8G zro%44nfdLLNOF;zE4@#P!_b6k2A0!+mF$%EpVV=xBU$0ieTYfKusg`?^Xc4xH477oJ;66C#_-6J0H}B&wUAohXe?^Yt zmjJbDNq2qf$MvbEJk4@CoXy{4z`%7;JtSo<=g&KonPG5_#!57sItZTpezaHVusSfl zZX{2~G7^bJ6QxPUu-6*}51bU%u2m>q%6<$e?XM)DnEPnvWA!KWWDo4sDl%6eBwm{d zacDQUTgO{mhqdAh#X8*t)|Z7%**q>!G@*9J{^|#1;DIHa7y1@XxiI};ZNuHDlTi9ULsl`C0XID4e@#ic z!yjCQwM!n8K_w|ch`7b^$u>hzFrC@9#?*U?+i=I)p4=3^Cm+I(B{qE4C314OB4r!E zEac-coqV*HHu(L6zG$9^AB)}j()fI?{RzpZ60tc4E~Amlb7frG?a5Ml5Cr2pO-d?YE4p9>*VxPgAzt8vH8 z-{V5S{qB_J)GK3W5m3Of$e*6m@PO<|{0*bXgIi9Uf4IW}WP0X%u=NC{h%a+o#uC+) zMNjtrx89bmVW%8@jbOTAJf3tX`!$~aRekpbvwkeKA+Jss*vk7FGuwMzY-3K6dkvfl zGw0&CBynU7ddb;X4Qz(e_xiFQ@>BFFtdpsGH4r>N1koum1NH216DH#fupITP@C#_Y%7$;6ZG5~-TH;v-2MH|l2Qb=x}eDUYms z(VnEX%E4tz!r{h7t$a-R_>w8$?Th#rM!^Ff1k*7Rv{9d`gA}Q5;=L&Re0|s5cX27s zpY~WH2#dhYlpQOC1bn3L>M1{J6sqC|cDa%deB}sW-^0h(F;00g#7Z(wvRTR9sY@GL zBo_;0W*rDJAB8E=4rOA%Ow-uK$8qoF-_G~!pF#H4>U52(?0oTAT4|2oXzo_* zkNs!s;H|FzsZf?9x-43*cR5qCWO7T%CBAj)*!Gr4x4Fl8t_9~$)ZVTj*pB&21On~% z5v_v{CY>X>dV#kVUzH!;5C0a3BUJy3cA;3H!pl=5F0GMz2=NksmjJg8Gao%#0Iaua zth=%T;Y^~0b%-Or_}zK*?uzWg-@hHFBb~4sm=j8Dpu`CQ{vcESD5t?cP9@-3IeIO! z_xrKczx!7S&vUNGfc0{Z%}*sB?CSaW|E@<2K!o)Egdbzc`8BIgom!yKzsdlOBc3pJ zkC7gusUv)QpmJZ=YRhGNS7W6#q5G)yVt2USJD>ZbYQ6j0HPg7&WwBCu^sLni2UWH~ zSHiL*I_8;w#O-N~CH%7f(sX@DlsXYt^mrD=GkR}c)zXva{m;6JpVlzxyMKi-QgD~` zt@kyGnLJ?L&@%*gyY6AwVBcsij!XXtx$Py@?0sQ{GeqM4!*5NZ%X5T^1*lTC2K+D! z=6VE$75EW8e-J&~DWrgdrnPzzsa9YW|$qeq%j|7koRCf8%HbH&^| z)|5%p+%+C{rJVYzUbQ!}DVPXri(roRWZz7Btr&Y}_n>a5R}>`D%MWP!2+!%gay>o` zfp=FGlZs|*)V^p8u?R8wad@yulyg&C2T-Rpf zn>CpzNycESjwQUiIo*<5Ip6zn#zM$3PZm5>9TVHvM}4}4-Hbqa63U2~HbrP_={{m- zjW?0csV$()gXdQGzQ%ZPX`tTI?%cuabuyZ!lSURT96Q|8bE0N(wl&gY-elq=sU8HU zCQ_)H;=#8r-;aJR|I4D-P1za&u7G*2@7p+EtLL?I zA*ZA)!rwGeeaUt|H&vsnjuMq3~!XHgm-W4ievTu(ll7 z>+M4`XX7WP?)7V=)5yE3g6H37&7an;jH-XMXYI}XLdr7bjrIAQk4tSm24m?+>VDU# zzM%(b$BUhe1ES~zVeI-&PpXd|gtr-?p@qRmR1FCJ3FRdeT+Z9iNsDJtm_28g7{*}=DQpj*2mS{ zpJM~Ej^n$YzO({xx$n6w8b++VZT{CwEW+074B-L5y!!ubgu{`tL#R*w01zG;Z= zZ%Lu){MHNWDW!|EjbTTQv>^It%1yb62I0rv5mxC`qRvBjGGjR99s&v=e1&&Tf$~~V zs~Nbyu>-wW47FfK;>I6ka%cGOMrHj1f&OU*;49Db9*q&EVTJ3?6=3Kfx3}l{bcV5I zWWiYX+rX(x*&VH>fatdf6@p{TzpcJ<|Omj1#eH zwO@cT#ywUFzed>*k zJ8DVQi0>pDD)z-`a<@1vaL9^F;BDisV`S)k>=RQffmJWl1Q{y4+{v&B; zEUUAQ)aX->RRzAzmYns9)^&|GD;f(|k}J_2t_N1rS!C(TU1VeiKJL?mKd6tSzv{Zq zaruz>9sYbwZbrR4NjPV^xDd_XD|S2Ur-WEiWbI^d*OJI9yW05qs&cYUihuon096|f zLu&pFIiWJZU4YEEsJz1@Ch=1-L-MWrF72At+^<&G=@MU$UQ+R-|k80#HQ*1PH*~4`SJ0iXOS(J z(35+eLw+j3ig6+3$BBVtd?)`0uRh^2z{ngQRhO`84!yLKnVJMtsBUEusbG* zWjLc=EADS^T`C`c0m21qZN9ahchjB}Nbe$9-S1DWbT1o-Q!t4+j!|0~cLpqUJUEKH z24=+R)wA|576Q@fD;?+)C@$EpYH=iIeq(dEO&yg(k9oN zHn!N8Fn%NxqD@O415W7%UM-D1%1q0dc1-GQ=>n-RL z6sv4Lu*Qj@mIKBKGXx!J{MKK&P~7dVHbgtZ>!_2!u`qGl?ssf~=g}0F?Pl7b$4usd zdcAi%Jht-!DcfXv6I*P=SNy-6wdWBNNsvlNS&)hJ z%#I1RXYjBM{H2AC@3`evC;3MR2Lub+c`iP^2)xhKHtYl&8ZuS1OqkX8Me`H3Ek@Oz zX-Die^SFiTh+R_F?m$!efy%9u#DJG}IQZvL;hm=t5N%WLm0Zm3gf$U7%%`0@F8pct zGF=EcIa*5TesNo_2jZ9%alfzA}Kg8GU8~xkJ!TQ?N9tes4Nxf!r z-=kgZ9deTKRWVAugl3kt$AE`%ST&1dru+;Y83woGw{h`hqPTaK`Se8s|JgUnd8A$M9F`4>1H$WJf^ zR~9E*6Q{*F_S5q`S~{wb*qXb0bGy<`s?FFh92_=8$=03>&Xzi&o;*Z`gZ>8DMY*Yo zM6FSM{n`;pLh_a3Nc!ig{rOUlN3(xC5UpM4-sAhXybPV3t%y)cQjtIHgafJ$?Oj(` zzi{$4ryvp_8&O>QQ2&ba*Jtw$N(|PWxl1kyaCwU(ipUeiQ5r6(7HEl%QwiBxnG^#kDuOZV@m^O24kdonG9w1>1y8Ii{sCcJD0@lw*Bl|HCGoY1~S9E9mp(+j=vO&h;p z+sTUQ=R!bIn#unHL0*q;fhu%RfzVuEf%Fq&>osA2UlEUgLPM!cDsv2V(2m1@B^Pri z=+efy{5X$9D!c-5xCJ87Zm%N_7I%CgenDL1rg%d5m}%t9s})@eGnxCzM($iyF{EUS zAuAV8{IkmSmmymk&erlPv;Kq&Y~iIx?$bz&uU|X~*cv+fi+spQIC{1!-t&K(&ONZC zct^h-I-ymP*1N-{ikg*mZfV(`4b?kJYm#%on!9;Cfdfg!*JTxJv{MggyHiuJ&1xw9 z(CWLVAh2(*w*D=oZ%q$hJFk@DDz?+papGs>M!`ni8HVsXGf^C~y=|@ICBlAq^`7#h z0T^ry*?3J8B98Z9LurNl(BgDUseM@>Jn>%=riyDsK0jNLk}C{pE!kHGRf!{NrO_-e zO?SE}yYV7~^*-L|Ev_|(k6Z3@SPhBj5Ncbv(fS|>6^txqYthRDR(*O!JA{GV95VRL z^E_}1g=~gu4bK@M5B0KHf*Ai!%Bd1G4xO8T%`zN9lj>kDkbBAh(;mnD5lBKunrzP9gL>=ZE>^gY4B5>p#_w4_QWMj3Lv~_>m)W}Tu z`QwG`CpA-L9e%lk_`WT3d(}VOPGQh!hXq6A$9`gI`=1Xygsp0^Bu(YXwaUH2<~tbhwR%Z_$>;qeSK&syRY~`*K2YO9ZtCf`58JxzY(O&DX~Q zrm;TC#%jFutfir%havPtwd#AC%( zw2t>orz*dhFNFVkn^pD_K^l5YV&tHXhjOE_9En=5Lr>v&$!FZiOR+N$v(M3s|5zNC zYg<;E)UwXM3qI*VaOy&OCX0UwIVIh(`~yY6?tD>hp2LViwq(;Il?YbOo9K~!FIW2k z#ogwyMPg^XM@^(XxW+`V$^m^bY)bwF(T~UFjCzP6rZXIs^DWILP6(+Z(vP>vXMV3^ z^HR@Xs{opx+@Q}IzByBChV#IIg`y{F-JCZQ9S*eWB-~N8PmZ=EFU%zzK zThy+rx{v8d%h1E323=2@oy((q8bavkg6pW=nu|aE@}=qbegLmF=gvpJzkifyn_{F` z72#VE4A?$#^TPO?U`RLOyGHGVBDEBlwPWf4K_e-XBp%eiC6K%+$vN!j_goRxH_Nh325?Br$s1U&ZkJH zR&9_>X8Zwtpoc}Y{+{Cgv;Rq78`-huvz7(_;;b({^L0D^8%NXpi2Bu122-HU?w#8k zQ&XIx1nIg2W#Jh7I${T^kP{xkqB8jfU2h+xGx+`saIknfXCDMhN8Sg=4tBeSP6%%% z2fVaTyydSQOXv*0_qAcZlGvo%w3E%X^bX{sNqI#Xy^s5Q;%(aS{T4Ul9_mFZR1Wz< zbu$Dqu&8i7Y(onBh| zAdP7TxfLm+yfc1ApJ%(TyD5H@|BRxOQvjaYao`Xmx+H;Ax4FE;q@x=2&$!}TKwa1@ z>iAPyy|;j9p3vOjY&u(HOD1)<4mq*}`$am(%Jn23so2*0+2QUfL23fo%;{4k!w?sr zjrPsye}&^+dx+;mp^3;*?zVLyu|KJ|VKzSAnFoo%lMN0c=1d$g&r+Ry@qa`Dd#6tg zITNd|&-x-(&*i>H@Q3n6Y=@Lv?b^@;t~Ihy4~f`+G3H^u+Q}sb^I749w{u zupZn@k6K5N&dSo8oPBPU99pwn7PxvWf*`77U5z<$1qP5zPYCSdlZ9AtkdSoWLS2M2 zs6w9)$)Edo>9kV5`EH?+MSwlhzyv5QJ!Vt;U`4Ciq(S|nwa=akcQ=ijQA!pKJ#CxT z!Ub(wYbkJ1j2O69r{sPWqRZRis^5+JFF7~SvDE0HkF*=@3ZGnpTZl<+jXx1TP&?In zRrw!yRm4k5u#JS8J^ev;M0X^2zslppP~n3zf$~Q)3=gFnb)NBKW|wX)=;S z@|ocU(gu3XwdK=;pAF7R8I`N8Tp_8$(V*-=A|*TwDRuKOKe=&cxzO6=!qt7FBd~#e zi;GQVYR(!ACbB1Gg@{rwSnSlC3CH>8!d6LtG7sLsbm{RR$p2JSi0v5CN9QB+Ifh4l zds#+1z&kMHG6V=X=eGJ5)^kqia^tkH87M6!m$2(aM##K1MIQFnhyp2SI&huX;tKbo z{>;pD8!^(peF0<9S?GNxmzDlXzJl^xoZ#hzZvIO6-Qgr@$JR-j6}BtH{Q!J=P2~xuAwM`eT34ToO@@J+?oNuYOl?P&r-Xn@YNd1!(vX(V()&@ZqT9! z`V8t7S?Dr0jCC_?7-XAN`bTUC1V7ifA$KXrfpz-t=3HiwQWs zA$OKov5Xt6*sE0dNqs6R@@!%y^yB8aOhx_IQaPtWU3vj}G)!?z0o~tDocOTywcjq6 zH3rSz&n5`1`LX5>Fpp7xL4q+HsH?DRf^m3#N_z9?&wIK|aN)GjjJSw%SxmOWnddhd z-o=Fyt#sUC1S{sC`WngwV5-heD?u6JO~5lEa8;eP7D{Kc@Adq&OX};=W&^f#7a#qX z_xFpFYg}##X&QqY#z8Mmp~rjTy>>U?KRMEL8@jru2y)^h0b41TT~V z_2`HChNft2f(uMLR;>tbR|=uENa|ZPwJ{d}8&kmEF%X4U;3dBVz0mM0*C! z8!#{E%$a#m9y3nnc*eMz+1~Zn!BoaP-})ecHvI~#wuf!zzv?x!KffTE=Cv9wtc3fC zO$)>cAZ>!dO0qWg{gx(bI@e)!Ly6fh_% z&V&dxCvS)GNYDgrnBwq>mQ6N&aq_!TGhS5SKZL85v_!txsFl6YTm1V{s&Xzhe{8shjkVfw`^wa9IMDE=^Y(S)+Xk16$Q>{><2~4wDXN@z9;8&W!c>eOxq9<@hea>oR#!;kFF21F;EuQ}quL#A$&}lI!Yx zVR#AYE~jM3YCebx_X}DmO~8l^d2_F4m#Gl6XFMSHeg=wnfslOfKM~!iVODAUT_B!qOpegy>>!wxdujti}pk`I-FcT>7i_9&^n0vLFJkC)!`h zf$d*;Vhv}9Cx{_0FSBiUgtTF?V;f%C`>^d-Z`it~Zmxh(0OW3QD)!`&kpuPgZ1hA-`j& zbe8iG`}Csc`|0uQlmoP&<}``iTOW-3ce2vleicE~SZ&FN%qB4#SqR2$9gxmJdO4hNt0on~z=-nBQtI~nwq?fdsb=PZ-x~9c zm3_advAO=sp$L4VNUo(eGwR5B)6@Hld42jlX+il(g^mcFHN(993$M%u(9lsu5}fxy zh)-H}w)Uxb`nSR%<|=TVP_3aI{@mITehP{WgHWBMFLzA~!-Ku{&ZvmeIdHfGU#k|h zsvG#B=^)idrI$%@S?-wemfkI2*mC1`D$?*&ufju{XAi&k-ehN%g`Plu-=1NLp}Ca@N&XNfCL1D34Vsm4Wj-&ZG3!`1 zz;L&IqG^6NZUv+Ju;{aD0CA%COLBWBqtl?Hqfq<}HR07&Oo2^d5-uDyN}KQH(4Q)k zcuyvXN)eX!+h*Qs=U0g7O=;9`q^y^ zDM*%%Q8JS--HEDWeyzVEO+#oZ7m%ggFYe*0F+*=`#fDCE;h%fVltzCLxnni;oyk(> zCTrEP#rbyVjt*`!n4``;nIN8)30>FTV87KGi`B$tD4<^g$__I}*IdxyKud1hI6JMY zGn|B=Z+0<7>!cPUabr-2e@9iZIK25fI!+O_w56xUP}6 zT^$TFTPLF3q?u?ByMD&8a#2k0PoX>{;lYd#m##x6&j;4HYt`V9SLr-A4q7c4wlz7M ztJ-`dAv18F4q7qd;zGb8s0S3zsPUcLUhX{3?AuL6T?KBdj7|9AvA#(1LC$(H8RK)%v)^}`=MY$2a8R?;{P4kv$52tH%@2H#J7NE#DT(QhhI zfiH94pZtPWjPSFLJlFf=Xhp7 zsu2%X*Sso91M~Fi);-Clm1G0q3EPlk}mVGKDZC;*$ZO?HW>TawfIuF*G znN;H|)I@Gx)LzfGlU@RfzTuRHTS0MNx|tCdAH+7s$Q0`Z@qY`;JDX_xqZ&7_fm3b0 zAM2K#c&MWU7rR|$a_;Z!9ho)qvipv{t9S>ykCE_Ue!t|$F0-uEv z_+ouz!L=W2+iMJAib;>@-c~*HR`vQZhu{xv&ndJuZ%7qA^aA9ZICE$=7t5~!<^A5a zZt7k4L;XuXd$%2#Ehab64M(W1YRv}9Q@P$i`6I)aJ9a^c@4|aumbKFO8`!+x(P@v$ zSn!6^Xr!9!D?uRx2fea5sdndrgu9w+o+>SnT+x@$Cv+EXK-FFzr~5Ys>Z%cj6>GG@ zWdI(2`?juM#P_ImLYxT?x_a2<6N={p8V_@msaD}Gm_-w%1BYXx+EBP)Vss@nIPX1d z?Qu7`WNIvm!`-0IL97S)cx~k<0Q~@!Xv@=gS{7Ov#NQ>AOi+Pk43m*KOB(8XK?h$v z_YfS6EXoVt0>F-=*}#KMF%m(jIXwbW?$ObC6|+E&4kpuZ#@SC#vc)X42dWr@+KSTn zbc-v~(c(PMuxHjLHwYr$n7-m=mNtqVYn9S9X&a{JWv-Iy7pzUjV0Tm7(A^!~+3(&_ z_(6hmjEgfIl@V$_>y$Ej^fSoI-WZnj-qhdHy71Re{F#r(N!*GI^_`%ju2y^Y57lmM z!hWqzy8y{yZ&R*)$NHtWZVMp?(0G@1D}Ic*9!7~!O2mU>Za&3hcS1eO#XYxMnRFTjcRCV`bSQs$l( zrc<2`b#%V;@)+v43}}?NsM%CKGD%LtT}B!QR?Q*qPco2JESZyzOsB~ zwj+3D$g>9V{5Rus^*dD9UA3`AC_^xqdk;jll&ut=?C+rR1GG3#c_$|2f(I0AMrVQLnxIe<%9S&g`$`?t`=X zKD4prHbgYHHDCCypxT7m)<`h8Ss)dH*ZZEL+S{*uX-$f4vGl77G07iHnMc92JDMz~ zo=B!1I5uX-I%4tIJ`x}jE@V8Uwj|jb!wpEE7N^0weddPV$j((?SEDZR3o17 zLD1(~hAUvI`sl6AYczmw{K)X&!f6!vd-N$D!lrRZx@Cy)-R$o~5N1NR;Lev#CwygB z&28lVTFiKI(;=P(8cnq4`f_C$U8V-Bp^aa@T&XY~j>D-SvObhR0J6dOk^R@hR`_6G zIe~T5Ntw`K_Y_ZigZT7Czje2IDjup(Y0H=? zacHcmfbdv&F5K4VDot?RM7*9Hl3ThuWpoNHvT z6r=q$IC*&_WJgD`O5SP--)rQ<3hSeFtATwb`-^u$bTjMK8W&zs@k(>|GCX8{vb~R4 zCzPabZ3hmF4$1^-?%!(MIl(1QK`WT<=QZbK2Zh`p-8~7WU+92_4Fu?-Yott@GV0A4 z_uMzV2GSeknd@FE6I-Br9&rHX7}!qn>mWDb=_^9=tUG8)I!m1oiNvq@m)2eFw}=4W zqH4Rau33hpM=CrgC*!%({d=PQ-Fm1_-|ojqUUTL)k?0>K97L7h-$=`JU7cxl#aDxK zCX@BIu!${%;-SSVm{wBnAr34LoWP6b!Zw-TZnkD1`{I%C)grb(F%QI@yI`Mqky9fc z)7Dw3*T9Xl=D(TQ!FT?uy{C`F%d#Ls#5CaAb8@3&!kew=86EJLn)S6oJ=m}ypnA}( z5`ygM-yW%&7BM-P5!m`=PAI9gbmM2BC=YdMh4cs`BnIqr59hyFQz)w6I-Ot?y`R`h zH{H*Cr#}?SeV6kUstfsfpf+_QuC&whsI%Ze3bq3Jqo6xFmuDHz@13VyOOL!Y4bP*x zT3=`aU1La45bSf?9^Z|XJHV=KF1OickXG#MNgaQv?-uxu`AaK)iMHNrBISD|ym^^@ zZdHCu$|BU**MTQdc}jsK<6c$khjOT|khGx=lon8V-hu771XQ~gaplD#%CkfMPHwM} z`MsmI-+>0lqJjmoE`?#BkyTFK!;?mpp)-L@yCzAUP?Yao8nsg$&W!%hmVcEZd+X8k z8{v+gnpppG5IMqYW-s;CyAzCEgiWtyPTj z*$369Gmga}>RO4S>c_dUnFQi3Dg|B@X(Li4Nj@}z$yOIy!_R`oPcS5~FwjUh^;o*c zOF57;pw7$N(*U%(rr|BO>|@?w->*`>)`Hg@AnB327ajaI9{Zs^yzrkVI{(~3Ybzv> zs)4I#_8C1F3f)`mF@YE}4{XUWIiqj)Yul?Wbbh5m>&0;{$!kve;I6edyYQWM z_A#S)d;V+J9%G2w223mpuX8+n1OQW@6`!zX&%f=c0stb~PLx0p`!Evsw7Agikb(6< z+Y3aV`$~CKbX%5EkR{<1Jz{zt#TMJDh z=Yms9o%pH^pLy;jwIeO3EtqX&VnU1U|Mdui)t7iR(Vyt~ z;T1=4dMAi6_ltu}hEYvv61ZE!s1NqiKv^mgtubbAZjYz302`I~CiWuQ<2xKNJSW1)jHN?=Pj; z21={>{=PPmU%gf|bJFgNA6Lj36~~F?-bwu9{Q!6KXA>eJy<5CLel=UfuRb;n9sq!r z2lm@6kd#43?5W7grEKjAKjCOLVc|C8V;<5(z@OlW_`~N){0$Mpjt1g(a5ugu!F*>! zR$t^ULao8%>7ve^7HQq`I8rbDa3_yEFXH3h<JGH6UFWZ_Ymb&M zRT~+*Jw*iIXN%?sFRikF_E*JLFR+&Uc<$E1l+lrgSKOItzMYwS_Ytvj1-%s6B!127 z2WV;yoMit*C~vN}UGs;(G^9=lYS$drz3}%y-5K2VPv;RM?jNnNF*i`%khYES0pb9* zfi|rL^HU-T47J@Tyj7Ax&%JU&5eV#+f6T^DK<4E1{E%l(D%cbz&i1$>rqWfdQZgZBxw_oAAh|v3^#`C;6 z_UcB>5FAK8zuWuvNTRRX85)Q@2@GEZ8Dajs6Xya5u&-ZRBL5vXJM{Uwm1 z0XCtd`BEfXOOSqLKEHNxW}|oJ$HYJS?tAz#94fX0Jty(rHZn zEvHlHVQJiCah8?(rfjUAfr}$1!!5XDfn$7q!sxHw&}^B|d^JSK7bUs108ih6ZU7~0 zd`!)KK*)9a;p%0>V&+55CQ+CMDV}tyIA_mVw6mHnx!;4TA+o7bh|Mx~&!HN{XRAGI zp)DJ_83{@K#*ZT5^e5cmCiD1s7*WeN)WN`%!KL0D8qYUW{x9@5&Q$^JTaJybs#O#t zs)QDq{r1aAW7pV@7fFZMRT5okeQskTtL^SpkS{!g#}^%as5d(tX>Si&Oorr6J>J%0)J#4fLFu#$b8PCq5-?C1R5{npI)#waNKJvL-+NzclEl(5V3U|>C( z_o--2Za$lF*MIeHJ8wsTOmLZ-Hn?=gm27cBt>($BXW@Dy@5!Jo#3-}Xw83MM8tZ)j zOiB0MOvbyd_t;}G8jRt;P|#T21?QZ+Y3+yj_L~?^OCt^(Fh(P_ zZk-e}A$1;#k}bYa*hlhKSGIqf7H0%pZ&(D*pON{pRseTBA?Jt8HR@@SRBuVmh(g%h zMIh1P#@j*$-9xv@8$CdJg)C5TqSy^Uk>skKsnN^?b!VIP0Jtu%_mi0A0iATtGE4U+wh&2{AD{nWVJ2r@8S}66peL+E3K?wKDPFbCZV|FAXh#oO`^45( zvL!@cIGUm+fnXK4N+)m9Y+>8nh7qySI%ApJPw@)yno`0kTs`iqb$@0&2cs=KrS9G(yCt{5OG zv;InlnDQHqh_pD-H=ikmHjuXKI$yN@I@!iv4Er6yB+e`xnyS>57ND@A_6&?_kQ4GlfPf7s z`Zgwh9B0gt@@vQiN=pCI(tnRT;BKcz-ZPj$iq&b`pYCcaj?vc-mScL<@p7ey>zs6> zsHgyv8Yns?Uyl-Sw%^!+spdj+*r|fkLHtqH9_UhUQV>vhEl#1Z{`dzOEr2G2sjlvf z`ZtW#5#^GJTTN=+!DlY-iR`~|$t1RR$z}Y-L`_kP8U-^%T``n`6-f;CfkaOE&BXY* zo9^-T5g0|xW=C1pn%^*z`lyLbalx_4Tp-@^$^YeHHg!J!fQ6@p>tDg+>Zs%&_bx5i z<8UuB-WJkbBsMnz6e{1>{LFKsZU4t-cldr1`K-d#zH67J$7TWW+*GQTi*eEkCp5V? zNdUZkDDOI*c#_XNCF9T*FD+V6kv_ z17HI1&<9~qiCOc(E!NuU25KUPJDjxh!vm?&8AhDnVJ~5Y*B;RvTQN6+V3gf@$ygr&J zYHQ&=mli!>B4LLf4cP!A~#s1 z0}jaxgL0xLVu_6ks6A?mA&2)puO$WK9{2zQCOa;g{B+%IgFauNM?tS264az&G;CDY ze2+$nQNCKJXT)%uVKyqn+`Ox;*r!$v1q}3F0p}=;6)s{_q#y`}E`cY4zLN&%YP2Pb zxY7xJR%@8aJSha`FsJLQ%Pb-}KolAH!MP!zC1$F;+u0|D$DX|UI15%gsVOog6~NH?NJw!a?hF3W55|_EOb8A{q57%9 z9x(s?ETHzi`j{B&TLAsDH!uL?t4bT!%Biv-C6cnPkBgWKD&DxBj>foshcc85nNzKIV^k90(xjKFFoKP)THvi@z6o1RNh}m ziMSW08WMDtTgC45F@6m_&{bbSJ+)GS|#sQ5Ovo^d{($bx$GRGQ-$Y7TFtCYZO zw9}R=SxpvNiSH%kx& zU6)5Zx0hEt<%ko9rY`uqNIo6@lQ$-(-uJ6Bx+(}9gm4M<(!25SqG--d$1N$cqvcH1 zB^{rz6UA{Y#uV`Az+{KoNtRTqf>!Y16fd%4T(Y~zR96wysx=h8JMT6`82zq*v1%_N zr(UDt(tysXwrQ|Lj4Gm6UPOmU0=%p;J1LSDF=JUrdD6Bc(Uv%&1u4k{2P=%#1vVb= zriumyiHd`l(@oYfAOV_;vY3qX$YGo~ej}>jo~`cc`>^nTOaI?I=9tLeo_T-9EX$dW zEm@B11SWpx*yOX|o`kzoUPs1;jztBA8w(64I!sw0eW>t`Vy0KhlepI(s^nM6&3iua z)1)PnI47-SSsB9g_0j?bC1$rXYJ!zV65fd??p1zwAVJ~L_q-kDN$-2Wfqc%B|I&=F z^MeJ?Bs9c3OpsLC6f#L_J2c;sKU!ORmy+36?U7N*;sm$6Sc@eXXkB{l5420Q}H~|MD9SAC{cve#;$r=!I&DYeY#(Vo9o1a#1Rf zVlXl=GSD?J(={*+F*LCcptHiBg*08-nxG qO3D+9QW?t2%k?tzvWt@w3sUv+i_&MmvylQSV(@hJb6Mw<&;$VHAL=gv literal 0 HcmV?d00001 diff --git a/docs/img/logo-color-text.svg b/docs/img/logo-color-text.svg new file mode 100644 index 0000000..f510e58 --- /dev/null +++ b/docs/img/logo-color-text.svg @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + libtorrent + + + + + + + + + diff --git a/docs/img/logo-color.svg b/docs/img/logo-color.svg new file mode 100644 index 0000000..d07360c --- /dev/null +++ b/docs/img/logo-color.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/docs/img/logo.svg b/docs/img/logo.svg new file mode 100644 index 0000000..589ee7a --- /dev/null +++ b/docs/img/logo.svg @@ -0,0 +1,1309 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/img/our_delay_base.png b/docs/img/our_delay_base.png new file mode 100644 index 0000000000000000000000000000000000000000..d14ad9ed86ad21a5fed783be6a3beecd1f9c1f96 GIT binary patch literal 34999 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 C)oWA$ literal 0 HcmV?d00001 diff --git a/docs/img/read_disk_buffers.diagram b/docs/img/read_disk_buffers.diagram new file mode 100644 index 0000000..1d04a25 --- /dev/null +++ b/docs/img/read_disk_buffers.diagram @@ -0,0 +1,16 @@ + + "copy into peer's" "encrypt in place" ++------------------+ "send buffer" +---------------+ "(no copy)" +---------------+ +| "receive buffer" +----------------->| "send buffer" +------------------>| "encrypted" | +| | | | | "send buffer" | ++------------------+ +---------------+ +---------------+ + ^ | + | "read() from file" "write() to socket" | + | "(copy)" "user space" "(copy)" | +- - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - + | "kernel space" | + | v ++-------+-------------+ +-----------------+ +| "kernel page cache" | | "socket kernel" | +| | | "buffer" | ++---------------------+ +-----------------+ diff --git a/docs/img/screenshot.png b/docs/img/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..e2301c58a4d467dc8613bb3e0f5dffd06a541bc2 GIT binary patch literal 501532 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⪇IY#~4S0qYUu~ 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

    %8@mA>j(%)0&z9t>{7CTF?Vfn= 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≪{_jS$A3{XB6R@A5pP@tJPi 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 u_Ps8A*2eVUX8M-H!WuSO9`6nB zYMW>QtKFBS$;0j0&fn+n36OM{HO-#MI3!aV?yf7Hl;l^wH*pk3{+oQaJZ~TWKQS(U zAhH4213o@7XwwEBRh9b3u`pO4egKyXOJxy(cDXGhQ2lVnfI>+Xy^296fcEY(G=)PR zHV~J}hYj`rR|%KJ@wXck1{XZV`uZ(!|J!yJJ6v`auBTVj%olCkbOjD1S>#Hr+P(%> zl*GhD3ubisR$N@%V6iy`oSYyhDE32Tya6QztOkfP3rs9uwTdplMegWWx)2Zj6_CWF z1e-`SkNFL_$Sa{Ce3UkZXWwIk0tqeu7s4taPg?XdAd}8V4s6RUKYoa(5re7{f-D|_ z*w$vIb$-XbKN!1G?CuicdJ5Hw9+NvBV*#0Ewn;Y7rpQpxhm(+yApR5pN!~_~#U^+p zvH2qiJ9>btC_=l;W6n^CZdu|QOB1Z$4_SmzMq<_7Ccd+ygOk~O&y7*bjvWZe;zv)E z4TZ*{XP;bK}ssSj93RR0>Cj9tS)2mQ*h_xCZM=DFlpkv&3rC+4km?{jEB@G3EvRa%q z*odR%!@|_7)M@3MsR8cZ=f8%<5tCx7YWKGHUaLSK2Ka4Lv)6P+kG-@G-npD=8V-^i zk}*BKO1z?cw9<|7_)aXhv3vFKuAr&}o3|byoR5dP3oUvQfcn4$kT(iOc02C0ng)ol zD=n77#OE~Tb}3Zh=+{CLcB;4k`#&5i?-OMF`r@i{S*g!@bvAYe)tivKd~K%UNy^o0 zqry}_^y?$NCdHjLIm!r|?M__Bn3MNoyR<;ukIssZS1(^;ym0FMnM>ixN=81U1UE)M zL4(KYf;(^568#LrA-ATw5zk|zeyP@m@DINYxqRp)9-Ys;d+y`aaC&h`XNqSp+)=U| z0w)bVYSbJ$#UBdY�cQoUNLT-;KAaa$*_hP~IQiIUKkvdT(p%cXY5simGo)FneUP z4CB*Hx}#xZUMm4)rTvBNv_;T5ginO z04M}?#305~)*JzaAmU+zQIZKI`sTrU6?3o@J_(6rVUb)k*Zk=WdAamtvF$NfkFN^} zVL{T(S6@GS*{AT5PhnGsdaos-SU%nv26D*I(y{|mw#cAZ%GA{Ny4|T$J6#!pg3{93 zojnE5_SV``Q(xbqGa^p>eNs(v4X?y90ot?u41Qpr7*4#f7}*P}_0X^T_3~C@!04!x zit5$2r9t(k4qUvT!96R+6I&T3WW%?_)hf9}}wX10D1*1c!XTXl`x<>d#Ue z2BqZ#zj{OD6RJK~0D)^Im}>ArHa!KjR$%JUU_mJ3Js~;0G2W=?GEP@VviyAJQn$ko zTJwinR*w}rPRYp$A8aY{B|{)vQ4#->eS~=!|KSaV>{oHSC|o1Vc-gkyA}^2eiDG}u zbx~XIZ%8aGY+Ul;3T{dbi)hH=7IFT5Jiol77I&yGxocwAjl?ed?&n2Zo0*w`BuFSIAdSg@e>Orp1F-eUL-T@W2>~+-#4gx@xP!EuoHjtasR0WG zhYA(Q@|(Xw$VhJpSOeKF1e2TqEdhkfr56@n1)#AQs=bghC%u&FjZ>JnCF{i9HJVB_$)5=09duYkeww z=qpRR$fc7yydEqbm*(Kq_n5u&M(R&C|3-r{yg|VNMuDdv4n;==+w>DDf-s^|8F>N8 z)Gaa6n6Lmii09v^a9!lOJntI>wW6!d4W@A<#RTT_g2il5o;9_sGWu?|aO`{|cYw}k z7oYxvex7MELaqlDfjnC2T0jSiS$>6A6Ep1~(r`#oM8G~li1 z_%$dw4!vbDTb z5mqfYylOS)9g!cQwIRxQpcJm?IjUS?L7{cc+4&~5sE?+Jg33_uhbn^sD%mzWQp>VP zZti><8Y&1hySwR5_R2IVBTKr6jbJ&CXxq3dMo9j6FHjzvD3Zh-kzQYV6ID*4pnszglPQw`ZMO{=J8H5niDEBV(>}3>mY1)^7%fN?EXaLfv{j=n~3cu`OxpQo>r7RZcc$ z$V0fV0_uPSjynSgvH&6g%tgNj)ydsoniKzy{tqTfe;oK>6 z-QU1#=?&j;3BtPRY@8R0a$9cRbBl$Cq55c}#r_9~<`7#oga{WF7kBse{se$KjEL$j zmSFb=TIt6Mi82Ukt)d074 zAix;lXaIF;X6&yuZGjea${HN|M4Jf^4+RK&EyVspp9~5O)Hi}@L3k-7SR||LQ~$(@ zW@3e(9I~!$B^Ik^cC3lKB!8u3{r$ALiaFOdHzU8=`@y@MkIIwff2=;f$(SxwUMaKh zVd&5>-Py9|J}rZN=URzd{*+(AJ|UIg85$U!{Y9;?zr>Kc&legAQ$HzxP1jIzO1l#M z$m^ZQNvc)Hpgm>UiM*ewUu9@)Waw9Lw(3m`iyrqv`sv3VdKKn-DDs8x( z=XbIG7XKpWip7NK`)xuq4ov&&k^oWBP0mO$RvN5WYiEx5x?`ibIr$>TBB(&ww|S07 zmyP*;U8=ZapNfvoJ$TC+99$}NSx#Jz@Frnri4vdVmiabR;Vf~vITT?gyNT_lQOa9C zdWTh?OOE0xjHGg0YkXvsnbro`s;@6zB@o>?D=}x9y<)AiFxUDMo2^}vry8a*FjOZi z=mJr>reVb!u{o5H%`IW=EN>}I2Q;Xz|7h~**zhtLUe%fE9SBj| ztE6rU4;<^_H~w`AloCJSl3Jz6NK5ksT~{zt3)6O4K1Je_Aq2pi2b>L&x()SflmNB^ zoI}Q^XvLKWcKrkNP4OF0;Q*I9>?N?;5JLQog`nZwLquu^xh_bn6Ieo^y6N2c^ECAI zGr{*(c6K%(*;>Zlo(BYg%lGwU{3gsH=)fo(V`f$m^dyGt{d*wX9 zxV%*PAtJ&V2Co&}hO}>M4B*D5AIL*VDF#i*9Yc@Y2_d1CG0Uo|Dx|0&D{DS-l+PNH zY3YK4i<=ky*zSe5vy@hNEDkK{&GuUcH{JgDXlS8LKRXw?YCMa!ONk~SwalK%%#_^r zbA~LvxQfbVemGlX3q+(Xg~S&Z_9o?+CqLDJ?Hc^g;!aNc;#ny521$hy(W=qvY!}?( z9QLdH0gT#U(rbn{O#!rzY?W(bCCzL1H+Oe-=wT8DaSN~^s@JXqR?`Xwqjoq35bPJm zKjY(r?WN(8)!?uMi0T4aIne*rhhJa4gy_{Ot@%F9-z$HgD@n+?0vZ01dwY%$Au+VG zFU;%*Yzv*Ii-QD=jM22DDsK+U;-wAnG7s$FLNyrTT#T?e=qO5#wq5om#F3>yvv-f7~; z>Tp{jsvdz7Dr4**>0y=ibhCXR)shsBD%|b5W4YqG5GhJqw$PG`aZx3xW$MwPgw6Cm z^VOZT$_XJ zxa}p(E>`Y+(S>rRy|)Jd_CJxtKz*{mLzCntb?Y}`uOyo8ThPU#bUEcJaZ8Rh@`wIz zi+VeEqT%SiS2=~|d-_aQK$DK#LXFbh+u=kQMj!RxdZ~cps=u1SHE>V zBy9>*0A7Ekb_Iawl!s&=u_{0&Q$<@$74(OIi%i97dYaLm6+{W$do7pa;h4Asy%M1H z>x2>)DAuDBq)pxQoV*6_#@faPf{8=#CXkPCQC)6U4+T9ipxq4@aonTX;PJp z_~*6ng=X3+n)cT9Px`KELNw|GAm;!Pfl$sEEbgGL0DMziSC+|QO zAu`Zij@^*yfVTy)6r2^lPD?~NOT6qspu5RQF5N8yn7l!l2TIi5)wo=MTE$* zRNqUbdIWaxKy=V8uEMuvHY)SO1Ad_l2}%KYAYg`f_X$Ke!w=+rTv|G6@i;c8rCa`1 zxCJ6HvkzXr(fpen9%tLsvoN6Y3Tl~Viu|(Zfr`wA-4vgg=x4vZz~;=T@pzrcX?Zq&t&dWNA?G#o$gZx%#}ippf^qY+u93fN0P{Z?FD z3|e5}?gNaa>z3_d&+zh>@?CVmN(W`y030b^*RNdzrON{-hA(lORE|3m7Z->3{tgGn z73evErJmnq=j+foI99~f)gvM9>@&pa!v6*(olpYO%i}}B@ExS!;8KDUA2b_Hf7iCMBgQsQ6&b z`=b7r<;2jC76=d~f_~!n0)H)reA6V3_Ya4{v2Ir&`px)gZyf>YKsO$ekkA2q(C|&0 z-KOHEnP2>X92+4v;4{-kn_paH1&9Z1yJ>%$bIw2;;4=zN2 zyYE0QF9`fnm9v@&jEH-%IA4f&gpN=XNI}%?MoT`&oQY@U+MBNY_-F4~61OvZS9MpF zoLBS~o+}A$dSQa6Ex9gC`}T_hMF6E~PMLGnFSNVx+3h-`GUZjfRZ*7<1*W*-d7K34hH(D&^#<=6k1HU(_kucO#HIo{ zA@*QXxwNH{=qt@cfmxgM{R~<8v-YwH=zh^sR#IENiZbtE{uFIkgKJh)m`HE^qe-ES{WB-K(0Yk}M;&+gUWPIbxnS6@MSOLznLTK| zk8Z^-M4$Zeu|YH~;S3YN}JynbDW?#{I{qd-<{5)%4N z@82)=6#Yh@*>3Pl=`e^F2s>0dk!N z9$W~3C;*fnmhrR@&_OrG3Jbyv`JF)?#~^>?W!lru{;Z~ek6*asXn)Yr#ZuYp@DPRg z0lyW+`4-vTqN2)sW+8G)(Une?H4-+%G=JQpC9t-y!qE^p0ICwdl9G~*8yv>J=z^=# zXXy1^4Vr@Kas$(rsnz!7<-u}zFifJ2^X3FCuhV6az#Mz{YKi!LY-ou#D zwWCOrn2+0<+fz8+gv$w^66Z87hghG7cH@xsUBaR|!IXln8(KFC`wP3ZhJC%HJbq65 zLoIGh@JReFf2DBxV=fX**hL6S1r+@n#q zFM>Zr$>kG*-R@iQ);fRYE`Pd!5qc~@>s8Gi){9Q(E79u4vYTY&b#JmTU5QqvBN189l;b&S=+y`6%Z zp08~1av0|1RfmeHr#1xySAQ+LD)Ug>Bk`pIqdum6x)>0+yv6%%(r#^g5) z4G9T>%j`rAAORq6gBD{%Ilm7{R@5iRM{$tq|0K=i$R6 z+VMDtF#~YV3+;~BFcBCd_3z)mU(wdq23SKJLW{(`$3~@ZT>2JB&dF+EvU`)2wMFCG zt?TLoYRzi^Qm?NM2864f5ckFsCnXu4CDtRV`BTegV9XV?iY87@{DTilt-Y0s6JA8R z8McP=b5V-w4l&8ecL7ch{d~acuyQ+l)P1tSt+_YLBtJj;!>6`KFIr1~Wh&uReN=#? zP*x7vI9QhamIJ^#8g<{o!c3zu^PKi@)~iTSgE|-@S4@_cP5w+3l9?^OWt|Vwxc36K zQ1E>{_f~FK98#<`-+c$ZJ^T?bexwWkQwsnI zXppWOIkkE&OZ5qqO08CI62aq+n4J-6%<(BtK-s|^i-a3OKFlU)4)dWF8&LvMtsgOW zaw_SL_Lic4&RnP$%K|)kWXxJTl`i?<6?gYpMh>5^J#T_78YPMBBF3iGuGc>>3%Y{7 zPvt#LWW3s%4o4WEN2^?@clhE6G@j7>@1XGo#@M852YB0EC0&wGvPPmna>II+^@U)L zL!H$py2u&5Qrmz4pR$Il0$T zA0=tS*_$h=y|lFEOkZ`|c8EE83uLE#@(8{RC4VnXKdy0bG91QxkEnY)e;;d$%Mv|) zplhZ*yKzh0sMXeXJ|#yyRJA!3izk&`nsQ~h0d?@@>BC-bY`NGM2_Tr77 zP>I9my0=EhjBAg({TxS5J~%m7`f<2KA(MADt2r?%i!iHjzS=D!Dk?LK4X%Iq{J>z^ z)w4MtanNFX<$D2oX=8709ywtg9*UfSxGg?GK_g4cKoAZ>2+R~10+_7o>gqt$YYfu) z-QSWffJ%c#j}T%%Pd$U918CKW$;r*1TR&FAVW1GTTZ9D;60R%X<>gscv`fO)d9btW z2S^a)(*yn&2YUoLIi3D4Z)4egNE@fCMo>!f^p?d*SR_(47Tq8Vqg^VXIpD%|3?0xxpfb z^nlROiBCWB!gOn5DLo;8#~}(A#;6hn=rio1P?fQN2psZ06hiUa&IN5eE)SSej}$$g zS3rDD;A04h%NaWl)~8WgF0L?u$P6L$>ve~|L$dIE2l|vSNl}%Uo7e8c7Dd01&^s?< zUZrH^NjGI(X_bhk=_a+i;m$T8#Xj9Nt=kN}<_@ghdJVhG`WwVklgE~p{-d8ROZs}o zyZ%zu&4HJl^h?&$Le-L6=3$21^}fx_;pVa0#Yx3m1SuDXo|f7$wm(l}$ZBeljbRB%5~cOf5CRuu}A-)F#OD&eWV`W^RxkC#=@i z#`Q$osl7iyX8JjNH88qI|3)9@4BO?fq&`*KXlf%Qni`?)tyQ|?Dmla*(OCb+dZM6m zd~k;)G4?=_RE2~PWqIK^g}xc}Mw8gpb8 zMooQi@MZMue4kojv$6luu!7>ZjM&RF$5OdV`~mHdPz{Vm3lz@4!kwCw^e!mq5T2Hk#?3>lF1W*Y$zZrgQ9}aI9YMhuh<&#w z%S%fmI`J1~Y&lZ;5T*}sjgR~npz(~v{}yO0eFyr*|6kBp@$?C>^qg>X9El5ba&bZM z5ET^?Qc~%+v756vDdW_fje`jSnKT=Mp_M=717+LV#vD*%Q0%;k$zDV-Yjfe;AC5UW zzL7%0lc>mXNHqsqCcdG#NaaU4z?#?zLNSOSJyQKjQG zmuZ*oLxs@;P++nFh z6%}zfiDpVo$!)1H=YoJ=nce=iO_;wq^dE++FWDJ@OELu%(NB~nZieKxnO>x%=51s0L@e z*-z`9Gu+}$G0l2SvtC~;+1rcBX4o0jxUorb-v9g~jJ$rn%(w64I1;SyR->fRobj*v z!Ru<><<;{~+E;E)2h_Nb9}PV_LT@tMWj%*mJvPm<0JbW9$o!UBMDWgw8vuVKTQZQ1 zW`w)HJN|~Mjo;$!sbJ}N8to5oEk|!5ab!jt?abipugu(;`qE9N^?${CxwyF;9m1&D zJ47U#{mYK{tRRZN|PW0UzcI0Hpo!h$&5scDyy!G_^IVMIkhO z!tiz7R8NL#cQv=`?bQtZ*?`pLu0%^BiuS>#9%*+AqOQ;kmA;YI80qt`O5Q!9e{MAK zT!czE;+>bo`()uX|B{uC3z0OhWk0U6+gX^J)

    hP@a&khtwrO4HCTZ;f~a|=8)t_d0RbC(@K4#}#jo~y1FJa>^4>QpY*$7WSi0qh8hb1B$*z?cI3 zlYE@=?^OtvhR$yYEk#nRVMF>3^CAGy-}3X5a#ovbWQ}*FQDqW-uzq9K0 znOoN*MIr9z#^$Vv!L!bU6iHIan@?45+iYbD{_r{uKSQu_?tIhFU%%e{@W!VLe{#vk zc5QvI;j8BP4_?>qJ6JHe9XeZWOmRLDcCfZKgkc`~Ar%!B-^RpH0K1G9`i-II=q8ep zLCv(5NxaOA0&_Lz%TCwu15hmyln}G1Y*ctV{L5&>TAQg z?-1I!f0o(EtnFgvG<01+h+TeB z5zy%i!TmV*e;zCe_WNAi+z71%D9P3T0B~bG>;A^=N7B+%Fh1_a_ zfgmai4G9s0Z2`zQ_>|Vb@?9qL|8V!7QB|&AmajPy6qKA%5Rk0opb{mB2#OMw93+Ee zHmD>eh=_nB5y=7)B_laX&S^`|AQ?70^m_lNs&7^Gsk&X&-FNi3emP@sR0Q^Z-u28m z*Kh8a+dvE6mDhlyf~5yhhnc{UgDed^ZnN{(uZEf$U#Nk29~-L-73~i)G-v(tPMd)2 zvjE|xok0T)ZEYp^dSY3B!WHdFm8Yok$Lkgmc&$Seyl_6n#==>H)Q3w>fu3RZrG|H2 z4n(x`+1+o0c0<;71V*@vlHr|S-|10uKG_|&Lxpd#vlO(vJN|BDd8B(wQq8j0vSmD$ zizv>cOQ6rN-i+0}s#m48?2<8eewq@VGX6BVp>*J)y0k=}!}D$8{q>TkIa*rzJor1g zR!yK6le5AcZ2W37`LJO^_t2soDAP=Y~iLBG3NKuLKg=JLG31IQCKV=sEMc z(=~_a36X4oxwW7YiSuY!wA=CCw!@Nk3`O3HfRe~3#q+4Z`8dYk@-5lrDE1M zW6@4KgfN3Thi(a#kS_1Kk1Wt4782wPmm^=;fb2X#LjA^vGgV9sd&7d$1J{(qy>y@v zf#irODA=67-TE=PSn4b4n3I%d#(YDbG6t^T>}glWKcW}9#m}A@I_>kH7h`A0|Iewh ze&|u;=`v@$$0T|#Hz$ao2Uyo z_e+@W2feioF_av*ybtzfO-WWsqm|R0Ki{E3|1g_0>Vg-z>fp{>P{lcz&^;|ew@vhk zCjJNEzozw4&87t|&6%<#NVV*pG~`i@4T_vd#&sk??{&G$dg@!yeMA{3l0tw^>`trs%oM(wjVFDL7S>7 z-sE$%b82Q=0#5wt)ylinYwHB0{^sYv3(kQ7Usuqz1XzrE#E^hu1!POS=_&qIE!)!zQW2&VX6&p$P%TL}=+ z^iYxZgc=1%f>i$!{^+5EC;eg&pOk}hrNgjyiK~}xA?MBNy3J$Nyr~gFCXeobS$_lp z?THQDw|6{LB|1$zg-PY2Ms@x)tdL8ajPHKpPRvcLZKZ9IZ7nDB_4d@J>k| zJG?K*;ktH&<`WGfxr|&(Nhv#d1O%AqgJ^q>88UF~H5;{9qT1~724=0vZT+nV=tqgr zzICO6>`DsRjDTDDKNM!yrsJzpBUf@&eB5F4lO0=k>_~NayNFHh5jN> zZs|Kbq32<%9eODg{uktl-ra2BqcXo`a_8!MvLNjp%Edq`%)ouH=(;rf`cjG`+h%l0 zDm`8J_bhMX=C>8fJM~TvG~-w=E(Uf9^wi?Rc0b@BEA8}K9{EcXZ%TQmT=Lv02`?`R z!3MkWm666E?B~@zkL}~1*^1;3e$0C$Kpg6QzLZ>cMfn$lFdgYPYL}92lMx;JO19Ec z@rG8{{Z0dN-mUC?p>kZ~3sElz6Ti)Mc=#aK!~TZEQ5vuCKvFO4Vpek9=6U|UAH|$? z`OB82tl>{{vXuXgd_73U!y1XUu11Ay*4J1@p_C#*L+zMR0ry>LvP`ZX<0eIe zNBY>9m;R?}n50uYy~HVXjvp@$+nx=}n`d}Z3bzyFR$doC3izrdvbmKNq%Pju+Z${& zJ3Q76OnYSU0*xY6W-=@<TIq~t^%j%4NxEpZ~ z*b&B@oSN&B3=AkJ)Z#TZ_1Dn#WC&M;_NQ2^ryLH+M2*^3hd}$o?(QxKr~cur3hzIr zx~j7?J!J${IO0R$NlD4`{-kIXrPfCR0=q}v(BD6KQrhlt_iX#GN-8k;udvz7HHN5B zOYC0_4nF0uGBh493{nx)YDHqP>!ptD7wPHAVZ4Jlxt}IyQpvt&6z1tzi5kDqiz}iB zj6QMUtmrT4*FJG+;oV*IAL?cI(9vxF z51*lt7V>W|>e*YIev^=J^3x~x?Zsp}*}ExZmR61*HtD6kHK_wxe(C<6+Oe^? zzvOStC0F>V!x02sUI(;8lDg;Pzz+dwDG_X%}D_qrA|8!u| z3h-XhxU>$eiXN_KU|>#~a6k%K#`aj@FjzS-8G>x)H7>3?$XjDD?(k}24DuXjCQN@= zw}3yX(NqQ2nZw$cD1P@xHNVI1a~?f+C*Ektz5V|rTHbZ2qmXVS&5(vayM0g7fP3+` z9AWN_=@BQ);_uy$v~cS6Yh{HCDTd!Qj`x4-BrfHUUa-xRTXyY~_Nx#3ej6g!U6?$~ zgyfpu*b&k^(hkn2XI${tULfsLK+7}2k*y-3YTj)9nK{?@!wyIAzSD`3)N^SnR(e_a z=)NS*yf=x72h{M}pp)S(7>gy91fd_&)!D(*|0-<$f}VNAtFT+3|IWabWc0qbW}0SJ zz5Vd-JN=QIt(_TS^5;k>e&^M_V$LN^`tz7sPT?&gUQ-eC-;q&%wKTJI+S2GyddO(6 zfD~eD-!Sfja6}ac{gVQs-pN#R z8grJO%K-f-%f@+!d5^vQ=g_*$%aMc3ECaPS-kMi6pZ(~|(P>Jkh~0GSACK@ZE4wbj zI4bl!oqc(HEC$m{+E-Luc$U%FJUAwl$E(oU{Jm04AL_{6%PyKz6>EzPDft2&mnbu_ zc)5EN<(^G{TyihcwYE(@wYi7*0>Zsg=0UPo@Tn5MX<_@D0{Tu2n~H58+Sa9y*^ZYX z{U7XV3H9wNdCSEBwip*)L{dZ0mk1yAymlZ>;w?#rR8k?OP2q}v_V${Zo6o>98%zwZ zHyMwxvp1-hYUS!%3+u93rDk#-*Bh@|^^c8l6L>ykZ~*RHW|rV+NP)+m4YEZ7TL~QS zeyOj2{gJ}YtGFosCB(>CSgpg`E;|NgW-hCI+U6D&4S1(`iC0Zi^XvTwWU$;!WMUG9 zZ0@+Z86@Z0${88uDY=0s=0gXG@*^GbI_J!v8f_8D1mx5;H-|1P$bl!|BxRQWKbs++ zQvHuJ*;xaH-Zfq%UEFI)q~U*4djjCVHdT)g>QKJ7Ifr64Xzax zjWYw`9}oaY;UFx~9Dx$DP^kEV%}z05tu$2MDmtJWrDs71-R8P}Jy>|c^*Aur;Y=Nk zKfS#=3d{9^0AG)h@)84&!utwln0{0G?55;u;UFr0*ad?6AlIHDB7*Xp=-aPONrva& z{l7AGUf$-7O_u!3LLHMl|34w`+sEorFMYLbw*C$KCa`vLm2gW6$tDerKQ1kh z{as^`ZJPq&tBTIeYx|i#N10v1j-;2|zr8Z^_zUjKT`%s-+^pOJXq)nALt4wx$!>?u zN1`s?RWjYea@H^QOuXL7^^9D>hGID4KkfCr8@x3hBBoTu%WS4bGSK_&VXdl!>i2|a z<2IFVSXvTA`E>XLmSalxKw3$t9ZHEYujU60dv0|%Y?*~q^&2Dr7{oiAq#P_rJMD|bkI zAs@ZRTDsZd;3O|6HwnHl0Q4bY0tbogY#_6bda`_;>&BE&wTl)WmT3OoI$&s-va#hQ zfYAg{fdZgGLL9sB+JyhWfY76Z%3`7O8HGVdkFQmmTR}P#VnV!?+hovx2YU=a+4@|v z2TmizxJwTdO*}z=gD%g=j%>7{jTBwbSF)h5FR@r#eql!6j77b)!h$U_ZS5rv&NpQ) zy9&=H)v(*WDnLYh_nE2Tp3i?RM{c>J_ORdi#*M=7#SDWR*z|`R`C!`JPIGHD762`d zQRl#+6o$NCJc|~}%Jd^6Y?ci>8yk}Vc?B4o^aqTK$v>qpm-WOSwE^YLjey%jR?MJ9 z;>c4d^7DjL2jbU(S%N?~k#aM*5RY{GQg~xG6slm@5>l`urWSY&k)rSJJ4-gX+IEHTdzV}QA4m-2S|%Hs0Lq`I!{CMz`!8)k*gLaEgA3i7?-w& z{Z@d1)u(@HF8l!mEhKWml)C_{o(O{}Or>h=9l^eJudr90;B1P%N`{2Gn&6}as`n`p zx--{asKQR1^hSPcOw7gE?((nyoCRQH?1VD0m!O&g^N~)%^}F&`E+pW8Pffi9#u9+Y zm?b3QfY?-_S{BKrRh%{I;{ZOd{pf*$*+0pJ&CU0#E^Kf?%^@ssbH?kC3J_LWaVC(x zT4j(N3db!hV%Yx}f0=UQ#1}`Kqs<=-q%lRcfD$;@x}|lbA@8Y?H)_ z{O|ee#(Ak<&HgKcZsB4!{Cn`!G$CI4HpGrl>uPW~#OMh0+ps<<2rd-vg+vv(S9uPt z-k$YZpp&dCYDLw~lMiBq!IF}@gU*aRD&;;&1SR4Uwdhe7NyF)@KeaU4}_xQ0i>kz zoRLgqi@&D}LY@?mogwCax;CbX+1;6%X)!zE_o}U>B?zue57gBmYJU{cI*>jfY(7QB zHYN!V)O`iw(q>+BNdH{f})%74|oI-XY)-%;Tv{G^1B z5su;3wukGs4=l(bl>y+|d0w1jiO%-AeH6-tc@}+%BEb}oy$(tG2TOyw6|nIJE)cAz zhh%3XP=v5T{2nc%Gj#GVD#-V~tvIXt`u zB2a(!st=F#gM!co={g8H*Eck%KY222nq&=H+LfzUIb1F}v;R$8y4SWm5GWhKALb*q z^qIjl!(u&6JK~1tqU%D302~G839KEz11AL*1rRQ1xn|hhoavoAcaWuVAX-2yBt8DG z%^Y}g0oVh!vN^!lMiAkVN)Pp^Jcf3UUt+(8L{hIRe=NL<{-?w7HRk5O6IoB(cO!Lq zrU@bLItW`@%X4SYKJd^JcdweiYpu;)>fv}2S8|$LhFjiK=;fUFFIdmJJ;36T`MmjU z$E&k>jP={;j0~2dLYPxn7hS%=EUqV%sHDN7X`AXi-Q3KXCnQ=9VgGxp`xb zcm?T|Hha!QI~D+c)Owp(M9_jpTpqKdB>rR6_PSL7k=(7z8uawupKe4};-|YPcJLW3 zAl1O|PcEi^3+GWVe+mpj|AYj;E-F1B^%%#+eT1Axvcmcmrdu1t^yK7}6udkS^}%hF zc5-7-H~{Vxa55KzvQh9&03WV3`QphFWlG4_%0B=_3y;7?g)d3@R+pOZ1d?V)+EOSe zC?o|?M|~$=w{kwvct*Y$+bcSc3Uy5GEDDM%5_~%pl=mT?Lk61?ZC-8O+VL@ieuA1l zrFx^2_7chLO^VaDTw@+MRd0T&ya_oYf(Fhf}s%n;P zuO&vgoz^3!qPVf|3XMiGKl<@a+2lNao}g3eEEhFT$WdIDr9N@W*uc~$<^ae4YvF{k z{~KDV#ASDUwCJH%hhyTbcZcZlUqLOwH9YMkT;Z1)cwH}evC#wsk?3JuKCQfNczXVg z@a~#i`EkS37cdX6QBoK%t^cZEXfs>LOnSXaT!#~NS*cy&%NKONwz-~xWsp1EKl#JW z<~8Fvb~eFN=;* z$H&QSxvcQvE*V-Td=tZwuky(Ee=Ta_J*8ysm#JOcv9=BtfAPA`#1L8m>$Z_7!Cn^^7xrt{0v+c&@<5gaTVY`^H*v%ZC~}2AeiT6ZolPbBC&cr?ZHSNu zQf8vvlq|~wi+-cQwWg3mVOV0S?{{X=*Y6CEeyno8i?hhXb-$^;i4Yt+dL=II%bz_0 z`R;Cw&r&E~%7Y+y1dkAS2{E5OEq%a4g|vwM)CqXTESW8IC6T7A5EE++SC0C69m;~s zLdu0hTDP1BrlSc!A5Vm??E7$n&u@1|;Klf8VygCq=jD|AhJ6V~BnGi36v_QImLD!}~MclOm#S9hP%=?avYALWO>Vf_=%! zNv}d1Nw&9lnzcXp;RJVGc{VRuZZI=*Yf<0gt=vU(TgH~gbaxN=e!C;TRb=`p=LU1u z2j|T+vvh@@{zqO)lQ#YwyH%H+^3^ZZO7(_;wfqF9!lMb%imE&*socZ+`!>uRV(yMZHMeo7ujCo9@J^}zIx~zx$Zrx&`@(uiSY2vx_DJ3n>ZtvmHsxv zod+XlO+84Ap-n4`CvKV`|9U~{=*=Hr^D5&v2Pkrc`ZVqvD9BM#I?*gG7r6BaUQq~b zn94aR=QEWme~xyE<9T|T+(b?Gz{h)&s@ue_R<0Lp&CHm!Cc@FucjPy6`MY25Fy8Xf zlTI!7SbF~S@an`4Yu)2d_Mh@UQrw=DO7V0oc-@(+_iViRJ3A*Ds0oN4fROn>Zn43^ z6a)ySjj-lfP8@6i1AK7?l$~j~E+Hz+$jAr}jiT1I>2o;0jT6B-2M)5>!{qNg6)vJqRY>$ z#DiNR)SOG}8qkEB@88!4Q{~Z^%|F)FWB4JBnj*waTm_BaS-pqW%i9R$meE$HK@bwD6jQ{mxPn+LG!EPaNX3Se# ztJ2cVDFaC4rsjXBSl0^(0f>K*4Yo8jHRD)>_q=S-9X(IHjT${0fe&G4PaXEQO=N1i z^8(qXFtId%Sr8OqqJhE7)decw0!N~%D&-jATsq+&cg4~eY0JYhG*Gp5tPH%l2O^?! zyXnM14TjLKeFv-?%y^=t$F7Vrsxg?Zwsv58fhtIG``p&t!{3t^a~)mbO$0*>pP--y zuN0*6tUlO63>{Lf2rC~p@XBzz@_0!}Gsy68ygJ=kSHpaaJx!60js@Ra9K5l&ht!I~ z%HBmXvYxbUHG?jQ6{cL*E89*ee~h%zz@p#4zyLz*f*%^lW6mUKJ;EU$X7G^Pz~x{t z1rV?o0r3mNlOr@JoI7_8lD3}DTlWBwy9t4GdkFHw2jNro*R28rfqexoxaB=Gl%mXu{m@g)G9 z6t*#;uOlKdvH@ye+F@`;j1oA+fCm8IgF1iWX~eQW>CBMmLRl^3qBjNhwK)7Cq~aBV zg|I@|lf3EDgxgIlTTsxO?>u&_>`Q4`hKp^O^-b$c%Is?Xs`dRZw%^one(unZd`ZCX zk&9QvS{Pjq#l(E46#Ndkdvjwdr+8=-HyRCFNk0g#*wz&`@^+oSw$AIS_lRCv@{ru! z-+lY-LcqF#S83@qn{ahW#_n{{*xMTA(-Hh!uR`ZS@7YItd@?e&dYu%)d@kc9-sn&R zm8V}9)_IO`siR-rD_?t>kS}SnpSx$aKr2(~{F`F-u&rEWTK_Rgk!B`em%6;RNHkyNWc6O+z zf7hd5%IvR{t3LAaki@8GW8GrOeEm`r(|N9yVAbz81>`xS1Ysmt`7}!7 zJ#psDmE_Dfj9fMr*y=;~i?OvV`&Li+JZyE%)LM3UdP^v)go>$_mLqnuwofET31nrt zc6%(4M;HL@2(Gw+08IGu<>l>HYhITY;1zgRS$P`r1$t$KNmA5PI8$J<35FGI zT@YNBV8~E@*J2EDP$LVAAlP+d)o_`bZDZeR#$%zY$tmI>*PqIG9o%uXm&_ zfZ!(+z><;6yVBD+_bV0&ZzF***!_O2qtiQ))6;2VV_#C*5FWSU{~+W3j=eL|4IrLl zfPSlzDp23wr~5TuAf&bRMrLLQ+=6cdoa>aJpn|pz*Nq-4>+BOBqGK{b--^3!y4@nB zj56EU((zA85zoljO3&)WUyKWAx*;JkrG9NB@C`;6M|j7~`#!$XALgJV5Mj8%Mrdeg z0IF3wO6yUohJFH+NO6Ar`0CH5J4xPt;K26y7~A9mRY#OTS>|c6Cz=3VPQ&GK!@5XcmwNUT3TBl zL_vZs9+r4uDCfUPyc&t+x+L)CZS{%->bVswT=MXclfR4WrQ9xmkg+m{+hu}N3k*?6 zoSgcJ><~c9JWNd5b$ee4UiV>?`JkqQcg{!zX%A5+wqSc(5CF2UdNB-<5F_;}1E6Bp zj*ij-gbGAPY5<}^@O~10)^OpqhowJ&4j+L?@&!ynKRQmk1boh^Nux z(>~rf%rmGMfG-2Vtb+_&`})>rwXj@gIkt$6*(ie-m5rvBtq}MI&o(7F`8aIxJbw-+ z>2vkNmW~cKSm%c?SVBS~8aQ63Ni!Rz6TNn+VXOK#ZVzn+d-_Nv-ZZ_nxj7RT*ISr6 znIZoN;tLWWg)s6Q{Kat5H3uY(Rbc}<-UHUqerz-~G{9LG06O70KTE7PoSO(WZFg-v z3;!;sd~>-K)r+$14U=4~K6%1P&MfQQ>KPp(ZlVl{2SMQ>7f+d<(qzc4=cy<0BKOK( zb78$lp%Je=p1gm%NPPNv&y(7!bIDF-9JdKXwU*JBr;PLNM}%Fpxofi>R=OvvF`P8B ztM-F~;39gw(K6wx&i1p5!?~t{mV|3X>RUG2HicbIDM77uk-BIHdKi*u)&`Cah%|_+ z@7v7FC6t}#Jgy^u=hW1=A$9;WNl2*fn6>15erGtTo~=2xNg({A@}l|LPw)14UIwCe zVgS;3T(4d$F*uOS+)aANfed)Qen;ny|KfL4r!Hw9Yv<#;>AEr!p!$xOxRkh(EyD3; z{|)Ct!C&LA%8G@wXX#a(9odu1G9&Dl4{9cY9jo>RR!RI`Mnul8ls~E6H=wsKkyY*d0Rs;Y@+0!{k7x+OLKMtV%J$BcdpJd4mcV7`5A<+` z$HhTw`cbg{GpllQjz~(%tgI?z;>1N>g9g&qm~8-yf5&3JQN(=n@9A0p{)4o!aV}JS zWYpv6>gul#QE6k6nCXG+X*y}CkhUmk7BP3qBY+0=v5e{(UzY)W1~_t`K0UwHlA%$3 zRdiStw=%K%S;*%0JiV?9?GJBYUdtF5{9ygEyu_27W)>LH8*PJgc1oAh7r1d_kS6U; z611@Y4o}I|K>H^EQfc(Yf-n|!RwD|TsV>GXSo$1Lg3k&!aB#l)%{17&4p zU>+F5=FJiTT7V!nyH&+mLo`~d-BF0 zi1op+<$&OY!1zJ74-nvxMhP_Axg4w=yKyt4aUL|i__4xZ(08#_`&Mo z6|$g_5@+e@&V=nm@P^hl7i!kFlXJGrj`}8ie&&|BpYyWsyCx4$b>M@fD>tu4I(Eh% z*KB$z=gD~6$eY!I-{Py9N|Qt2Ro~d-&JJ2?Pie%jjq1oBRNw5h8=$BXEBf^E=E-3E zo;keF{@@FQ-Lf{#n=O9c?0%Tu65!LT$!;iMM1`NUgL_qAOE9x>)SNn#|0Dkrwz4US z@luWbBwM1ZQ+6lRrR6Rb3kUD|zsT2L@sw4C3)Vd~2~{f#E_Dm_=+fAX7iR~GFM9LE zEhHvW<5tbDS6mknoDP|^l`iyt>lkC3qCZCn2yXWC0?q_w*nWy@T z>g@>~w-e9LEI0(H|JYQo?TkP*){eex?o({Uik!M+XM-1wpG|vCo=+8<|R3K>N`Pa zwhZ#r`-20GI>YnMiL#tC-`d66*pm}$dTSrtFHz$#ijQ*}HoL~vX(q+*mXnn=4KKf? z`9|XY@z3zCE1YmvSqNbCZUzl^be4JvupLbhNU*IsI29!OQkG@WseUB+OJyZJxZUss zL>jsPPJ*yL-o0b`n#&3=c$o}MGU#t#&aDo8kTHWS8202KHzI39S-!PJpEkJr@`}+Fs0~-_aq?j`msj0D7*vqGXU+8F$iBeP|6SN+Aa*9}~ zuNe(#O?A#lnlMEt!rp_MH^+@T8PvwcQu#b~AI^1fH`Fl4Em{7;s{+zybLLoWoiIMl zyJ{_RcQ3t{;73d=WCu+wVBBTo=FTsu|JtbG%B=w1=qlX$5Khg)!v6g&^uRzP>&?TvwcDm@2D@UVj9d8BBf&a*fNnf`EYAZM9qcGarc!T)lFw1 zllnAP^XukUT;yyLBaA@2q1mG07}jPf3@hg=oDMhrRu<+@;kwX-WYV3zDx- zgxyxTdw};%od$!Zy0PA_wCbZtjP#zUs13soQPi^KK?$GQLiAc~Pgfu>YmSKterRd%@Lk7@D4- zs<~os!sE`ti4(7PIiMkt>CbCss((oD$Nzoly^86WZ?M|kOj~+Yj zQmXN)XQin^(dFJ=`3%Q|PVoJi^}Tbre_4J&J{bSvMx8K;0(zlEBc6ktO>neek7pf= z>Xgkh9Qvc*nf5kDR5~=V9TT36nK>*1m8e3j&~_>Gu%>Y+dU~2Y@1`e_JSL2A=nU_D zNS<mH9K0A7qHBrxcV6 zm7Pb;+G?eX==zF0QR!}EIdYDW`;Ino`~paP&!(QT4&r(xgQ}q9OZlYCVUF7wdrwT_4w0&qo|iVwv-PtR=CdX3lh1$ zIAXIk7T6N_?B~$YFx5kITkQ40Jc z+1I3YPouNxc@K$ZA7HPum6y3JqRPtICms!8S=S%rRvApk>xoi{`=aCS&34WUo=7>o zq>hHO%t%2*myOM6^9G%&$BWW$ki?G_ww;6A734Eq91!sX)))}t46=e86*ag>_6JT% zq!|i+;6ZQR)J{xPwH3~_$Gn0DaM<<|OC!KlP2=(7=0c0!$N9!*f!~0P$oI$j|6Zua zEb@8Lsf_iWkJq=K@RfmNEo%8%ggA6oAH`4bitMxX|14y3rrbprd! z>Z_yu^v{%1$Y86kh+u#hym_;q1E(4*WIt7Z7My^-PS_x2R2rtG$;tQlar|&Mh#_c& z@e$@N1$R%ETbK@>aprI%eFxFwi*~|B#welm~4?dc(X9m)8ftR~?l>=rvKiX&`AlDKK5sm9kz$=VA*hQKV&_$4Ku< zFJ__`E`eXRbR!Iar!YvQR*GOEXsObe|zx4?ER3fp`x%iuP%SUXI(QVLfA9P; zg*vU=X|Fh+EjQ;Q2wwYnQxt8ULWOOoySctPZ8RY1?uv7jvsH zoY3}t{qc7Q(y}8LEJ$P=1r8tZhBsl`23A4u!V#fhZM`Z<{G625`hc=$Fx$)2%na#z zm1SXu?RMz&*_qN&QaT4XCWN$u6dXcfS_Isz_A;fE(NsD*7mJJQq9qPAqr`QwI0IIW zyX}D&v+Q4lHuO6mTU&FXu#1A~a{z)uA_F+=T_YoJ64CMTU&F&kgTgw0j(c%RNu7@o zOgj;79yH=*7|@%C=( zlfQ0cWcBHj%>(^}J8}&i4vy&g-PP;yv%;qBX294uMj5qHQWDH6g(qTWM)m&DFfdSi z@uC=Wuyb`NH1wi~coT3IKe%nR+B?R6b;RW59Y%}ou!9)4vH2-1Lc=(01i|vajSv+4 zOy?$0l^C@#@Wj19QOuywq$vAED_S`B7-^VmeBI&U;WRXuz#Y_mqh)kdC)pDYU^XG4 zkB|UkiB#%e{UN-$z9#@`lt_&n37vVUY`h6j3>u}@Ba$?nIIEy+*#-wrE8d8TV zg3v;ZplYDSsIR|&M~UE3ZN6OXyod%!B=A%v!3KpnEdq8;pFD}_m?pP|!$=Rc7b^`Y z5|GL3;FE%AE+5upODaYf?+=449cb&=HJ55c7e~T)&=K;+Wx_2@Lkt=_kdYu+A?!sf z0a7Q;;4wmzC8FhPYiDWK^ z4hgv!Buhjq6b#LM7ccd>VY~IPBV*1%C^&)P5AiuKX@dHxc=ztzuvS#{>b&<(gb)r& zhBR(y36~Em_B`rMb??T|P9KKyVj3}h2I&eDEDFpT=FWq{i}+?{>q#&CO0s*m_Kb1I zFXP`|d@tEvPsQF6t-}kcSYeOtsr|cB|753C3}xePV;td^+pmT#)dpAYty=3GT3o_n zKuYs_+R>XXzuma^4FB}h7`L~6PA*N>U}%^8#h-t(wyW`ipXO~s9xkK$aqeW3nIEb1 z{8GBqlWdEpLmK`Bca(n>{|BAZHtJQ?6>T@x{ATYfZxijLcV?`Qys~+b>Cvh{JKoaS zUPmOC^>SG!M{p@t9sJT%&XhMvS^kFR7DKEDl7YL=mJHuSHos}FSJAdf52j?O-eGc5 zyGAW2$hT(pt><{Z^-ON7LU-y<)7QkO=1widE+oI>V9454cc~)Tn;-YM!f5@=NJgIr zMMLrPWzB@FsK}(5Roh3dGA|~+pM4)}n?lVnK}L#SA4ps;ODMq(^ZLwI)kQmDJwAzz zh?kP)f3NKrI-$dA^wmKWW}23q((K9k5T9EDnVRw?<-=Z;fvV3gVsG+AR|@A8q|LZT zS&W@GphaAW7R*EUV`_{s{SPY^#tX(Nl}z?*haiUTdbs+-4Y*L$!Zvi^aRWQHtz%2`<}U-WC4H+AfIIp#w(&Zof&InV6BXvbJDG0{vj_ zx*VGZ^^~P0&x0p1@+KxU47D%qQtC~X;bE(V1@lusVHMtJ6NvLFGBU3qv1=kWJDUOOp`nH78Q$Fa z3va{BRnfn!$wEUXJr7c{%GeqCnCMtp-!k-X!TT28K+J$2g{(a?&{W|u%K;zptE0n9 zn1aG>Ncg&qRk+?u!oNTWqxu5G6N3mtg}MwVt;GFv;Wq8vnhZAi1b066j-gyDLw!O?t25 zCd?VfVLr?EJSJg>>(e{*)&&v96JBY0WsnFFK;b>1oMv%h;TF&>XldbTq0{FLo;es* ztlZqhU}}J;)Kv2-bEDHN(W?=BTvrH34@3S9?1e7#I_N})B1C&w~SF zQ=3I}knKLCN)cB+i!rP-9=m45+sb#5yE?WG(J;~})a#XcA2d*F&z0d*1T!$`WDFt;#_gFUjdlS(eM55NErYf- z{mt&j?TQY`C=~)jOQ*5VS%(Zu;oFVvL=IYWEVd7ELTk3)3a|{ChwuI$UqTA>(muCu z<@Xg798ZTv&{^c#D!}Ffe@FO;W0B?bz;vI~^Ow(I|7+o93~_P(RPuK_imxg81^YB_ zJF)ed=jRWYoSfLJJ4ts<;|k^mtYdU*u!Wk}i9)E1E&O6jEhn4n#>g~YiISLr#QRslN#!C*!6O@`(afB#%s`h+zBuqJbziRkHM6wCn#1`#U0AawyoDugbN z0f1tup$L$g2;;c5wRUiDFOT15@e_&}M@WF$m;3N^hKaCzXK-TTgim;QWFsLYM6P-E zfv(=Vm-Kspd%|NI>6C=}TMYn*z}r1@J&Ws>TIU6i!*ELi3CSch4i(ui4-Son-D1m< zW6Me^+!lu`TV7tN(TCk#{aYR$f)LYi2_peXOQb1G4Q7q7z|e`4w*ad8dF1NXHLI5| z)4zR_g}P6T2;ys^&PzYwpv*`dFQ@NobLam=>oymSr}JccJ2rIcfmlCKzwdSS!!xwWl#_&2sue7m6C?_2Hd?F_C5PV|KFEbk)_G=PX3L~N0*2PpCp4(`7#?3j6Td6YQKdEWCK?IOt+A8mgOT#n7nayemn-JweAH3)dJX$s`tB5}Kz z7v}~qE$Mp8#lEcgv3X|bf=`TxS<3Q_{vgns{81uwl3tSFuACXZ{3XKEyQZu4!l0~H zT$=cvqIV-M_$JZqBTA)YCIMtd9!BxAdp}14$h;G{`O2C9>SgwRNX?l z(ERJhWB}vlTKW15w^Qyei$E=gVsvGYN(OTc*m-%w;GzM>MBgMpeL`W68tu77Ocrz^ zNVaD&ut>p=2kx!w2_W$%X>taEiUYJAg$&J2a8@(MD=R9HB?&mTnvDfGxwwW)9n(h6 zqzz*D@ycet5mX?-C#A$F)%j<-`Mz8Ao zh;e(nGf+9K4Au{Daj(BRb=7jl7DyM@B9%fEu9OixGT>sIZx+UGW21q%K#ubt;l6p# zxyY2j1~@4COjs{0<+&!O4? z%vaC!gX+P*&dkpS<;#pkrLuC|cN~0v6NqwP7u8T- zVC@(%ArR+%(HTFdmuE9B#-m@M2qjb?2m-6tpKy>!SX7sSf53mnHu30MO%ul@sY+Y) z5{?}s5W8dND+1v_xlQ90-S@pu%0M1&Pxc9GV=3L=g!%u=X30ThclHl^e!+PIBf}D1 zgSPzk#KSl`;V^gqHs>Nw-Dq|v6_;lg=JiqGMy1>lo0~C-=2{Z^|I#c`y!HF;Gmu*V z8xZ#8OZ-c|6Jii*c>+T*c#!0;ng7)1LyQ^*SvRPSdc6r8{ndZ$Df~Vw!)GUXRf6~w z54?^4=9h2Uaj)Aaj)k3{45$^S|K%cm0w;JTcUHeLK~2LxQ3+KHZY_c_1RidI_e{Ff zaHtS`p;`f$v%Gz|-}!~+_V@2TCuV#V?u};rL?>eB11HP*%etc@rP!?t z@}x2RtfzT%PgYU@&51TBSabZDq4jDV_JC}sH`k!+IB#dNB6@7>GJDzrcqQv2aP~|6#(Y+#n&RFWX+fF?{xI4F#wbYY(Z30_krpXeH@B_Vld~_G4{K zU>l(w5fxk|de3+zSd{-%#8I|N8bTuWm}1ylvoCLHtEpF1*9uQGitb^(YtTfyY$mnF zJZj26)tkz?FXf+K&`YSRAbF7Ei*MRu|n2Uuqh8{q>RKQnEu{}3=vyzUXUopGEWau4xnXy6NytxZ=? zMpy@EG^3W*uLES^MNv)748n|H7(BDJB|QGn0>&{U(vb39i1-85=nnW2moKMi95|gN z@Gab{D(ma4{Ijv49))CvXWODQ)YWTXtb85hdn<7;3~>-#=3vRnbAK{m2?>a+1rtdh z%#f3jlb2T{AKVQjcyIp@g6|*FYzYRCA;f9|8>V>HT|IQ?tB zy0lp%y&=HIFY4r4XL&>s-}!m!j_7USq^GJ!zx;4ce78iqENF?ork(TS9ZqRi61Pab zbn!d0&+pDx{CvV)c2*n~NsHR#zqDe3h#4N)R;JQ4@631(%`Ka8Ka;>%{DO*Air#ls zQRiF0lWBOhlPg3p%%cB$%>ObCfb{=GZl5A8Eb=C=J1H~A>WKo$(*i&Hf*(?LY@k^0 zIEB{9qJ8Q*?|n_r2&cT4Se7O&Wv0|3hGZ%Gh)u8Jo*U-PjY{`=O}t4Dl+*Y;5dRPw&-p%kut`>IXvn z{FBh6faC_K1x=ln0hQs5-3UX;@4V3TiMA=_BW$|a+uY(g3)fYk^WT&m z$jz;zBQ+!AETlFeqYrhA0D*NycI*JF0oYkOY3!fl2!{x66P1=LNw&e+*}$ED2Cw_f zHhLaBJ~#Zs71R2_evrSXmc)*_?1`Vh1flbk;$jNm8AD1N%Fh})*E{a@G&ir-H<|rj zle)~9=+_>j@lvr+c2x=t_N1g`JE!3K&h&Lvjo2sh99K32tU#2dVmaaP%f2pJXMw{5Pvx{VhZQU34V zRa@*sYJ*)|TnC56kUdvuIujRf^k+w}wZd(-q*S#>6gM>Fr4o;UY{iDF+pne;j(i1c z#p@IZn;Tl+``7XRkwiZ_@`mpwy^pW&B5LHz8Lq6`G_#|yj04SwPz+C&p_yq@T^Wv2 z>x^lS*J=G-vt?%&__KwB>~ugNp^)1M%)hflL^CkSyng+9n*-N7xRZ5XY86NL>3bah z(P`hl1;cpa;OGbl5{+_3@fX2x6^9$&lj7&pV8YxG5|W}YiTE4uCEMROy?k?#41YUW zwQ6K(o-#N%cnD$&3SkTlFrU=HCGl@!_xjQ>Dj~2*NZ!K+u%?z)7OKrS^JW!P3d8mW ze8;u|0|S`^1l~h3*O~6#y?d}q3VWdK@Et;k=PmEWrw8NW=wK6H4TSpKip_JEFJG>2 zLQ@lv_#0$e1x(waMCTR!eW4?_VCUhvfPetR@Yx{$8YYd8hSx2U<%y4{2T&@U+Hi(7 zg1LO=+>(UUI*+&YT3LNHY-Yh9LiiwXwT3M6SB2SX1Kc?M#KZ(*L6xI%p&;9D0JXQk zvY!(weB#*+@m#R@{=XxNZ-=7V6MWRK;Y@pwwW(gn@tykFE8o(i7&go`<2P${d%|oh zS$rLA@p6x@*0onI``N;XLZmMi9sGpa>4uAwgYe#srsjb&GPE+69-PEA?WW-kEBzT0 zFH7~sXC-5diy8?g9<6^0hn}wfa~8m0y%X-V4$D8eTlb<|Vj3&I(^&>3Br)uW?%a9h z+L|G55CBP;N{YmPH*fF4{o17gYDlZ-#S<3ULz%;sHokvf=|Lyrjy+xz55C(}otBYY z8FBdkVbGrNa9^Q@SHbXa6>`qYJ=LU$6wYDrzd;Ibba4K^Gg-fxX!Vx*KdpxU;8)W- z`oQ9{L+N&c^T)`@6dnVRu|a|Z@yy7QW`yfN>zeT;O4NU2?k&TrY}a*v6%~~-KmiF6 zrBP`C0RaU>x>Ka3yJM&z2nt9^mox~7bSp?J-62SKHzW4-ymQXA=2~m6wfFuX`@^44 z9`n$50%MH(x$g5i&)+%hh8-G!c8NP+t6vX4$}Cvk&#dvR+`)lx4xOyejf{R2bqcq` zMFl}8(t7Pc^%V`^D3ORg?CVZwzzG9o)ePL+JB2y#{^P>p^I!AAjqb;_l{2W_rT*%} zxi~1Eax(MW+1f(prlH~C?vWAb^iT#`IkVY1{5#D+LCjBZ$a8?+D``Opeucy2F z6?A9AJ`;jlGMgjqdeGeH0H`$xBXP?a9q^1yt#{NwB5d>kNb9gGYyepE8!;a5I%w6M z@Y<(_!aY@#)S&L+x;o0G8GdFYJq;lc_~x@`58LI6o4x)zHi}eRoPF8-uKKCaWdCSs zI)}%F#}cAuaLvHKHlS8*zv>d>yKK3Ps$_SfCnYyUTX`GK)Y_Hfqdq>q7bQZD(h z<=Jwnvs1Z6Ya5u`&SSKaTml|1no&{kh4SGEpw2kfRD`}h5~wAcFftUS=^Yc|9`jjQ ziJ9g2fcwQiQ9)RiO962o2F!tw9;xmuVqail(=nx(8650RQMmV250&ytX!i8zK4a!y zD~BThVlrf6UIQ@)_A{>TInE!UdFEnKl{1H*ukStBjXd*MEy1y%Q>S3wfrnsNbhH$B z0pRP_ty>>He%w=FsPhuS{Lk8R^90xDf2BQ}TdJtPOiZNP+oSKtQd8B3O5X-)zo4*; z_LYp8I5?i52m$XVM2G@ZSkf~X zt}ECt_1Qd;b5<6qauLi5((ZD@9~^JW(@Rxo=^Etgu|jLRP=HxKKEBCu8FB?B+e2Ml zIu`L4-hi>!NAhE>QZ@+V$&#zZH8tny=`9k_aG9`&MnJ$`(@J5hrlO9ZGGa{``NR*x zCthd~H#$0c2uj#cjYrwhxk7mQBMhv2`maZlpidqi^C7a-aQRS#bS7-VYin!wb*NTz zJcZ8VZY%_Nu1BI2D9Rz!``E;U0T!IAq@`K^$_alOQ*Z{hV89+fgaRO__qq+PV0ggq zyg=`6cZy6;PdC8*1o6fq?)!IlYZpkZp>$Wbw(5keY|dsyWTYx1pVj7a7`A5WE)&zY zuC5QE^y}{@8X6h^g$^$O=Dmo=ClshCW3$~pkV1m#0Bo+B<+gM%vn;u7Obg@4TsgbH zJW%yi7d6*lxfZ1dLwy+K$F_?3j*~ft(>H5F5RXxDGo|3?MaKnPy6*nALt?I@-!#x?hz|uE^C-JX^*VI3q|EA=+ zZy|A7_M^LzGY1|BN z&kk)putOW&Y3iH;CFDRulk3lrV$hfM_vEekhfx*m%bwf|+6|Jn2KA)&86CHr>PeSI zMqF8stJlIk-^531njRYMaJl|A#E*SAVfs0o3+#u+vxh$!89ITNt5f5yiWw_Usk~kc ziWvkyO6nl}`vKODWxW(jm3}A{NZkJ`xz=~b$q3#-phtd6O?AWU4uYv0*&Ug?0EnNM z>i$EpHgU$h2OPfh+E_NPST;kt6_8TFT!SQj!HlDVk(4CR^O_BiQEP@6Z#38Z*&b2YSgw)iMY8{s;1PpUTP-fe{DdKvS!$n4|#A|9eq(LR0FH z5!%_o4fz&a_)f=1Z`7Z;y@6jD7~Jik3!}SxGi9zWFE0qOpTjK(SftG%wCh*?ai+i& zv0jH028l`x7&D29a#mJ(?sSTfFt~Uz`I&1q#2hcYc?|^@%^Ad{|4Cf`Wan$?GTVC_ zji-z260IHC!Vv(FYsuC6cXjLJcONq=D-3SfWBUtBZpkLD;ek)q&(u>F&b?;3!Q{gw z_jc-K#{4ffb~YiRfn(O=rHaqfPuN+11)Ck2e2UOaWGmr*^yHMv>2c1i8*L^Mb7puR z+sB4U&T9{cZV^vzSLDx!U_Niw4r=Yk?rD+0ZeFL-F`&L)3=m#+PEM*?U#?!fxPoei zp_CJ%gwMQoRY8G4J=9_wPg#rJFpkQ@s9hwx+w{l9Z3BlwLTD$ zj+|D%jqzwqii+}wUU?w$)4{Ug=T8}kYWL7K(G+9O9ui%EjlLnAf2;NDV3UIW=)Ls` zc?SnBz>`2eaX0X=;NkkCAl&b3mtrroyhtUvvVLu1-ndxXz`vi3RcTIm=#8Q-t1d#D z8TqXB$l{TgTu7p}Q1-8^+cxTnUAAjty$`P>)d+J>n9XhFw6|y($w#0&?~~B=cwg+D z_I6KR)L35Jo*h!iS5yDWGFv8`*-VW!EZ0(PWgMmuORXdjmt`kD8#Eo97BC;?o&I>B zY-F%4l2x@X{F@1s^ftxpbpbVRfpa1xb@ws9sPAu}qxWhJ+ok=`-5CkD{nB%~$ zb`@dKEM+3(nTL19hbG;{Trczfx43@7zB6R!9^5&NChuA^Z#e!NuHSiS({o8{@v<%V ziG7#x<23ApGWV&2_*4ZEtS{7;q}I{?+OTR+99;6s;Ial5jlj-Wvmnk!=AMNg(fX#R z7W|~`X=aubixZ5iW5hU)&s0mnFF6@TM&f}*qkGb@TWK959uBUZFYptjJU`=3DBsBg z^x`wRx?G|Z<6!lnX3T69LImE?a~DSNUKSP-yt^97l8D9B(3U?4x#{KQb)Ah}oMseV z0Om7zLpAolJ#}jQHUonn#8c00o!?=Cshj%Q`0WW){jBX#+To}bC> zK1at}@t-ilam8sTOS zeoA=u`iH7KQ;|m&yuS%23Op-{(U&P~oYn(5bS-qpuzw_4Z0LM%mFV6267;pvq?RZH zn?dNVe=&tQCE7J6i>j7!cVB&LeK+8mu!rZpU9Luxe4_93uPMj{zI675e2l#sRv*rU zsfZ}dZ2n}J(VObjt~T)!b$DR!Hb-@*Gk#m4frOKL%G~wVNs~FkWz#l?JK`0b4#d5d zD*`(r?Hwmg=?#Tj%6n1;qK%9jx;MiQM=M;PRYkLr&oTau#Q&h9@DxXDZ+&5xSNQ}# zJXetDEoi_>Drx8-1#rs177*dX)6vmE)bAq>_EIrIe~b*yzL)SF`n0s#_%4&DMt9V>8E;2_K= zl~fzZj29LPfjZXae2iTD0WHw@eS&_G#>Xcb{I1rit{G$*!#gFcKlgJ2sbftyz6W)z zoX=m9+`vgdK=C>tY%k#I>UzO0&Wr)efdkMNs%vV<{&J@WuJG^-^4op`ezh)bz#6#I zp^kN9>j($$%sL$QSSIlS6r_X+o1%^?y=s!Mnq>R<%tT*y(LVWB7;j=>bUGu|NF_trVRVcEO#yfKgrxD`m<0HkTVzMR2wN?+HhRNFdZ*-7$X z)a`R;HY2Xx({t}g!7=UJlZw-WWxB0Lk{_klbh}kud1QJ14&ay74eB$_CM4%N;crYV z#=LjJ>ML)DWCEj&I!$D21D7`uBI)u16}&MXk94g9*xO+_((dkIaWrZ_Fcv<)!h7)os~T*V9gR{b3@N9 zeOmS;91O1u2(l^_9((2M)P7vL8riTPfyGK=cd2(cc6n|)=rTkyN0NFfe+we9o*r!4>DO04-; ziyX5LrAk9XU3aR3E6>`H?3?InvGkOR?CgJMLe)}Te?9S#>w)Ys7koaS3ib0bJNEGm zarT5P%s7y8bDxtqJU?4C>b6p27bHuMp)s$75#W)eq|3ow6^PI)C|pExCUCX094QWf z@*?7E_Z%G^5uM_09hy@qySRB8MT&ehA#n&DF&gkxf|iPZB(fG=QoZ5btf;Oo30@{p zMu7ambafC6Zs&-IX0tOBr@+)=g2Q4E8vB;;Q{6akjLzbq4S8=y!s>?aJO5_ZM~?$2 zI1Gf;)TW_?^?kxPEx}v(5WrkuU}1q62*4m!UWKy@<7Rq6!ez=}Xv&9oEh_zIQj&k} z@`X1Zj~+piF7C^hbM@nx$Dcm^Af)(8_>xXMv9YV`m0`0O6VpLJd_1`~n&X_yzG|Q> z8&tgg4UaErx2gH)(>@^cRgE!0)W`hFnTbs!?w1D!uEg+j1t_Ls(`R569t%61#_jo# z=h_;l4UEYG`4M6NJqKGx7W>y%3jpcxp3TMss;$x8L1 zH<^ST^Y*PH8eRHa|B`R+@-~}}#`_wK47^R2=L7TeDJUr`ae43Zq3cm83@o?Q?`cI6 zf%K37sL02d)c~~M{hub^UwcBm?b`~s8-R3;j>E^6S(_oJ2f&$;X6n8yMXxsOj?=Mw z9mYX@jYLaW^~eU;8*ocyW@5U*#dQTz2td0E@x=$z=`F}e*AERv1F#z^b0i=-Al{3E zkRe$o60?Mx{S^k8;Z#Bb3#_aJ5Z8&ELUnEN3ko7-S_F5izk#t7 z>3M?7>MX$U7E7^^qkkI^@CTM`(px9T%I$n$-a^c?;CY8P9?Luql(1>Iq|T33IDl)i zyQ}LVU^+sA?pCoR;j03ep%`>T=L z<#^N0bkk)vUmA^|Vxg=Ar1US2i%KxTR^G;8_G}VT0x^Bv-E`1b3BMWe(9?0hcy%n{ z=A^v(nL`p`NHKW#+1QPW4`&3<_a(ad+PgJ_?H7QjT ze^?cJ)F=AKgvWzl?o3}(pgyh}Hr$W!^zZM8@bBr;J2&a!SFg z%3uMckoJBFzWMZiB<*}^2R(CmvgzPveOa$L<5@`(1~)m*yxmnnH7|Ux;v=&Zvrhq^ zpEP~5!^^JK$(w054kA#!23@ks`PC;koKDhcKOjGvQ@9X9sbh7`XnvmC{Q zXP80K(F`#k?yg6jq9j5VLVeu*_x89m=n6zcVh-+C)0fyeN`7y}CFmEU9=%M7(?VvtEOg4= zV!htOvc`1i*4z7T<}0T+^Bom;)T9aVDCX3me-w$o_4v$kbsTku-Q6*nqm=A?xuv9@ zE3R8o%y*|L=2J*`ewFg}YfV1ATZ6#XU|(^YuMBg(3+SB?+5QI4Q(7r>$m@xMA4u;k zJjK8W1$p6n+}w}U)uZ5ZNQXK8*80R-bqz8-0*+CwvDrCoVv?CC|Fw*aM7Vj@p$`l% z_V*7{A0eJ#C>s$1(-W+d2!Um!r8NV!vb==aqfmxnWd=DYq*j*GtcL{b*5?8q)m~Cp zw1Yw6w6yePCRV}Dv?r1kPRoKjJ8Um3+0>*93JXua5SN$t)*XxCdU}HAa~`0$YA=Y3 z3r)_yk^va^|Fh5Dl zVoLn<$(4`@|If@0sln#vo!&ASeBL}hflo5RO38 z2h#AfkL;Of;I5A3?Cqw{o`5a-6r39fzoT!n8_MZ9`1nkrh<3aO2iq$RxMw5lQ;;Hm zw70*5xONUHG8#c<3byig=mR?$+1auY$gnfXDP`s0pz&pUK-=aI3_pM7q5z);@_ln( zUj*3fGvt#x4!7r3KYoWcgTHh4`}&N4yRTE_oF~~7l@rnF^HF~Ccb2&xa$4nh?Vg6* zInrvmkdd3)4r#=V-Q9HXp~L&9DTKBYybfgE;Nb-$(hWMglO7%(2-2rJb!)Q|PNoi^ z{{7`CFSDVApItep0hYqxV6umPOz@N=1y&6tB1oVdnnywbgq?n!0xP4w=`!~lY+Yt( zIQd{Fpl@OEPKJTDPOrv25(t2=#Qlkxu~LIE=>5?;CkgevUg#n5^*s#(_um_n#eA-o z+o=2|TjKjf8+$f3g6ghl`y&D(AtGtHi01-rq)ybkcgEKxs8}-|)K+V6`k)<~4zsoo zyk;e_SkG$QYEbt*`X7Y`DYraM(DTv1MiFJqnG?-t#4B4`pH$J!K^kn|WncY!gY6{~ z(b+d`&T-nMilwXh8R1Wi((XKc-0%?3OVVrS^XzqU_fq#43s}1gjPt2fJ!%<$aCvnG z%G=hI)-bN+1&0~l9vgGJ(ryL=V^d<2Mo00jZb>rG3*Tt+N)@Du z6H*zF*L9}0%Cm}gjeGEg^991&PyMLvVWYUAdXhJkcl-jX?x>2E#XEO$Y{ucoaWlQU zxNk;^iT`ZZwI%<6vhzHjVkLXzq6nQ$KK1JfR@U5}zR-U8%6(zG3pQ@0xNBX%j>5Qtq?UCEbgws{joUhEFNr4V>;Ley31aOvY(6D|uW zoh&W=2$?Jpwx5WL<3SJ%r1u9Ajh(fL*u~yVNNN}l=IbULS(s(xJ$%|OwdiGKMGVwq zUf%s@5noD68Gr@_$%T*5-T)1)T>wtm_fpl&$~NPC{!Qu)%3=Ei;OfcZv{5GCUS5*A z6)AO#Y-Sz@jws>iXmla->PmI=$B*uTfm3VGB+|ZYkY>ugaoAmI_VxX7;zLvv{!4#< zeFO3Iph`O`5Fqa9HkaF}mE>U!ua2l#`PS|L>4n zDc#=G&P8`h#l{}#HZg~{c6D{R8ZDNUv??g~E~p7vnPrPVZP&P62TD17?)~8b3!s|d zgPLlX3XdZ&i_3c@cUv;eapOHb^?*P4`3es|B7dPOX#-{rFwG;qwQof5@bExvqs>Y5 zC#IH$C&1n64Y9Hv5u=uifUPG$KI(`$4Eo@}F$N9uLM)BAd&mx}zn_eNfB?Bp!D(g? zpP7S&a~)xxLKsaSFpI16>->n_iI`g8hsSyi;`FGDY#tfWx}#h910je&$+#Qbm8x{> zAiDW`ZRwtBJ95EFmZlvq^ioX6JZRf<5{ZtDg?nN>ygq(^rE4p+*zTN`T|w;N%2p^$b4#;V(-F*(@wBo)6NRyqi+It%4Tk*!13nhja|D=5^#x zu1lwG)LmuU$b4wdW%1;k>SBUDUV8jPdOEcP4#THQ~%RG-QGI# zp>YBCUP4?CZ+5INvzpyT8IBE1y=~0-H23#uv)@S0+=4_`4_v0K48qMV>M zKEKrbZSvKSh?V@@XpGPZ_T~yzIVDYNN4H_Og7P#-D-(WlDqDtW>5ZR6X4L@_o zLW}E;Hdp`9-xqQdSmZLfBH|_h<=oW?&Wgr|w>g;$G85-}yA5sc-6JL3y3%(4DQltw zMceW9N`crG#Ndp(Z1Fv9qVc!+4zR1+uDMuRXT* zcS+lSgbuOqS(%&jgIfnUcyH0uCTxnJ#~dJ};*b&+~@8j7JEWfR1Jy}`jkT>FuPiQ$Wd7QP8WQI^Q$ z{QRJTb&sF1PN7*@r{;cM>aMH12EmpSGuL&%b_2GpP+;xC7t&olJbYMp<#_Z*(}=z` z&9{|db@&;s(0C_``phxU+JN)1xTJ(xC_jH6r<+q*IqHci!7DB8jS<`z01&>RHO)h} z3VR$wB(L4vPlHpZN-<@gwTq!g2>HhFZ%BHh-_=y`oh$YgOt?YJ7q)LHm6eo$qXP^V zjX5?hE-nU`^I#84gVPp<+1>NUy@RV3@;jXg6(WR(l}mLnfr1!O@mhn8frBFyRt@QI zz8#+jVvb*CW~RZPO_-a&N)M4Y+E}cpcMyanW2Z>Nb9R@(rz8`9Kh*1Z4}8RsE;0aJ z05&vPq_~C;Ht>W2!3lW@k*?b>Uv4lnXOA9r{`{$OP~~;Z3!_g4Bya$X52xQFeSJFQ zaaLK$!mi;0OH;p_DA@KO!wVeY*8O)sB)xoAYN=UeL8;1~gnv8f;h-aMy3IsLzIq8s zfyp0v`~{ zZ8I0yY<9M{!*(SUp*uuHNy0`IFp`TTh9s4!<)7Y;#v({xv^S=53g+#PV)-!irPvXm zZ}%OndtHJ9;Wh8kJva=#o=LzZMdR1ko5pLv#_2EVF@|Q|tY0~gizWKzcNQx8!$;n0 zZ8^`L-<`h0O~k!9yez(*U;-s(?>|4@do=vPG~WNB8T9tri3oK$e%{?Vw6|nZ2PS>w zmUdF_G$`acc{wxTD}*VA|8)^i^^Zk>oqsq;*T3aJ?0|N221e7mWWra@V8};mSl}rG zTY()d=u?7bY(v1czV;mK~M#; z(Eerz?)sIVvHA@j50VDlkIpQ9AZfAsR}v5{$w$AiaDday`ON#rPixjm zWP_>r>*4MA=Jhp&8k;?x2KYR6_48bb=$3_*hn>CFnBis+O-)epjYB@E_d&WE#aSww zJL~J1ZwMA=ba;4osCl0D5%$d zKdU!Ea6C#u>oKqpZ-O*udaA?LWGBWs(V=wK)_RJ+&6`==Q$eO0^}sl@UB!7lu%lk@ zc#tPBgp59zEWC&v|K$qG#9?_`TZ~!wR{S%2gP++P(X#g%oKx6ST2rEfmTOZ=bYDFE z7IG3E9-j>JQ-fGd5$ULlp8utdYc#`2UlDuTuG61pHN9+9+MPQUPrVP^?7;jTfY)rANGG z4qYIh7?PXI3heV2^QG{=!vPO4;YNT5s<$zwP0)Zh0d#VN00{cX_tDWf;El45HA8jg z)29T0p+K~8P@IauU6_9V>sR5~okcfbmr*+vVG^LtQ+hFka`!HS%mJ~oGD)MM z(FUXIMPz*plIQpD-)qE#Rku*Eij^m0((cGM zveaKlcqL$D)l%=2uyjQU<}@_Paj$LBtK#|IwMBVZWg__0AFS+(itCBD=0VM3<#ne% zy#mP*rpHRR7Vl9<@#(a9^TqLM#!X?q1Oc^u_}o#@ivQ+4L$*xKPlhSIdFZOT>EH#j zOR|q;#nN5jQ==t=jXEr^nI15-t0bq`e!qS3(`7lX!S9#2bcF_|KsbMlV?x5yki;C4rGqCOumFJA86j*>eJM1!V4Z=D?X|57Dm04;fvn>JWZjg5 z+i`NJ@ZyCF-ZvQ;{b*5y_2XWV23MYHk4D2xLQ2-CsN;Dxbir$F?1`!-3h06zec73X zrHQ^JyN^sdWDNSUP|SyBX4&aqmC{_z%_BrUTI*kW8>z}2pw6S#Ht9pv*Efybv_i~&VexOj*DX3ysK=<7F+FXrcW7TM}^?Co)> z60d`NT~WdB?(QDg$Or<uvtUVD`J%bgav#9m&nPp z-XfKY06GRXZM2{Z!dd^UQ7HBYy2m=ejd||Eg(-NGWCBV(h6_iILxyQ!xo|qKXdwSU z1XVjlavFfexB2-gXlZG|Kyf$|E)U-DAHf!1z`TLru?#t$@B!0F0 z5%u(MNWHZC^eOl2ZL{diW6f1EAt*L=K6A-{%=zhxJ6r8Zfh|;a6c10~oWX_5+3ywc z&Ke|V2ju&ml|{VSzA^cCCPLzVeC}mrwlc5({q2i5s-VK->xdP(vOMOIuEkTqebKWL z(dNrt@=xO!vLl|T@usPcDQ<0NWL|4EHbyJyWbI(T{h}&8KC?w~mzH?EvUSjnPY{sq zfvu)_%ZqGap*q6;k}O=Yu18Yw-$%>$k(frg8(Er`iqNORvlbj?-!Z;5|fm~kCDlUzKDZ9_?3ye zq%HeHjhs(YoVRbEkBGRXpyr(TBs|!DdBA01QL}4E<8#C_L&Ni3&<}LxglPxc$-Xb8 zi7%^;SqO<%--hT(sj3d5UKf>-H2?Ny;MC;${@sIY-Jj6Sueg|~xO4#Lve|VGp1*HA zbjR7Xw9Luso=RVauEWeMCK~Yu(fj;DZ!|os+jp$2G7a=Eef#dii{3A4jE*^zoNnBe zXDQ-{%cp-mAL06Ev-LG7NNl`_IcpC2h(?yvAz@wnRsDQCFA2iuFiGN2d#_ zRDp1eG_=5ae|xi;mi5qMF+B>YuvzNM2BxbpuW?tTXTmJ*pEYFJlUdo>ZVQYQYh~Vf zU_vST?M?tC;z08hq{81>pP+>gC1U%8>QfNXuR+JcYYhIa%jW@$w9+LDI|Znh`2*=_ zK%GNB2eR->5OiTdBZw6MP=k05?ch-U@*!1n7A)B!xOljbZ6cOzn+air`q?L8VQwxZ zD?3~Nh5+sW$PA6BP2lLNaNWEKN{}=kCpsp7YW-&o95uZM&_9jTzJl?O!@CarVo1CQ zq%b9PXg=4W>TJQzUESez#DLu;sOYAiHSyLPx(1c(t536EVdviAX5v?Ty~w28=hRo_ z2*i=k(OU;w){P`vy@IeS(FI(hT zkWf%zyxEhzs$uH3jb&fGC&4ljo)6z2OSnAX^~n0{Q@J8AqBVMNllKzKk1N4{Q*&wF zt&2V8cgkZ2u~xZ%OeVEBlA_f4R#a5U=$IDgP)vYYn`GH-C5m^Kr#QO$3_he>Dm~hI zL+{}w?#P3Ep{LE6|Amd?)%2CjjgO|ssFUNL3vTCD2Cg@j4S$RjEl;Qkt14SOF*hW* zh3mOn8udqxKQ#o^*{A0{^)7zd^Ilt02;EST@P&Q}{Xfid2^Z#<`a{m24>)<_?p=}(Ntcy&cz*we{yZ4RVcA02)ihj$ z6Sw@FHw7LUox+OSgCSez^Q-vEuJA1ATZjyKX@3u+>mj7ISAB zNbb-S@Zjst=!g+$#E!X%1ATp61$rWp=%b?}`}q#)w)S?!eBCSTUWEw2svimjovPm? zp|CM{_{2=QbaVeKrwggYY3FpQVj-4b>*Ul38xUCH8CY75SySzn4;H`*2tkwql^iXqq|knIS8t^2=w>AEs_FVz}*Kr z91CyRnkjlX0c#8qLF#8hLopcdF$i$hTuD#JoT3d3*nc;_wbWkRI`S6&RIhyP#9@iu zE0HrwahgxXUYrV0zRW>o@OF^+ZsZu*mFN+*3&dm!JG8Tc(N!1pX!PU;{MaOLrrhvA zzF#*d(=&|%PN#?z^$nT#U0HHKxrb)OLPCeYv6 zPvx-Xyup0)3G?UW(fqM7J7v3@8`l3?ipBqDDYonMkuaVQz5y8GVr)$JG~%J%SCrBF z+jtu7eCDs4`fY@;a&co&Jj@XVD$`XW`TtSoU8g^>g?gCiB-Q!=j%p; z<~ipDHu1`qmdo!8ox;Zxzoo=XcKLjrOl;F^jKh5%GnP9NO)|o6V$3Uo^b%Y8bE<1n zBYH$fdmESH6CWh(P!t6uJxIuzGx;r7mGzbQ*1xN%<;~~82HyGaYN~txCt&zpfA;Jd ztaHP`>Hys}u%3~|3Lh=d|MuD+cg8))&&inrjU#Y=fc>!S@%Gl%3rout9(cA-#KHTe zcVGZ%1wtyotgNga9Iq5zZ~@jM9O`0VUVxoqSlDIos}>CThRI9E1-yIrqtk{!QPH3& zM1+l&tAEh&`SXXs$^z>RFdvkmJSE_Y{V_EqCDQ%`115+f8}QIC0ssY=dyriaBO^Ze zI1)Y9_#4Rrm9j}$S!eCy-?iiJnaZ7o`*w1HO=Ha&NNEN2!KT4|A(LN~QYtDZ9p>IHOia9+Zf0a!s(>{rfFROSZp|t5+_5S*@GhmJR8vlT5&_TaH&o0!uJ-X~`U%E@ZpQE{Q0t z8)y_+`T0!)16K(M-ucJkJW4`-TO%GkvKgPOEG7uw1^^HV0BT@81ejc&EfVvt0D~xW zS;ABR+GFI^%P=_m1P2o%$tBRo;X@jhkT7mBv&@2nE+wd3@_|ip@7UOT$aDZWr6%9o z*u;e8>RX$M+FyvT+R`$ux;h?o07VTADRXlcM4BKNI0a+CdNjzfFiSK8@>=@YGa9%) zg0?cTo#rNq_^*H~3s9zS5C+fA%2KHOq)i4|E0lj3frA;I^u)BnpY8v4j= zWkJ}ogmUhGzw;gpznoJ=m-k8^$tT@y_Omak3{z#U+vOKp+n{ae&yf~x?YKzUuS;%j zZg=kmmCG&J2T?n$>|bm9YZ1HuRO35XR$aWHojiEyQfNmcYg}vV@LljTQ^N}Z$1>v4 z5v4d)^D#P_9QNiz)ow*Ml3u=45IF;i^l5jB`m=*q>chW$hv>_{`r0r`zLWKE(vEJj z`K7b^_1`4Fp@7}2L^)($c~CkE)<)%Z%fA`hzdPhI26ysb*qxStL1Dphblgoi!As?o z_uEupnnB($U%b<+dK=ety2xZ`gQhdHBvBYSMLvdNy}o*_!jIXO;??UsM)Dv0&By=l zZ=O7(vK}Wi2;6%zN2gz>jUB_m3kH|veGSi4NgH}_w4G>bx+6V!bMK!tO%Ai3G{9Gg z9bYWhgVz$|%b*)c4@1C;_4`l1?rTp_b{DQADipNEJvT8KV4Vh3+*78!n>=K^p%r(+ zxlWqi=Q@%R?BpM{zt6WxWn33QC(Oj~TO{#ouQ5xN%VX0&1(;>Z#^%6F&4I1MAMqaU zi4;}r9_`}FtBluTRvPXO~iEN54&=C z)@{x9Y&|uq?yq5OT9fovtq49Ve#qKOq zA9tVoBjL=bcyb4GLX#^$zktH}!k)=w`X^1?QMXdBn3C(|bde~|y&Vn5pN{UIpP6N4 zZzkD=Yw-6aJmhkC=Da=mm!UZ)sxx;j@s{5&qreh&5KZ4RJqQa>%kfIG{g4uRvqm2y z^ zvt@G)VKPy7iVtKnL&DkUmS%_RGyod>b8Z8Q+&vztOi^R5!|@+#Zf zy9c^$+i&hY%e5srciu=0P2z7E?pE6^ud&^WA|y1IJ$4p5sq zV8Yzo+f#c0t`aY%uY-L$^WRQ75Ysw)-Ks=sEZ)^Fm1#8*vOYw8Se;hu^ZALlWvx*CQ%mq?x-~p6&{<{=AU&vf{+zrdE^(UFRd%da{l@g55IT=}5 zGB*J*O2YOtbgC>kSgwHCM_vSjP;pC+o9t}@aNb8{`3u=t*@q8RBY4Xl^JWP85@}sa zw>6TKj>`q9OJVls^KGiac0=IOQuQf!1JsJ*f#M#mQDmc>-L6D&&*TJgJC(1} zN_JcFKUtPz!{TavgfnV0jPl#lIMc&k+gvE${7w|F=d8w$&A54Zb}3R_K#vqO-Uc|n zggv~vx&{^OEm|y+*CeQS2bV*6r9(-d2t+i@5?%W`@hd}iAj4I}a{3;UVDY9u)_?Gm z9BecT8Vin3Mb_5K>D?bcQdU$h+s(~Eqi$Cx=F#Mpl$JsF;vZL2IFby)-MUp8{TC@?W{6*jQ&_q|@0FsH$GnhJms;LL*lGg^XS5O_2z z>=8#Q+p}tNC{u#+hhISS^W9OsB_fgpo?=jNVb6{rY+<+DI0aN?DrjSRu-m7Mh8{;m zqyhM!;&0Pzi{64HR?HER;Ye{2b)qdSKi+`SOE2-cxy}3mFK>8Z(Xrtx0Z2TQ|Fxkh-L`zW#{H9AGJ9B|JX2Xh5DZi(;lv_&#kS4VW>6O zY#kofd;*LG=X>60*Jmh<6Wn7-cgq;J)+g`9ogvr)LO0#q+$*qT!}702f=_mcuJwU3OA z(|gW-|F0ftG7zp|%mvcqHOStxLC77^^Q*Tz1X)5vD(&m^*3|0~T1hs*41pjr9DooD z{wuiNCgJ~Z+A454!eamun~{KQ10k6TX{?r0f7LNNZ=ODPVvQv;dTyJ%65jlNjE6 zVA}`I5Mo9LMe8f<>)s$V-v2{i^tb;jU$o@^#uwdv1rzu3p^M_Pah0mF=D9!!5vWmB5@kj6Y3NUy4tn^z}xBKtkdbIncM;zQWi8)ur0gJ-PU)8Slg!18=< zKR6>G>56vb3cu#e9E)9E0>!< zP9+pQ!{1swP*&(i!!%};H+dGVf4wB?JjImGOKcjbTvar!7 z#fCV2LyGS4#8s#0rkZgyXMcA!k)07xAhd-#xIH`t}r0#L7^3W`t%)CV}F4qF2t=mpvCp`j13GU@GM7Wn)H2G4ozh?S1~%g0>nk)BCT1wydhExv;Ra z-ZkFW7yhRK7X_=VPn7~y;vz_~UEQ*==Npjk)S6{zwL$yGJc3G{seCO+?CWG`H%Ii;G@`CC5#C->o z^T~cCyQjy-)%9Pzkn}{0<>XXiuf*4Q-G{|hXlv|w;@`QhU>@X3OSgiY1kDNfb)>hV zfc^oV-N1?4w*z3mrmR95lJe&UB+bOwgnt_YCP2CH%el!((d&&5n}4#MU}yKSj@$-^ zF0GSgn^z`WN`D|BWR89%|~NC;z?Cx@!FI zvY2ipwkR)e`@Dqd8pz46Zf>xMY5~jp2@N>v<~tGqL|G;|>-x`XcWE7+0dz}6$HlSg z?z&Ak5kYP1eQ)&rjEsyEh-n&|J<)$49=X|y=}|sfmBLOSro!Q23NSNs^Y9?8pRgMB znAyr!s?B+iL#!#+FOiwBY2$h?T?iCoBS5{Y%J)2}mTeAqrfBZ;g3sUw8 zWjQOOW%Aj?gMED;U;u{pEreXcZ|{r5-Vt)i=G0?2h$F0x4!Cq1Ct^InBcHkH|K<$@ zV*G$&Vu1GET~bAe!C>u7JD}G!-P%{d4O{{r_4#vXvi}#3>Gdlyk0h49s&Er>ce(UQ zc52mrPdCl=_sd5|$TZrJ#m9LDyW(6Q*}U!I?)%H=%nY{a@yf~#YkT1~IxAyJ?8Ff( zFVA|$A2mFu!6hbswfbKPe1-8Q*)!HFX76WZ+KIPSow5V<7}3)9Mx0Z424j4c(}lz0 zv(^2^gWUroegS2zc^UR7p)k!>wH!qj;4=j%3t$iQED&Dq}IvsR$bS?so_9q5ecqqwA# zpL1j32g&}}zl-a4RU(uJN5sk#gutfAEv+4+Zy{;#76b0!=TDo+KHg@q^_(L8y=95J zn;f@WVx+3RS#!WU`jzusS&_Q^J}>n*Di2Al-ch5o{@pQV)1A%rn4@-i&rpX1WacZ> zd)c;S`tR=Q8~^66F6w3um5PQ<43uObZraHkBK&I$3vYiX$uXA}7jHlk$zk%_D`)iw zL6kg!u$U>Sa<~0Qw`G!W0@Ahj{QD_ne1q0%8iK>{u&^vZkp8ZAgNRxWq?aI$4z3Uq z+MmJS5zDu zZ?`19nmE;$ZJYlL6aC=>`3W%Aoj7Esd#pFTDhniT*et22q@A>i7djRJvm#3(9{x|T zcz0n>501xauQpY4o?lwhVu_Sj{TDKPe)AY7B-1EE*f6EM6z1lX<2jjdy6r3v%Ks-X z_FD1ZJBF5t)n3BT%% zdV9=3(xh;&|5Qo6~FDU*TU)>z^5sTbf z&B4O9GZD)70G(G4qS|NyYGwvHMiB-s5Kn1$LJtv0IDevh=c+bHmSh+H-U%KXS31mpyL`8jGT`Iz4!^ z6**Q56mshnJ1dyyU;MPWb)Sx0NKojI`FooQxBxGL)&t@HpjY81pImw(t*<-bJUbU0 zN1e!aeXHprl5D>PW^2Qf5cc>`L#J&_-5P6bRGsi@UaixaaZE*&>=2iOEk3qjwK{#W z_{r!C3oCJtm!)k-%NMqp=lVUlj6`hu1=3i`>U~EQfX<7t+MV+xna&YTQ=3O1Z-Vddg?1R68aJ*nq?cpC&)@K|TaB}hw2wTRQ9U@r|i6C~8blal1YHwzaMSm~W# zv}gf~oB|Um+>()(Wf5ykWii{M&ClO9DmxON}soKoRVS#Ww zB5zT@QrANTmESkDv=Wczn1(#Q0Z;=ZaoD2%t^Gb_`4k1FKAO7)YZFgZ;qTehF8YV} z8o?fIaO9$c9PZHPOrcl20Ywy6`0USJ!5SsNVn^r2EPiQDj0CPMHEsa6?T8zWUpCMHPdtd^D* z6r@ap50Ubh2zN}9V^kv3#As^)Og&+BU{itSR9Jzi?&6YXd(*smVsIxRq?_Hg2-a{| ze0IZf7ecwfUr2_(8xcVYa$4Wj&$LFE@h68OOoue45QfuT%w+gOYXUnN3L$rH6lPfu zQpm{wvO>yDoip7ek=Cf49Y^?$KarQu%*l~neQPa?0329Y0%1bE2_7=QXEkkFgke|z z;#2kaN5U!=!nh9sn#CC-17#G)9JMiVaYZk>KM{wBu67(S+_2?)u{r8?DfCIGnAwiO zl+Tn$&HE05_{C!ty)cvU(Km`dwPy(r9nU!pI~jfcx!^RrP}Uk!`BUIR>9uo5b_4=V z0)!Y(CiX#XDZRwtYE`VpE**i;*^9G1Nv+M^wV8|e$tgD{6hn1#@=V-%7N4mHkG__) z&(4TN%^6+5>t&B#SlDtJdXfJ$PSbNvU2C=`*9#o!?_tCj;g(=M`ASg#XovgGv$2QP zYjP>~PLCj@ZZIapJ?7#C@ySpG%m+_MUdy2yE$}p1_zU9ljo$D1q`QnO6`>9UQvcOg z2>#8KbEj#pitYJ3x^hp9U*1?hF{WO=?04;-$#l$+A|G!N&a8-sqFM zT)GnI`>(%ipQZ2d+Nta?oMz<6)$7##eV7#b=kpH5*z}>}J?^Q6@+X#5SI1aK2TLbI z*{%->9tW>I&(CZQwYxG*!+AlsyJ4ah>E{Nzif`bRa+G1wgtW=e^0%*E1u2lbwCpUJ zl~;AH7amBw9}?4u-U@!e#U(%YD?E#aHTR{*olYOAiI$d29>;I}|5|h`+<}A@FkF>B z$R|s}fWN%4a7R>FROF01J}fzA;KM&OH1r^B6Uu8D`qV;WpT@RoB{T+nxDrop_bFk1 zjJ+bB5?bX_$==poy+TVR-?(`pXuRrE(MHrnoy_nn&L4IL_GL4tK5RcDev?FYp~N+* ztE``qllk)Y2?yx>e9rmpw3nb)WK~Q-%m|n3_H#d+%YCg8trO+A;e=3034?84A~|`% zOK!ZE=UVJC(~Wz$jz9Fy&7y*EJ&T_b*WUer%q?JwLV>Ox~b)Ddsd+tsTiI@(TY z@{w?6Jmx4ZFxkJd$Hy_-Q_Li0X}MYW#i(%p;T^ez)?1SE#2As;F%lAGE^Me&d#jIHVEOD>+^ zh6#scHb9U9-oAa_pQ8%5!uo~=Rc~Yh1Z?*?5|Vk)(Lnpc5%=i)YQ~i_F?kw z9T|}ZUkn&mO<_R;t7TR8;FT2=goZ6+<5+0~Fyw-J_z3MdbvnGfN(6!mZl_OwR;uv# zMTciA%TLd6!@Jqm(+d|^va9gWO)^?6UvyUlKks%aU@pQb>m~#E{wd*`v0QsJ)ojYqik&yBPb{+ zl2JfFf+86SrJ$hXC`eXNl7i$+i3msr0g)^sIU^aQl$>);B{@luT!iADulr88Gkxb@ z_g^!v)vL9|Mzrev&U^OW&(^=^)SwMdZ;+C&0~ZVK4iO`HPiuPP!CxFiFXjhHKB*}e z$N@*fvN#lOFMt~OiCns&`jL$-O?tYA`QUpNO1`;-16L7^1v9|wwO{wZnHc&(m#3zyoTi!O?S1)RfVIW z%h6T(x1JQdgJA(4ZSZ`Tn7UkQ-Ycc&S=D^fx#Fz7&Vm14^>axJ!UsoOjE)Z68vXh| z9pBv6vvdhP+eXcZ$JfuVz8>=hv!fkn>vT&M2WWgfqah8(lE z&5Tde+aI85~Pe`$_UF0{V2=-LH{f3vrZ9Dc$O&GnA(W!%&H`NlpZJvyT=^# zj;`Ft2XQYNzkB}qx^Yr_w_5LgI~9I-t_Ncrm8kU7Gh&)|WoW<9a&HODEY@dk>St6L zN{6jiXa|f1rYmHmuq2=23Z)48M%N|K|Cj^wBbTtOV1g+9Rx(-T{wxWdm(I_!c^kni z6}Ni39i;Saxok(0H$HDAz0{}jCh>N}Ovx{PPz!8HEzS23RB8Vcwo469vSOPZZvH7b zuXi>B92ZPYZ|7n}r0$eQf4}=FI=|D9fIJxAI^s##n3Mgy&ugPn&`3yH#=lR*E%bdb zp=4fnk73us3F_ftH4CRLJDo_G{QiMd<$kG0s)iMXn-7L7#f07rgmqj~9p%KJ&ym*G z|9HbS6vfwMR2uckx>!Xb$~*GiqJQC#5a;_V9S1U2hDn_h;wi4^Yyws$rhZc5NyStJ z6WSaYA;7$~4g>5(8k)>mmarzUHL4yWmu?D9&Cgm_*Vopra&iX1OBgoi!l2**84o<& zKHv*WTS~7IUmD1r12IvGCCoIu3Sx}lsz6FD5uPo)!}4J>Ui@;X8Wx5X=Z-#iF+&}) z?vk;x@wDk%Q&d@Erg?j*)xMCeiKx2z zqG53ED#z|#m4re&NprITBiX`LvISsZwPFFL;IuR50McX)pbinAH)7%CjbK=O=T&F$ zbVAZ>QS02fmy|q4{0j?9jqUgoN-J;JZ{nlO2ezNSTJHGu(-PL<(JhfE!yj+1aNk~l z>Nr7%@$irJyl#6Z{rpH?A375`JHO~1&`&Ddq~wVTFDM9eP&=I@<>sB9PGf4idW^{X zyo*nFw_|$(dTO>ZvG{Se&e8igUADl$XPlg;E?!s@ID3}(qCcrwIR4Y^irYK|Ok#(G zws!LQC8Se(TbBYiZoz~Gkp}R>RAaFTFtYIgbERs3rDFdB9-jQ+;j}4vYo|H6+~BJ? zpQ^w$J6z;O?NySuR{i4#xSR+%IV;_lG?IJ!W-_73xq$%(p;Jn6SD~XF^bz8W$>8a> z7d$%LYcr^QA1v&!!Vgu97kvvuTb@5;_N6WgmM%M5SU%+VF;U|Kbwxi~TLa)W^9=&5 zeb+Y$nwpw2FhJe_xr^z}9jz^Gg+s--eXq|d%wC@WFTytA7e`xmM!!xs4h!^j-4`! z(ayUtO;b@V?u>MNy6uTT*$4Do0s(3Vp8n7#BW4zs;|7+NitwC*vLrzJBRkd846Z*| z6WHc*!SaQa3Iplk7{me~d0K;r|0RmTM~LPQ*9lkXLIYs$ouRD|+t)7z~&xJ2u7y=VW8)HwZ;@s~i6NJVdD~Whh-~MH>VznZI z&jZq=s}7YH7ktycEBR+Bd+V^TcI$q{;zA{Ly>?vtREIjLpEI|~SKEbhIBBo5jcNPA zz@uA#tJBR)#cX%WSHBC+X7*m9Rg8f74!SA-S3Gor}1@Ro( zjam_2l@Ee&7il+irW4{h)4@cv?+!{=PsSCT#)nCO3PokbXzFin2o#(iFcI!);D&mZc9UAMc+ zlHd$qFP@#K8l_7uQ_u`7tgdZ&SN%ysy@F);axEO%4>`V4-od&%c*E072sI_wym5o? zaypGPJ;h<#qnw{k9vgHK!C#1Xr@Yo3juKt9KXnM6!~tPPq_T~x^P=xVoTT&d zV1`E#mpL~}p~98OOM(dv?}Y@!ngRS3=3BQEw?o?iQh>#`8mfl>y8XV2bxcpL*vGWFciz0Z&EUYo1*4h}QQWpLx1 z@6^3%nFvs4H@8fZF)pq{O3~f(+pDBW#R934C(I^je*^uMH~Px5Kn<&Lr@n`4iRr-;5i+!2>jMkdd42_ZL6Pc z%cgeQ_SZ2i8{gneC;+@5lpVGrM6I12W9aR15XDr)9GI)P!x;)A zQf$oB8#7W#IY2Z;fX37-uD4h{}T-$DNC+4^)-Ea*M) zOQ|edBkG5u=hn~K<{p|&;;)9vBqN9nx^gN?N^Z+>Q6wd2Zk|+Dh8W$`&WRF05(GrZ zkdTl!;LL_&zp(ko1|q@3qknw@TI!I0!#^1dYw+j-mdC^aZUfwv$aNM9Y2SgL7oH)2 zpUjMFT$3*M!|?6^@lIhX0pLWyM*Gp!bhb=K7>%)AAcL}Ra#$I2!KzQU8LPV2b5ZxZ zKE#rd90x>ifGrH7m%d5b@i8gG%#gYZ#I^x;Y$(m3oU)t4-oxPx>^qr*tcv?Hu!%=Wzndpm&@x^0DBtLo^E?LC_*?#~17x|xC`njrmlBN}7PW6X@ zR`IKH{Uc8nZ#7&oGch-*vG_s<-W`y%2j%mU_FRjitympwZi3b5>^ONAkJ-2k__((s zmu{ex6!5ILLB#?M8^I0U8utt_;niaO>GZPCe4TNDcl>ubZhQ8hhe0f9o7f|)caUZHP+T_F(l!1c8#Rj%=8!!Jo6RIySFPrh}(x3aH)!lT!6 zxBu&ber|Al{ft8ikWMEmeny60QMs^67FH!17h5E}Te+H~^CqPE5lrvJ`2ujMro6*; zUdLO(+k}SA!70i)h74~mBzSY|S8rJAFjWaX%ON)@x5h~Rpzf%gJWh!19q#O(->ntR z{>JVwl^Tf?(RQ`39v397-&V(#yH#a;*eiaVB3z-=(=8E}6?j9TJvKW_iK>J^;Dw%% z^0VWo8^oKi2g6vKP^^-){j2jRzuf%mB4MWOH_cX#ee7?wwVFZqd>HW(Lx2Bp@if|K zvMT|{xI=VAN#ELV?;3UFy~nT4zp@st$$F<-7Azu}y&v*;?3;Hx-0=MT{NQB;!RLyx z=>vF6fcpB>*th|-rr@ZkClySZ-lPoe(C@-09zElLw7lx-(!uxwLjf|Gfa(gAC`V=r zX$D)oi_0G1ppgbeNGLfh<`${r_zQD#(uY^=?8+LA+Jx=|RlL$Xefl(bWRYzw($g*? zLc_$wG&86X7WnZaNq>Kh8vfNQNfV2YX#v{k+F|E2DyICgg-7OEw!ZQl_NA zxWZxqB`xRQ{X}(3vKgC82ZvN#R4mS;9`!! z-86lPR9{D90P|A-a=D&6ZbpO}%O8lT=&yUjXaMoDT0`HhZ{sS5V?bXGj`~M<=t&HTdEbl*s!k-UdCKEs0 zRD}pUO!-qWmQ`g*xB`?QfaW+h+JP4{uq8h>v+ zJKEbHQ)Qu9nVF#@^9zrKoiW~tb?*=I!H$~-#;XG=>ES?SHH4D$)Ku2Py&Jg#kO2ke zrIJbIaR;yHne^YOJg@_W@gzth=c+hc{6F|cPUU*s=XuLlgdX`x{>U|Rd`9TTJ zd^*S3Bi%e{cjTE2S_vgtmbn)3C&DPV?L{pqQ>Q?8{ggT>kH&LrrsY#`>GX=OBt=G} z#M2UIkN9IhTY8#&?~-Yj*kY1;nNXN~Gc%|jzF&Xo6%kp2Ty5`2c-}6z1ezonZ!Kmj z+cTWp^SbEX^IvBa&op!nDV53Qm3xJ_U4x~VL2WP-x4qzOtL+vOOXc`BBU-!4%s6bA zd2j33yWgbO7;nD~A-q=DU;N>lfV3fQhRd1<+g@uKrtc#X@;h~qJnB-@uQtyvGSiFj zy|uv!2WCuAItl!SVFQe{|3m6*c_9(yGr2>Gx|B^c={c1yufC$t{jJ;Fm`%0u65H@4 z7$i+~+#O^M?=dxXuuQ>mwWeP~g=={T>$VU+jz@hfG{n^EimJZdj(yA)Dpa$s;%oQ5 zvNHbCKL*=CGWe{GHB1b54(G|oPe;z_3{zQEJS(?B-IVvA47-@r+21`03$yL}K_{=~ z<^&C~1{GXqdq=GL0wQcT0vui&b>}DaO>+13J=a4!e9`H}yfzJMIV-YB?%B#EB)D*c zEIwAk{uLK7`3M7{9ZrhtsgL`Yyu|qFrk(}kmv7Zrl4RItU}^hRF|G-Aw14>c4G-Wh z`4mgV6A)Z*(@J+3Ki^Ptnsj1!x9O{V)V+hd9Jyv_kEm~daIa^P z@Y(;wGhB7cU+na{mUTCs7v#~Upt|nL6(2zSz=jCkikYMTQE+H%DjTW-o3H}tMF^WF zIr*fBNQJ#L<*V_^D5PniCQdgl89O}e2OaU?hz6xNEGp`el+-Dp5h2sw7i8KKL1@uz zmx!Rk%n=^(&@GFo#Ox064W7=jKskrkJ!Gwcu$s;zaYJ85TgaZ%@AK!t`_lfv;4Eq8 z&NJ6QeeI@ZQ4oc5)eDe5UR_BJ$C~%EAlbFHvB3mucI1i$@KgA_9x=eQqwU|oUfL@#?zl>jk}ai2?$)cXQq6u7&_#>Rs5Q<00q=YVZi%LSB;44fovXJIY^ z*Fy%yUO9)p#>A@hq21#u-6m|`!7L!9p*iRyoSOvVdV(A>{=*6Jm?e82nZ)6a*-#snr&m_6*b63{t@(gcWdifO-X+MLI;U zjt((L4$`>)M}5g98}(WA9#TQunt;OS?zWHTLg*Gi@_vgqI62jt`o2zFzHNdCwrt?bu>)zdTrP!0F`y^NX#eGU$fo<%x|D_XJ)Le5JH>g1&(@Z(W|FO1K=E zTTYQk+@-S-6AKE6Y1|G`p7*0aOm7Za|M+d;+MsLpU+3%4Cx)+X{%(NErK6DS|UfgBde6{JKRzgwg z#0}fxM1f*hFHFEA&Rg>dR+;wb3U@$4eZZWsgCdAT`Q1I7!H4p4Wqe$I7ihFyIXR#F z77p=OHRfsRDw8}?6yB++=?y>|>HY5Fp&S?pbu+Br_?_;kCoj%nTONF|h>I&g%r%6t z?peD+w0GRkLyV3++%aK4hKzwA4doYC4dH2NX`zS~q9f2{0iXb;MtH{_w}6oANGU6uOf<(BHsF6i z|IKS#S2Bkk<4GUWuMYLAe4T-pF8U08Mj^t*z70EWW_MZl``Ub}wv`KQ4jm->W**$*T(s=`tO# z9tQ9Q)J)cc|rrF=xZJV}2?vZ^62XHP?ZZ>N2D$Fml!Q+>wBE)zWWnfQT$0kqMg z2{`PKb^@_+cm4^5as5^M1BwfdpE}F77!aw*?7u}S`ckH5O2CCu3&OIX_yzwjvXBbZ zjllmbyjVmAeGHb(By33Y)~RC9Lz=g^NC6q{LFA^c4tA7w_ot}OVjxsHWp z9koeZ(@1mQc{1k-N)jvwnJhmV_IA8qoHqM9qY^j`A4j>_eziL?wMS>zb?(+5DnT{$ z{;Tz*?8Ly$%L^G;P_UB^hPCFk<{8aDbZL!bSnbBMV@@YCz;(H>ujm?A7-Pi4zQvWN z-N6NJ&nfRvP96pcrpv0hWF6pRQY$}cYReK6i@8M`z3MG4HlfAGtlgeBH#f}4Y$1*( z6jtJ#J=kjAj;nfD+u(6ca{-8Bw#U!Edc|H+xpyZ=Hd%9NsfW!x>1%qvuA^J1fF&u( zudQjD#!G@Ns|+=s`M0?ZWbIkxd8jN{Qev~il~N|IC+;!65+EU-tmDO~j1IEdmImLz z>T~>PYJ9&{^LDob;-EAJW5VZ*N5`D{vICZCB3s)#N6nHh7t&x z>6Gf~P{3*s&&ocef}N&{%3~Ycy74*6wc{)-hl3S6kS&1AeTpLHu5#D09DT)miX8!a zd*k@-Ho?n?w5d!0$drDJg@+u58)cz~(>N0H`HZfI`i-YSW1a&FUs zoW6bnY@pYGSkB|NXWJgXyh|ts+eKJT9~r!Q)dtDgwA@^cGF`~nK=kV6%a=$X2dHli zi@6oclB$Q!O6y`Pw*45hZj3`;)ha(s0xt*i?(xr!L7^Uibr3Ym-McXl(#=}|?2H$$12dA3tLl;5ipL&{%1BQQ)bu1GyNZ|L% z4SH9LtT6h6Ofp=d94ZdY-lnYPI*7OvpAmgIpO#CD9XFJXuufFZ4W=Job!6%$S9VFH zc#?FkOl>^5;vr^($)}uX6_=4}ebpj7luz*Nz=NFWS4~N_mN&##_id>#cS%2&eh)*m z6#9SEQl?+E)wq?3DA>g1QJZ@CMxOP$y(qEJ``2SjC$)p^MtVlA>mR|qXv`q6gpTzs{+-+O# z`?Au+yeM93VrqU8EcwjN=glORWN-b1-OF5bTv-y?YxoMmfvU>-qFXs)wtLE9I-VQH z=jbh;!Khps{ppvHQ9#hgGiX`!^~J^Vb93;+V6W?{L5C9V-@A|CAjFxrI>w7yKb%?p zA5%*x9kJauR#rdZ$q3t;96fdFXxOQ@BgM<-&YidlD11dlMO!;Nr%7MdLD-aQmsre9 z1*0DOn?sfc*4nSvKJQhRNm%WZtizlFMJ_hr=zyL1F(^Sgb@?)BgF+nEAq8xO-;A2b zz>f(fKhOhU0F($Dn{OfR4znj#nv<+=>6CURR_KgN$jb}t?LDR|*Vfv*?YKts8_NKV zAPPS2V8^KkJ+cwTVR&mG5^MkC=SRP%`Hr!so~^ADPE=})Fw|+gULUycdvIVGDw9c& zmLZc`DeAGleuAC7{kFxZI1w?Xp|dk6C55MG6D}IZPI`McPfaSsY7&@C{8~HVd5Wu{ zwl->aH#I%IssFQ^LQUOsc55f%iHU?vcLr_)Ajq1-Vt@CQRof+5S^WqXH@9oq*@6un z>+APrWT!wyX{h~*T3P8ZG5Td~9SsWh8Q=k@Mb8fozJt@uReHK0M~0xh|4 zpnpzEssaBEz-J5UR|x(Oy(`yPVRzma=b5tMKQe=c7$8!S1Cc>qz_=lH@zav5DKv)!kZ!4pQnZ3@HbQ69#~@5)u}^7RfP}3(T;|)CBj* z5lc*RhKw*e;&0ugByi4t1%_usAY1|Ec?~xwIF|tJ-bV0y=Hp|VOZW%*BqACgv*e$qo zObXqGb)v&16q|!_tiQN)XUCKo&*AbE<&2~BM1{6HsSK+3RJXv%0rYo;1=8eYd$c&`-cx#moZ_UM z)HF-;>q#lmin&JLc#qh-ax9h)IA3oVywu-Hy5&bhgZy;$N=l{F@EyM5bej497Xf0J zMx#SN*QoXY7us6(Hhcm zR<^VbBYEKKf(_uGYT{e4HbhtkkiKco&t;+E;-bsS_DMe&8TRfRfkSN|O|vk#<(}j&B8o)D!mhT4 zMgyeEcA*pv2v=PeVU^cw1895KL9#Y>n@XaxuoiQAPmh{*#b4egQ`8 z);N(!xD#C-OP*tXj(+@WNy$aN7B z?dNN_IDjHBlleB6_R)cj3HGW73ySFKrlv<*gTuq)#R1dS!o$Oh8<}9+vuKm&TR75l z{Zpq9T)dirUSY3%^CRi)KS~O0!A_msU5uM^N{a=H58umO{nK!oCysD|tsF{X^CysPuM2&Pw%FCS?~pJEFP`==#+`XS@bzEcWfPJSt0 zSgOgZZ`%r}t&n9|Tr<)?b1{{P^3T*6FwqI~>bemZ4UFhbTefH{pQ(&EmP>lo2z}?W-v!@y zCP+rCA9BC#Uwo`WkzxY<0L`oS7Eb^8QF4mR`D!?b31{~N-uKfi&!8zclN22pbmDEw zvP1t^Aksk1#7|~XVXw_}UflEF_cM}JX0Ov~Y#77+tP;O+zbp-XEscN?E&hXm*X9F@ zSY4EFkJ4uYse@2^Q{5_M*}=pFwwGzFPV2UNR54d0n$kYV2;Z?F17okX(dd0q`8G=Q zZiv>PgZH_qq%2vT$YK<92mF*25_jh@Ref;*<<7&;#Z?&>on=M!aE3=uR=5Ep74S77 z-xu-W1r)EgJnQhN{1|JC?N(#Ew2Q-~>;29Jz`V{)z|pGA%t1E|;?V{T9r%mjZnD^6 z%mWuF!oMPM(0d>)4dDe4CMBU$5!xvJObL$cfW@e(wbclMsPGIjFgAA34?eTt`n2M* zrKKf2E+NRy%FBxYwLlGhx5;nxPr88FtsRKm147|E6xo3^iNr&x62ot`anXuUJW%SC z`jS)^K=n;qIM>45XduV^VKTp5tRwVy5JmAX5hY2l9uMDnGBZ8>Yi>>&yuZ2Mx2d6E z2tMI)&{M&3wmVCsAu37%V60=x%1_uX@j%ez=toV@h9YE0ntl(JQBrL^?})!gpr)Rj zZyZeOc^dN*M2GtiW(=N9HalR?-(?8{=if!j-D8Sk5puS+w|R|U7(Acp`01%gAVU~g zQ6U1Z;G~QUKiF0yYT?@2?yIN2sI9EpAxgq+SpNXN0J#|ZApD_m=1fPE>G6%(N4;=L zcUTOd-46Mya48H53Sr0>V>1VpbTksrllOK3msi`Hu8QkG<0YZw78al@7Y}>*+@i9g z$XRE}$qHr!4|j8dz87M@L(sfztWG zQ4h`DKzltVr(PexFnzn4Dk>6ym4#J(#-9=n2POKa$+@{5J6Lq^ea3r?p&?;mJxm2# zwFikDJMHU0WqDp7MArhIGhZkq5D|%iQ5Q-;yW#KyW1OtUUf0k;R!Ry&GfG4HT>DD< zH+`6f0hFgU;|K&p2n_;@=1W1r2N+V6;8V-U5CLz^jCgy@8T0$aekn{t7r-0>`>j5V zd3V+7X(p)vK;yoF#{}r?3f2WLmQF(}Jd_a>K#~*Cz>Uq#5-efqZrpuz0R8a(_Kg>Y zs%KUjD!s6mgfFRZs?l&7d?PBx)NhgQI)x)am>T# zd^WaU%J&~M0DBX@rAO!XsJW+muJt}22+ik78i+`OUDG1Z65F8O)%galdCDd3&5guD z9m{KOPIuNH;|%+JI5#QphRdQ^81`}TDN_a}wP@)F*6GF3GmlbTbcHR5>6?DiS_M3Q$q zNj>x)!HXS+JmgXv0^Y(-MUVZvAgzINa%IH#b8@G7yXY?wV7t94 zN#xVgf)yNN#XbQN8k_Tq^8cDUxjbK%KV*5G_k~XFj~`NO0U}0_u^Ank1H2Z9(bqWT zoKE{jMWqhszkmGr*p5#8>CmeGu=qaU7KXFLElpi~Uvmo_tw!&Z23icTiQ0Hv8^D(Wl*Cj_g4iCcnoo$9Z86@Xwn` z!mkC9hgDp^@+Nl2n(ZJHv776x%k|##pdSySl=Jpk48ui zM-&Sh2HQSWayf}REsFE)bvY+R@b013Gu#G-evy0rGZFZPz>nS+I+In`EuA<0T5nxxTxVFg)$`gLbM~^Z?13pn?&k9 zrSeF3^M^m9hb685W(Q&$z#pD{p3(g49Fgkd9t*Mh+rY;=(sDX ziULC~AQOEcyOs|ZYM`x%RDRWbh%t6f4Shb|KP zy*ro&6aq!egF~?WO&z@5U^8mIL%lu24R7Gi+g(3r6mM>xUG>b;+wNSD9;)K;!)w3W z5g8X7uBtlA;E74T3|_O!U&0|>+a$e3MP}Q_pPJQ61RuN4N^ATWrSbCIxhHlLqO0er z#@lm)9@c{FTkv zjkL_nDPPw3&)M1MNl8foT;vFAeu6v#U})|k^z8C)HF19=8vm8z>W7AJR{{kaeF{K(-5jZ zAlfuo@Z3M7${V@CK^Yzt&Vthg0+|^Z8EXF0FnuBwL&ot=-pD-~aNX;uqYYH^ZP~9t z4Y@)=Ar3&9f*QJ;RaLnc=(t`qhVXALi=2{f_#-5O)%mOk?-y_^z+&bifLX!i;CX6n z^bs`$MNMzIv6X%N+tj&@)+p6CkppBA`w*ZwaVn>w{bs;DPrT2($!)?@oX4RL{I9Hs z2jRs_SB~T?EI9Xfd;pMc9dS|iU)6x5tl!L3R1DUK&VKbXf@ZzOFzwQw_OTKY3V6QQ z%L`4VRnZCk{dY1ldDS`~ebzS$qQJt4AerTAvgo1h4Fv^qeg$;vr!l#xbq??|TR|Z0 zyW1e$LWm~kY?z|>)7DS7Gpe3Kh+v275F~;#M8mLjqtw&8^=Gr^d1lkm9fRDSCV0AX3+-4Tp`rJnpb?pjkCIhps1*V1h4f2+W-5AgumM(;Jfo&I+)@A)F9=VQ3#kb# ze0<^13;~fme)}W9Q6Rh>fG8RM9?XY}iZ=|^`dmi<)MbWTQ>31=Z$SYM1obln+=m7S zVX6BD9&0cRgUcAxFl_0tU*bZiclVcjQdN1nol7v6I#=@*UvX#1d<8yKyua+FWfaIW zt>P59!g$yoOuNWlIhPsV?E5+?;A`2V(wo-TEoJH92>s)PO$EM|Gr@zob|>wP*ZM0# zJvyveQd&(5Fzs zFY#OX#E;Ip3r=CN2a4-w@#3GnInJxmW*Yp;@RkNEgXyQfqUPC<{# ze^a1>a`$dGvh#txZUBv-!g8G`(EUJse`cCKYUcu6CU*AW)vu7hp#$&?t*B#OT+Bah zsG}1g*wewOTORI9&iWxTa!0AxCopjRVI>&oARGHujU5bHCm;t40aX(V3ySX5Q!D>7 z+CYSNX-0-EBp=vrDSrMkNjp`)oRqib0`UqHGj>(k-1Kx>B?FT*(VHu^z9-*MkdP>H z!_g)u7wa302J}-7>aC4cl(&D2+Vna_bqz}6P~6~$go^4wcYpud$Yk^l@I|oi?CnWt zX@G+8AMT=U|)KnDIQ!5Absr+4ofvImj_kZJ2E|&BZeW3yt*xw>=8yPF$GWgcd zAc>QGCp*Kh{PC(yj; z$Kc^&KgzYvafv5KAB(le4~`=CI(VEdMQo=E4mu>ORGh6oj7P&EVFdq`Q@<(}c#nwW zQ2Z+9F>YjJBrG--X%_;lYMhql@0GnG!K!()LpTTF>;;GMJM#fGg+s`KAh<)RKyE$|L-~R#C3NGUjQ`sd2}t^MFAdwu6(# zv9rWd9|Cc}z=q4%0&IcpwCNc5@W0P*k?$^VOON21rW(OP@(BXS?d|Psmook=L3j@b zJP~0>khUHG-k}~6n5!`zht)|*Nw9hT0?7_V)OQiQ4m?H?ws@5uKfblKl^KfH0apQE z#}nvol@3a)*8q1Rytv^d1H7*>_apnhDzV?2$B&Bz_Um2CoOnDRv!H69b~fwm&&FjpSEedqVpPho~MV~onw%Jc-?nJ=#`uN`AJVF1>vM{n<&G)V`f zSKO%5c5QOJM%Z-h>=g}tW=lhQNi3D~fY83?g>6Gh{z!8nUYuM-d)&b?&M2pudDq{d zNOePx+Co6dAvvfZ>G|mH+9{GATE1$oLr$yr)>l`Ti=n~zO2)2Lb#QE;u)k5lyK6Dt zzbfi9mhSY=^d-Jj?`HknVd)}ds*EO>6Efa=V4~1_ndc-`3r{C+PfhRGC35Kq&z9Pu z6Jmy90WSkX1k+6KQ=Av?h*}&bDc)r;D=;sYzxCk!R&P=91q-x>o~oD|^{|tO)t|eHjQi&F%T{=W zlhB}XzoJwHY0#*QplZJvo6V*nQbJHXtMlxM3{L|i>xQ@fh%SNPqWMJ)jXx376e6|4 z@C(e|oz@)TZs?0UYLl@H+dfz(l498ROUI&HFzdPNB|i+tNy$vJJx3SWwJsA$Z7;J@ z_NK;O6gX_m`0XSOHd;>vrV8Ft>zF(k1z=T{Us7TW>~pBDG^4$C;sT)UpbKT>08d<0 zRD}3PF-M21NP{@?)OA?RenUV+Ox)Di_@kj=F5k-H82DRNw6&2U40u!{jT-<10E2Ka zqHnA##@(p{068#7b_l163VV;SO9#EcG)v#KtZrJs`@2Kv9+)VIxPe5BG&$>GOx*gN zNd~Xh;J~(rcCI2K7v<$upqqDKaPXWoOXhu+3dyGv@Ph!57Aq7?6+0dj7WS&Dd~jUl zp{lE66?Wo*JTI12RFocBS|M2r{uLj1(!&e6)P|K@X}yNDBQ)e!R@L_MH&u}mwY{#< zpLV(M|A(EVtzFT`J$-dMDmpqhTH1PXZawqN#Ti;Rw;qxWSk^+(uUNr)gv`l1cbwxE zQ@Ob8C@D3~F7J%v|8P%R21ir(eQNFQF3=cC7`MzQ(q7cs z+Dn{E8WYsK`tWGD`RMbXCk|au(hOGc1}Hl(KRnZo)E9v{56X=jpq*krU9*%WJrkP`(Awdy$fI3GkfDB!8HT zcOe0b-qv$un@lq@z6;p>99cv1SHp7{4$M+Hr}-l zuVPF;{zvL|(vb+Yn5vlQ(fp(Ka$2(jv)6u?+m9`urF|iE!RqGQrz1XzNtu$hWoM30 zsY`qCUE&MDzq|uy$l3kd0k^S%KkX~I=4RtnQyKPFlx)x2o!yltxH!l7Ug*ltPvnNf z;i~9ty=4L-S(2LCy8A`4c?DEm7cWkrymN z@YVa+_h%H(O53X4m$f5yqS+|S!F+jguRTV1_tl2^H%}Rw!gNfPm$*W{!e+t{rp>KK zsq=FcwZD3MzSJWo2(Eg&omXzW6irNNeZBedb#DQ0t)1bTg0*~ah;=L+x=^y2Vy%sCC^_4ySv&9 zzE+TrR_G5HVupXI+wfYxNHNbQjFM>2q%u7)+@u_BZ9VC^AI{UBh-&$MYQt==jQ$pM zR1N_hDFe$`6$vl-u&}Ti@EMz0Sk#Y?$M>qq?-u~#bliP^SwN&>b91wI!T)b?(U&h0 zhK%`;Iu8g4*j;bA1-ZnB$tqwMrS}&A&k&@gPovRji_y}13JN4pq6vGVGc)E*kWvj$ zNoR%y6$BB^C#y6O->Uq4yA0*J%MGnOaA}2}j=>6t+~Kpy(R_kopC~1jIv-C?G6ygm zl{Mo_1YZAoL&NjrvTADlv+>H9);O04t0@wKjMXQw=9vH`!_`$3Y0gEIh001=V2~^> z67Bxxbr2Q1!fUB_?Dp+4wg~I0U5Sm8q_=YCKJl{p3=X==$?rpPO$HOm>Sz-z$h-kc zknl-x75amnKE+hn%y(X$wx%jC_YR=my2)#tLViV~Hdc6Ob9t#F-;kUD9ziuVo=7<_ z)boI;4rs$adwMqJygam-nM)0fS%Co}TU+<&eUJiJ!8Fg~V&hWtUMH$e6%=kRTJJ;e z$KdosRyA1!l4QNFr(Z&)c2oN^>uaEB4nTv?5?6BX}Vf7Xb*+X zc-=B&2d{b~l?xto<)35`K8p`3-yAt)5^T%n%&7Bs98vwga; ztxbJk18l+PXoW%`;K$C+UJBi)PzMTEM&S5o=Nn)z7gC|co3_a3hYzG;@Ll9cx&fXA zpjLn_h*?axV#^bLI65}=q|)&=s2PAoV~lL!*ZZ_cRAMoPRJ(^h{91ewUU%R`wr5X3 zgkf;Og#f|L-Cg`N3GICG{uRX80)+gmF~j>J1(su>a8le|06 zaQyp+-Cai>US3$=_$#4~9ZN(LZ4$uS<|U+z;AfyP_~wyH zsC)0^@nGUFmiJ!;%`Oqal=myYK51lzZ^bfT{{8!FmZS?2dmAIOKKs@ViRc3&LRms7 zG=KC{5${Cvwdtt#+DAQ~mipZo2JmyAxcf=y`j@TmM8ig_*7PME)hNgEXwI+OS=6E@ zj>+#)Qd?V3s@eP3Kl?ng@52IW+>09T^Aa`0CPy!~wOf7kj^rEe~y2ens zWJb_=lSD#N9ov{+xY;d4k#C88&o;BAM+_GItQCmdHi4e(uPnIZNAY3CRp;IsT<8g&y|OuI}K zg2l&Jtnopv?f2so5B9B@_pdXbWL0+0&{%1>L-WWQYR@M-qV&|Th~)zb0oJ9%`LEop>FrE8NXt;ZV|!rjX=Fo~<@p4Y}- z)C|n4hR5SItoN_tUik_yUxme0<;~#uEUe`M0~bHnSE*FSLxtu+Rm0l_{EPinTPVqc zhf!p8M$*Rdbn}nwZO?@siX6H9G(PS8LCBOkGRZJ+V4ZN7u$8Nnmd>-WKPd5WKj#j0 zYjT>foQ<`qAYSf@pePq7PP;wXqVH!dUV)Yh(L=IIkn9D_-{UOJd+;(2b~oC6bPEe$7)TMfC!oL+?E;mNpP^W2aSiwZ zFhNPc`5wk1{|uB!gtN#*n!n2sd*`w&w1OHAtTHhjwG^5nB0_*q18;4;9_v+uxH~$` zd#Ep8+^dJ2*hxs9?(X491q7G~`UI=PWv+wcV{sRA@Z#mmEOnfR$13;J>PSuP{ft|( zvK{p4F7j>aFmz6BbbM#*$72Z7LI+ zu+G~o32zv^-XAgcvsW_Rk;zTUl{A;ap9ubP^OI<4Zd^kJX#bfC4- z^@8HKVWlg%1C@;~BBaTa7UPnF=MidaKDcjZlw@gBgbj>x4{MYMlp?QLp1-yq@iJN_ z`c5{`xjI^F0)|JmGI%o59%g)=a3*wq;rwdSP8}Vbf!P>@T~g=t|aK(vzZ~@OZby)8BYjFt*_wum3YPGI^3eFiZ{ANh zg=9!k2NR@?Gu8y)RDzz2mqP_!a2Y{ZV}PrK8>h=9M;DiGW_|1_a#7V^9{9h0{o0@{ znjhAvl5G02$nCGS^=nQJ3A~)(w&1Wbs(gzccFAPGOXKYjtwF9OF!bTZR^3ybUo22R zdp;8w(WjImzn?hv_g4Q3>(kw}^P8(Ede+oOrKO{~pAoeX9`iyXA|M?~faoCUJv#P? zn!4O4E$v%rY3q*`^RpYXmwzpPm!K0je@u}i^{RecJp7uZyR=U1yL+A=kHMCU7{)8B zO%tI953nB+7&-*8?|6(F&5FodIy*mC&a}$p5%Q#VLX?v6^U{`m}~v? zhbbpV+0l`+_UE-iRkw$Xp=&_HKvGLv-%;=XMR2t@$4GVX#UL#+w=jXyky^S%-P z{CVw?NJU7({>N+Zwglpc4Pcys)!`CxxNv^EC@(Jr{(i?DuEaw%F}w1GoS2@q=QM@C|f5f-A9 zp|L2JX@4uP2cVdI(U~!%gAaRLQ=jmM&Ktk)&=#MkUQ}^Qq;~S8^^)s)TAI`42@8L= z?*k0q)rjw%w{CAk`RwyJz++gUX@bnVinp-l;AHu+=Q~DI`NM>gG?Lv3@|HBk^`0Ia zr>*VVpTL1N@-C)4>Y2gwH|`8>m*k1|0+*z>>dXR#^vu7f-O+zgw?Gt#^%v);cF4Tt zsm~zyXX2Sm6`N^E!2VP5L0URDx{Plb=ot6%G|ZAnF7ip{aP6CPJy&cjZ7f#GhL-nS z)DDwU7YFDLli0r#Mb_OMvo{~2OdnUdWoh>8)t<1C^+^wbL++k|22I7_l!PnQt3KKJ zxAg;K8e3IaLj?FI%=+=Q+rg3tnp3>t>8zOKoT6m;T>@U>tn-0+JltO&-86oNX60EO z?SH>fo`5oLX~9($YEJFA39b)OU8>eUkbb>3dtRKQw9A5OrzSP{@?Lql(Lsi&M{2Om zq;(TXQx+T1rOCapif{L`1A{`G9t8Ne9ldgh$#eb?G+ zzGv^V&N_R4Iewb{a1Mqtp8LM8e`XqL_8KrN97WOa5;KHkT(*Qfw&d&^kp%_%P0ig9 zwtR-y0tw;~^7Bifp5f*RKPe+8r_%5d8Iy;jb?g?h#3w;01Kt$9c(Ax&v2@`}9eg53|=HzMh0)21v=gK%% z?Cy%3?O|ke`JLC}r_4tLzNijnvfqY!Td5p$<-)fkB2~Xidf1XZWA+qva4{PG^uox~0LP`W(4y>TA5s4&%kwfDbHhpPs zaq$z(1)%uSEKjv6%LA1@Xlrko^rRCvoF8n&UOBdD-p(c5Im9I-Bm^lg#A-YQM-d3& ztY(@nB1Z!RE5Te-J2*)FCMt@$shQiJeT>sp5klzS0UL*hhldc$KoKKx%cJgC*PI#1 z>^zMWd?H56Hk5P2Nhoyw`DYAWs$x83GDjhm^1V|_xpxej$l(Ti2bwej50fJ&5YJU& z(56USmRpbgn!gBW{hJ^GKO;}gPlSt$OIksJ8QS7u zB7 zbn?QC=;)ByCC5w-6`1en`Dv^vehHs_z;mwJ-Y2hbC$l;0!SBpuMbUV={`h|Z!4KyA z+v%b8Pb`~dZcXJ8R8AE7Tt_M=nw~{YEZ6-0w8_(!^YgTj_i~=&cBEXc?sF^BmuNhq zTg6qsRf^VUOicXqM@O{GpILL{-?$d37Zl=Ho43O8hGwVeV^-F_jFk`Vl9U99kTxVN@{`lr4v13^}%e&$J$9-LGz}JjP)}hjP`V z(mxGT^iNV|4!I59v>P{WAnN;8`J!*I+0%wN+ueqbPr>Vn8oK~u0+{>Zk^~8?i;D{# z%^ZcOi3vNv+pyn1e)?1zXl8IBGqJGjHBJ{7N4PKcut6I_+v-R;J!M`>3h^U;tJRe6 zJoKeM&a$L=@5wLC&r3miHs_D^Q*il!R2ORQRNE`rZwm;Z0hv~5DE>y)*~0{Qs+*r* z>udc28T|r5NJN9*oI!7wE`{!HYl_Rt3PHck9!JPyO_$&hmZVab1J|0`sgta%tS74a z`_-o0AR#sh>?(rxcTQe|p7G0P&$ca^jtL7ES<9x-LrBn`0KESFs}I!rMYDD@GRu7Z z{e_8%_24}hsTOUi>x>oF-n&SE3&qv}*)A!M-}?J&r=g8yWohy7!Zm(Y6uZNqkO*p1 zSOv=EF}vv*N`Ak<{e%n^E4xLEc;~3=AQXZV9xLRZ0#RVW2B3tR7z;uc54eM2g17?( zo8{Kexwv*}j3ocLc-Y4)|0nKX@jvkm->F!HSX~wW9hN}K1!+5i$Pvga^9LZT zNDJH8&H`drplvrH-4YsfU7p{$vfeX*{a=Kz`|In4j9lOe?oY}0=Gk2X2)l=Gu!w6n zORx*PejG*)aWW{5Yiz9EKBgRBR9K}6WG0We=1%tJ>%z|bDEgVKmG~XD5anC+`uvU#cnQOg#^!)%{rpx%)=ArvJ+;hny(f(@`IyG)YdM);P2yReX zI#>3OT_(wWn{df%X)AZnwhKc6B+~r=_trq+C&}{B(woy%@Z>&?`r_D=;PLh(A-=~&NC>81gU6%o@Urww6#a|lu+RNhZA8PbTe?`L)LX5NS@^6d>>VYBa9bom<+%S zjw?m|kJ!VI^c?DtLoob6&WeW|b5eG;FVI?9;Zj}Oyu?4-^6M%O5500X#%04}@nbDqiI6>zNJO*CGTT}% zEhk4eJG**W;O?1HP0!rit4R_S%`K4&bw4lOHdNqoJw#hC58&*$BJrib50zNY5yPmy zzrR1euyC+dQ+1P+RP*aMvfIwP*8m+CmrT2@E1K=;($=P|Uh*i@@~MWCZRzpSqWQ*e zLvubsMZ?wMELcuH#>Rg9M9?}HtvOn^(Cu@<+wKTz!^%>yLW`Sg989jFmK$eZ=xurX zwW{sq`J)>kA@CL#mmW|ANZrrCfD{nvFyOB|If(wFnRyz#4~Rbx%ssmVhut*GrZ64= zzysbs;0hqtGDg5wCTktU?H8!1e!+-n0f7rZQ$rQCI785rO_4=3TbK-l@bqxzLhIk3 z(DH{T|2&o8Bt&8=Vqz};)t7+K4p(;~*5T*a1r^j46+)op)<|<7_)h@z;-(;@j=6cR zh9W@3FQDb@LEh;8<5<(}E(@cxq zpmE_1tV=$ZI8#ASKRm2CZB<%NsH`;sST?vJp=zgx=L*QPAe>)eW~&;X{x$ToLi94Y zH|DC|p4Ld|?yCfWmX|?uc#*U3^7~~q`xKzxVmc<~wmAL{b6yd6SVjh3JR+*ct|SkACNm1I$_n9=ObNVM@E#NH zvAQfTchEj?hC@xQ06nZ&p%761uHkiQW-zw7nS@SmPdC@13e#aZ9L}A8Z%0ytyy732 z^J3pamXmMeUceH}p*48l4WkWk@_p=u`LMc%1_-PA1>rg$&>;FfR!+bN55bGA*;e=p zVH2o-=OJWZiEYC8^=tV0-~GBrJ5Rt4%;N=D#n}H*Qwk^?$fL#5@jK2rvZ4=>21yvM{iz+67=p7g@Fy0bB>L6HXx9 zhiA@gpPiFV+1%^Qys^mxSAv_Zr+=ePVs;4$sEXKdw(?Obp0>H|4FY zy^G(17iH<%uw&l)cE#bD1TE!HL@mEnZz1@i#qli{+)4;~-Pk6zWJ9}P-}39W@1O|5 zBigh}=m)GA!6Ww&jDU}SE^!j61QO=Eq-%?YYG&ps zCAL?*?CG!nI8;ZVu>}_dzn)As*7WzU)^6VZ??bHVZjN;&C>MlGyeieLtX zJ};!J8d!&hMaIeijY^gZ7Z98O6_*1H>s4r70Ny{d7EV-2Sy@8JI*^OH>kX9!`LHJW z-K(2)f4!)31Gc_^kz*RF1087H@q=oed-vk5^6UznJh{{}!O)2%xwu0HC%onBGqwGD z?qBn%PNr9qq^huY!tmLEo#6$>E~g3N3BAseRn`p-s=2~7uq!oUYLZ)E5lJOVR&V0N?YXL!`D$`<9)LS=!EUT4H&hI(**etx8{ zL1PRRKnhVAJ2A_3-ej-FDz%PLK|!5l-FW}Ru>EHPv?9lL#hu_hlc=pW7}lAoT0e}| ziT=2AutSbX3nYp6Pndpvwt#?Y)WV4og_&b%4(FbszD24`5@k=ps>b4JCg@mKeV?P)>iI?5~QH_ z5~Dru93@R4$=Fuu*FY(CtDqkudvBqO$UY zcu(+7XcVE~jl%@1VxXcFq#{g1-*_IxjV)HP7m z8)Z`03x(f2Cb6Ucy^y$VlbB(0RzqKqSoS?<&o7>M$gt?*nDs_6PwV+sV*=gHeh3i+ zFE)_tz$SyKH~PyLhQG(cV^2_1QRM?Lj#>8-xf0Rbi*n!Ylym_{9|(%^Ae;|2N60Ha z@vrdmseGdl$(uSBtSHz%TUz9xs2au?5J*@7xxX{14Rs{ok-4|k!sP-o9i;ec?0D5a z(=a^u>XRp<>QB!Z3>MN4j!aB09THetl6syW6UAbtfRXy((>#LP2B0sjuik%}?qDDR zxO*fv87w=K5N81riLX{OGVm?;-LY%fj9SCirWZZwtph_|@aNV(`h);ve&+< z*1Z07=hD*WpKWeUK~B^oPok$eb}y)?H6E+k`veC=M1=bl!9yYQv9=BkcfAI;8klNd z+r)fK(qmFm%D};^Od^5mR=V*~6=Bdxe# zvwmQJLP%&T{_qPg1;y#Ri^Zn>9Q6Tz4AEc%2r0v!g3O{|nC)iUVw<5EBJ$Sr`)Skm z*3qp;2OdP1OHK%uU$}hve*QPYPBg|Lctz3TYaZ;dkl|l2qD41&PlrCVGHKmQ3zge6 z7UZIJ&V677ADO`bX3sAy)Q2t{xyU;nko{=u;N(<>c0wE$P+keTcyKA%eMbFDq&#bA z(NJGs91sUchC~$qW;OIo?uOG`w{BelmKh>I^sa{;Yh+=FRJhkMVrmT2EV?UgRnH4yJWl{KJuzuMXms2$>X zcCdFMP5h8Bs2CyVa3Bs5p)9B%)bn3>2%rF7S5{YVe1}VT=Iq(4Ae{#^J*QUWV46$; zM0;=?di(k^L3BHmen>vRK-N5xS0>9@A#QBU1`>X1ba7G9&w&9-n0rA)NOeo}kj$3u zF4PMieP@BK`VSmkU@u?*!O^8x{%1J)`9VDXx!L$y*GR#Bd(AXBPeBbj_#AU;V>6Pj^Bn_rHmsIDn()qL!H=H{SAMTs)7_2Y7XA{{AyY|~T?2igM@PKzb~Ou2eJ;`!v}kUdLbnhu#78Mz;Uxr@X5 zanAP{C=Er&7yR%0&q<7FqLk4~?78h_lbgOxk-h2z>9&VDV~(@|y>t+Yk-GM|^0RZa zwRONOace`0yGn}MM8QDHvz>6e@|ezs%KJKM5&3q+Ke!kI6?3&Frm`GqQ;WEHX0t5G zA{`fTx>A`nTU&WcnNs5<)j=_2T{plTgb{3&T@x_f%IrG}=In)`>E_tqcL zhB;5vc-Ylm{PZ$2vyQU6SUv3Dv;b5)X1v3Lg8|QLF?B%cJ=Pk*Y`uVTWS5;@HKf}e zM9N6ay|L2oIRJ8Bw#9hEo(~-gLeH^Khw{7Jp7SDL`ZKYDm)Ne$rUH<^xp@~NZEISi zb)XDU5z0*@9R2(Z-hda+`Gi8eHqrd}Jm1@^51#0muik&$bt*Gc!0l{XFE_OsNJKxq ztf>)J#R?mBJif!l+cl_x-I|QT?dtY<&!91Rt7^{yD2 zuQmbsS7N*L@riL(X5C%xFvbYpk>Xac+4StlN=sh_$u?}p;0Kp=aNvX9Z&r6VHfESL zTTdBDf|wOixGx+|Nf-YKEP{7TUMvSb0zBF>P%X8pz3?uH&4A_N9o21LY|A#7h{{~Rm!;_j{1zi1@Vc| z`=^A@RJ};Y4aV{A2o5;=@^s7Vr_=XWqf4VgrU+FLt++^UT=*gd$PplCTiLPl@yhV2 z#<4iX*-W{2s54Ilxz|!AUY@p9TVxf~sP{W>F@CF6jB&WDoz#mJe{DGQ9xfCjcG_nDS9Eoe+#PoZrzI0+)(`5I8oM=m^HH2>tC)KqOS`RZL< z{wxR6AG-JLrBFA&KLqY}gfX$M;mc~@f_O7Ayvv5^MVPX89s#(|MLdEcYwxwozaH=U=`|hmn=H>85sD}r){&z2=p3jWJk!0khVNaNYj19Q5xzI$;Vv1`f^yrLXg7UTVQNW3*p0?q_h=y~rkigP z=@kb486F>yR2uh}Ui?ewe))Lg@09K*{$AIaS$qW&sqr5qgatkX80vbZl*cA$&+uYo$oOts~OI%LdMz%*f6o+n>=Eu}D z(G!jOVU7CstT&F}HzeB&*8OZN3#A{!!<}SgNY}LLlS}rQSQI57#_;fiSHo$*kxsnH z%gd`jhPpi@|qh4WE)d*ND z8>*i#zmFfu$&3ZxXDV&L*^2}Kt500YFNB=Lf z1@=0$;OfcB%Bt<^+T?AvL>)|3`~a}^{5|R&x2>U78O|%~B?%Lb?8jxH$q7sxxuqW; zCo%zffYbu0KrMg^&3!ZTo4-hYCCe#Py9DxM%8!Z&)D)2(Y5{feU+&!P>Z`1-4OY_U5e}zKo>^lp_lMu! z#qe4Kh}OSAs}52uP~4~I)xQYaN>l1ex05^h@9B#N6Y8d z{wWrvk>Qq_X=Q558$?9vd{yr$)mnLpOf{lPmDI8-na*X3_2+mBM6L2OpBtBv=T2-u z&AxP{UFbHPc=^&_j$_VWu6~a!&GNPhF$;^r&*qw7X%>IeWLMLDDNLMm8%f8|&(N7Z7kB?mg%XdJl^X4D+BfaJ(@%4Pz2b6Lxc>0tIP9s&pM3s=)m!xERut>>(JPI~1H9t-b8ot+6 z=OVK|LB-~6i&6R6wu+__OZ*ePd)c6~NAmn(83AoX7?Q8At?ds7805f$dxf`x(YqtZZK%Ad{0G8xAA5Im?%cG?sK&cTK415(qUirm~&0D zIY-64sP(c3-rB28R2L0kCrc_?ZC$@%#~u-kEHmWYBn6zaqQdoaB*Aku=#fP;_= z;~Kg{Rw4qM=2bA+yJHW>pluPt>S1I1DE>uZ1dhS~5;tL82Q8xz6D%0^M`<1?J{uuc zN<<_c-jY#RZqmUN=Reqq7E?($CZ*t*P_d;Q(53s6>JGM4Ym+vE#r|l;j z7HD0f!gRhi`B?-7DXX?l+e5RGW(qfP%LzYVEYG7~qJ8&ma9WF_OH0rUpD2DR8$5so zg`wW|L^m1AHGPH;bmH;(B$~Oo`a7lZPO)_MxB(MZs@Jd)5sddfuU2c0y*W;tD!FO; z!f`R6kTov*&=mS_A+K9X$e8@Xy!?Mq#3!5sFaqY?byymJhH!WRpgaWxz7l_b zS=D?RsB|%Utni?G_K3VU-=(*s^3%@ZiMsi53X3Z;797|=T%_Jf-AqF4|9mLc|Rw-7c03p z^$uPXuMbZnZD+p%Ur)im+V0&=C_wk-qj|f+H`I{oUf5F@NFDoWTaZmp(eGztU-~oK zy}wh<@3~fEIdz$I0^;ko=(In+TA4RZkM;ep@euicr<~6v{vQS4^5q-+%Q4}_1l`>`$L1cNqhgr{=&??n4udOOa83-g$TMmKwX z5xO3CI_|!{zD4K7|Hwah{*!;m{jR9CtZbi*sLQt3jvGo)w}ss|sv+Sq0+Ues`f0q5 z;)FW^0^(;y=e^)*B!bb71pCkzKBvUBf?txhfjB7aE9}x3`UET+xSe0S_Jz4k9DjJS zd=-3>R7LLOYQ>t@wo>HYiL4D16H33sIYBE#3%FSzZjDqBrwiQWu9g8(Mwb4 znCo3@$tpVOee5}Z@*;d7Xvi0i>P%9f1ZS*ObQwGl|Q!)qtEKf(E$1#Q4${z$}0s-{Gdvxzf#C3~1AQK{yM> z?7K}fM0bnFUwMRfISY3kUGcTq84$Sh@shH8a(bc|GBoOpvRD1?32kM3@37jkz#_2< zf^6MauYCU$WY79=RvPUwPC>XU;=h7~KdMqqgYrKW`ip!>IU-00R{L->bHU}-nyH#~ zhM0I3>YHbvEeK-RJ&_i)R^BmiMT$Lteid2AdV9gH2U7hW^&gOxp#NtIip25ZXhr6l z*1{t73uFEZr{4=hunp8uu~^Re&wgv@-@EAIs&kcdO%EV{Q8hJsEv?f8gsPi4&~yuJ zZ8RbxB9IYqfH_>F;=3F z@aR<6*T(~TCoWO%l&g6FYJKgh;bFg8ueH@5Y;4k?5g;U|pqtrUAtE|=Gb5|~p|muE zRVjc;^a8zjKsLPd_$gXYfFWR|;@`X}BNGgzA&QHC%JfIE<>|Y-0A5RdEug+HS{2R2 z!ugPaKUMW$s=?hqBSYvCXC+hw-~!t)Tp62qysySE0UdG^AgBP$>znP;6QpD$@qw39 zGD1ACatK$qMq`n%uv#yR@s`M$;WAtCtZm?8;P3V&&CoY2Y_Hu;R>Ew>kN8m1zHx1q z2ga!}mYXZ8i;L#a%jtFhswBI7bw@{_*n7O^1_lp7;hZGlHwn>Z74?{HyTi=ZElwdJ ztz&zz9w7q6pFfRhF<;~VVj_Z}L8a)x-}g^00oqrrA)y3zdweh#Syb#$NCwcY+^XQ& z>9#xCDCt`h+cXWq)`~}Oj*N^TMaWR<4P{R|1FQX%l$7gW!6Boep#eHXR!b{ZU>Wc` zNchz=HI;=#CisA8H1Bsx(r3)tqtOFL*yRdG^)Lg>KK8|!{ZoB1Tyh*7-t~kV9G{)p zzZhv`M25IZpt=pSQ&hN5j^7tY1^+Y0sL&W)}ex}w%*j)(X33?+dz1KWTW4{Cd3V*INu?Hk~Ny)pI>wf#9VG9M!6AWJGeGxPE4#r}R} zB_%NlPxL(txClX5l@I2y<&~AE6;9mX+Xc5(*j7JqAL|ehv(`w~U#J$$ z8Acs#%7VbW0fDy{58?7adQzY@0Lo&KGHCd`17TAOt-5^R70&d77rxEIQ{N!@XKTRB zjBS~_>HF{K1tkwSpUt;>?fvPO^D_$0o~YIK+^<~n$xY{&r{c9d35<`qGg6&thp`8~S| zaECIx%H6W$5reb1`GhR>=9<|{=iK$=u>!Z4Xh;8Wboi4A`NPjIbj{vWh6!7F$7m-x zD_KjAe4MQ4ieB96Rf%~#21NniDY54;L=L7Wt{p-dv)bTG>hm=&Z}{Pg=t=Isr*e>~ zoCTS=ob&Tbx-=#}YdWr_2d(nvZm!B%+eK%`F7E8zq7^IUB;~`Q1}S=uxM5U*?Tzd8=%^XNiWHUb#UWRvAa$!2yeey(KqaFBM=643k}C zD>NCN&v@u3-{G%ac6y$g7my!EM|v*gteP^{5om!)v0RR}MJIkt3%z*{^+hckN$l?K zbd*&DIwH(;Snsm%zWSNUmlOk()X&XyKZ-LmW)>3~hZXl8{kV9cv2@c)VVGU%(A7|z zBl`V=1Jm&fyX4awW^UTi#`n(XTsPgewUs{3+s26#4!+*>beenIK12r+;(_Tqd+r?K zH#mVLzaUa}m;Kk=DEJsLLedPQK4p~=u9&qX=|#DFQX z5wPl$c#Ff%tOo}>a2;K1IB{`JSyr6dKL%2vB0${M7UH|Vo0Sa>-~js?eOZ89LS9}W z+U@(R-!(`@09Z~DH6ZvBW?!bb+1bXv$y$QSWlbg4P(3r-or_<-5C=yT<7X6-zG-Tb z4Gp{Y_(`~wqa%Tm5=%1ZiQ*g1%Gk#d_=JkEhGKu6n1V`i+_c+HnhD%@U~ zfXCoH7{hm@j=Q^eoF(f~U|Sm>9~Xrx>gWmXQjyFJ)kKGNLLX4eut3KcqolvCp5Eyy zFbTlFTtEAd)J{q19xW{`4NEfA<5U4n_4|h-7dX;E>Mx>dS`K$w zG|YG=rlvzN_z>R>8}0*epMe6A`P#LIOD-Df>fp?7WZiK;Huv z5~LOabfc0`!~*8jSJ$Eg3S1PqR(9h3=ht?(lXy0_nC?8U!C{vB{08#JtxByhPT+e0 z141JJMCsWoZ8|BB*~;x!oOJJ{L6mzTZR4Gjk+X^yv9YYETT@M=8s$i{z`WRa@)JLnEV zfFj(55fxAEs}An2-g!5Xhn5djm{e?7-5$lYNqNg(g``A}A;Poh8)pbb2z!4~y_NLR zWM{FpWCxq$XQ$6ECbRFpjx32vFw9P?qztf4QPUqHYz;?Km~ff0?PtE39Qj!yo<(oj zddw72sMdYv_oXjTkgWJX42X;+RzXdP(K$yxd8^0P#YA80j5Epj*NR4`Gb+>XUWkPb zgU&Vvyj+@(SSRrsLpm|m5b6BK>v)^Tx zgjl9Eqx%n<2o^P>B4=k6Y?aCU3Mf)b$}Drg{0Or&6%*mC-?4jgm{XD%R~G(~;qqEo zzlvhKCxz|l5zm(98wxVhjU|Q-f(bkJ9Gi=#zj8BX8@@!pYHHJuiY||G4)mkt8WGw@ zZ4>4+zb=1ABI%|-=^ei>ut)7qPf?Rr&5*NnB`YQ zW1yzV;Anp%a}2VWR0!kCiD-Cy9k*tS9E)ThJ-PswHLwwrOG}4!l*K37;)G<)%x;2X zK}A&+3DmQ(xeGYqNWsDH?rsstcZ73E7tX8|w5j=%%5!GAx{15aW@a3BB5^K7q~m+q z+SxHft0R(tS5m?UAR|Ie_`CN0hMqJ%Ltd4cv1&0uGoL`A0F7XIwN#3#R7x03XmxGo zXP!LS^X<~s-ZD1Nh1zut<~QNBYYqHvF4StdE;3MXEI)m8+1&E>-+_HWK_O{yr@!lq zOR1#Jn95d+28X1g;#z)6-lIlfFO~vhczFAt>*{8NjUCQRDNUnpg88)V57R;@W?=!X zI<#Q{{Yc0*rl#$f(#khz?LItZ3bU55urQy10N6q916f>DIVrDvP#EieFJq>g8LOq_ zKB81PJNwcNb6^Nu_wU6|?QrPavYwR#LmIfuwX(jJmX<=m#8*ftL2@x51h+f!9xQHF z8q}S>zmhXrStdZ_;(i6b z8R>>84zv=8S*vno_-N=TJ$A2%<7$sd%>Np+UiVsVSS~aTCoAd#?FF`4EZ-?EMXojl zHPpN6Ia>P7@6#CyIGOTYqw@jtfge96pAq+Zc(?!Vb%H{Yf71d$V~OOFKp^k0UoI>m zZ~RW?D10?mt~_aR?#lLS)(O-HvL7s;X9aVbu?gsI9v8!-sb;-EL(!d6%5^ zbI|J_sv2r*Js0e@ui)<3YJR>KEBta+%HR%r%3}p_`N^)H$jbpyAB#P%O$E@Z5+1IO zC%VMgdJJ{-sfko_8Qo%OP-J#(A|kCf5iZyg&CbX!|zTrO;L7 zCZn{`8yj?0OP7M(c^#jy!o-e~&O?mEu>V;m&^m_rd7%%he>S=SK6$l`jUU%gSO{|V zh4gq9mN{=$9Z0nY`6><`9)uc7f}0xvW#~a7TrmB0Nyp{wAGq~9;PFpG7aVARlE0UX zq1;6l78XEziDc`5>%ZwtisoHjurtWNUdMZ(=R?W$GSBMB$XIu8Rah#e7uL(*-`@y7 zb1zXtO8{Qz*te!%zYqm0varHDj`++W3EI-mE;W}U-U77)R_~M0xl2YH>v8|8-k9*t zrF%arbaZE1D7tkn;k~vzC@fs)0c~tLa8{QRDH=?QQ*$GFz}8Jl51iLJlC?0A)rr%*4@l4j-Qc z!q1s`zJE0CempYH=Q{x8{%T4hKY`fqU1Hdj0Et) z#SO}@y)(#^ll6US3}Q^BWM!3OHxDe}(CV2og+Mj%HoyrD1T7e{LEQFurV=(*B(Vj2 zE%Ng6aG+&sT}@|mah~b6!)zpnD@Grge!?mxu^pq1S1dFuT_Qo*7#ti7o05rj65sXf zvZ`$``*h{$h#=)~>FMK;78@EG8W~ zpY{bqhz&OBXaA0{w;%*TjN5QYfV?^Im%a(7oUJFkrODY@PbsFyI~$>)VNW&adwo`;-z0yL1dBYl6axI&2`gwbPgz8a3c?fpAtH(w|1eXEd; z+cDym@N=f&+ShrX(VBZr&p;v|s{u}no|k>WC~lp=fJ2aV7&fXsFe)3VOPPrXRl?TqWBOYE{9Go~CXbPCyoY|S4HNV9EKs50DTe@FhcZZ*_ z9^^{iM>>KJ$mj8(R`~#WZw8Nq0B zbhfZ-`Hf2V03ySbu>_9xv~S*QEMJ)OpBqU9^jJT;FR{7!=IxnuJp-f0jdbr<7yW8P z_x41%f5USR_CFHpCkf4G&Zz(LrHX(cgG=m5@1YslKW*-a!^fiTqx=UCHk8;4*`vgY-B2i6rIi)27-kfMP_p>u( z)5_xuRgl^OovMipI5=e|i?o=ey+364K8+!bo$UFjoC8GxO*1VeT%qx-X+~-!XRn?O zjla=9s71EofkIcSn$uQ3RMI3VjkY@3=qTd#j*}3eP6pr0z8~fM>)0C!wlK4w_jnr` zJEdk;4O=;*zV)txox%19kKNDn)AwJ6-n2~E!{!-p&X?Dy)LhEsc&MiAt`mW$X7 zZ67uL;i(9h`K!gj^Jj+w@(uS_Cp29S7$Pl1hA-;nT5FNel9EJT_71A8*rXApnL1R{ zVQ}-iGWlvrtb@DnV#IHbfe!;6dtu=GA}VxZf{prM@31Q1>X4_dP#yK*?cEO;a+&yt zt^(wHZ&Xj8871=_vNj*U8Pe6&Td!-B1k|-nLC|O4@OF}de@)|Pdh@i5qNEqSgyaVN z*ggyjuCwU4=gRp==;yf}9<5cK2xgX>p)yp9Tbp}WUuaan)3mj zEX}*_UiOnOyJ;pQB!ooRgMk09sDF?|J2;dywL|^~ztc7=1l!+$hDUH$xKU_XfYix6&aB6O)@nadGV=WY!=* zQ)Ragnx~hGMPG;0+|r7mVGQe@|BdDpD~crb5o-r{9S%^3(NJ0OWZ^u`?n%HK1N{6@ zix3#K7+x?|YV8**NIOv8KbPO*Y7a%c`~g)}sC)Sb*r72@Js4-9ac;>HotC!L50Gsn zbkvbS3d%E!3J)R!TD!)g#n=RSHu?avIQlbjJkNj`nluJSE^39-`LPkc0 z_2x}8uJbD%Yd500#=e`;Ncvw!B)M$LsexdYGR>>#YZ?Phv$Ue3hQ!2OzD1 z*9GrAAemEZYXLs8{qF9^Vh1z=HtB<8mo7B`N2{V2&a<+01@yV9o0mcF1ljB$D5L;n zWBL20ag)2@_5=HH#e}JqJU=(r7{q~rK7vDce>y}_1q;bB$L5P}&~66V@aK8myY5yR zeffs|R=%D0MpP})9V=2_5k|A@Pjp6sRz3{ z-}33r@;bH1qH31rmpZJ{Ji-SlY;0_$v|>ITZmn*An!i@~;?na;ll!t}gk8@bC%<4L zD7`=Pa@N>$=oD|;!}!`mTZ_fKzK{wYn)qH}4Vv3SK3F%KZQFI7wSc!pCl~Tk8BGH- zhN(Acj51%4d zbja0`v*{6jL%KG;3(QLN-0e9p{)tGK$$>ET}gsrYMSlQ0~FOKctP|m zx&$@u4-YM1fpL5XCAQ-f%x%no56TX96>=;%-|={7s7R-L><$7qFtfkVOCOnT9jXa_ zsC^viFv&7XMJ{e9e=dt7Ztl3cmQPzFI~O3V1DldF&mKIWz(z^A%K9{|NNC+GPIuVHP1Qv@9_M41aLh!%V(Oh~Q$hO)$HshPEa+D_#dW?+1=y^tM$C-=JI~>t~p^!on8y0#I1f{X>+5Lfa*P>#4Q0T;Y$g-cXsffMh$w$O)J2`08pKJP(ut zHms46O_ywIYQ8@=TanZl)bRCNp}Ob59l?*o6-%7ru?0ZEA0j7~7_Uj7nkX_(7t9w6sR9 zO&Qwz=B>==M5Bf5mMNjk7ZQXEKq~}wWDOv6>8%d{uj&R+R7D4|)bbO8KB?QTE-rko z=(_;O%`Yw@%`q*lt-Z4kp&@#b)BS)DQmE#@1wu=(HZdV4FMkeF9U&VgbM}drN9vJQ z$!oElJs6_Eb#aY{=gAVf6sVn(Sj-*@4pTjV>4EXe7>3{m1;E92cXxY$8c#|hBm|g$z;kdCMiO&3>yT)jvAT}F zN52&=%IoJe-5+IYF!_2Bw1}W@W{7AKyolAhr{>UQ@l>R(^`4}xt85mZ+}{ukXZ}Gj zWIj5q*dKav#JkTmo^vvEnKRKw#~4A}w@%Iiq3|d~yL+OHd)Yv0eO34I3+2FY+1r+; zQ#SAJ`w*L^21wJB$;HTbboN)@sS5Ty^5lT5?N+Q`EAP7Is`t`mwO5jZFIM#ws(Drv z)T#di2LVUxpBzMeDT{(|Ca22O_o|hk?b@H(ryV+*qf5P6*wXX5*?nV!HgE1(d4CAI z6dcqvP4sQaBeGJs-pNVIE!y|UQTN1km*dY4M=XmP8Z?rJQBQUCj6M{-y-FJCn?A~s ztUCH+YwL;VmAR&7e#LPPM zOXac}2KB#b2;QZ~?n{EKRn z8YD@IovIquJ_rVP7ZnUg4?#po>J8jj1Nd#c#Tk;v&fXIWQ=8HL;gBzH55caBi?4ir zUk%w5=bzQ>NZ{qSVfBrQis3m%qTT4-kAeV4MkIYeHMh3P94{{loK)+F=Hb#Wk67od zdVAGAeiD$<9;Hj}k~zVCol^-og{My$W@gyLAJQfyj62K8T{eF8BPCp0Pme00s!rTd zYdAxh@C0p4Yy;#BS+%r|NJ)K`wYB|TkA|L;{}y85OH}&rAs=YyUV0>$mYjTno?a19 zJjnVn0H25kRY@89@lbKM3SyH`K)G2L@w`@5Gh_@3Weky~8+ds>_`LIBAjdD1CdW*@ zpH@zPfV@1wcO8J<(`C3jmARpOJUnn=`G?f)BuGGk)3p@F(X@v4Rci2oxy$b?0pkF$ zK17FUqTFcTK&(EJ=ns7;xtetBAKi&JWIi zNR+4pn$`}6-;vtXZ=-T^Gpnet^5$`A&>Z{d&ID!m9#UrU!dz% z_N3Wk&?Y~!FY+&_HJEmd^Xg_|;kE~oG$vIbXiIxZK5o%R=JZhMyfyjCjThr(Z};=| z`pdKztri~@vVJ8fSbQDZlw$SxQrZ{8RIdy#Hs&^&(?Q-@XPxQ?0GEE0LXg}=)q zU>()+l`WKw=o_n@9qXwj$L?UDT1uvKm4V|XO1y3G@{#bmX~olSPZuefYlS_9otnFyaVDP zwX{@uq(?j{+k-9FJSK}(jjH$rkG}Rx%f}3o^+L5nCv#BNL25d(cJkaJ6eIe|cFfS) z+dKUJ)$-?s)oN^tj?u+GS^+K}1bMT#O)d?a9=WQYiO2IP=H}Y?mHP}NS-#J( zez7CE&}a;269M7*7lynz*Rz}@j5+fT?(+64Yf!smmji->r=Swf@4A}sHHP~KsSW@% zod6lN$uVq|MBd_?L&n^=X^JRO&6upk-=*DR2E&j00yc5_N9Nq7o@DX+X@1JhwNtyI zrlun%DM`1bQw<%N%QH`Qh>CH#Z4FiKrKZ97Ly$%pyNLL3UGi%ZE^=pR&e=}1zBvL7(RN8rJN=w`AV8$+Q zv^QBq@DF@Wtaa07Y5XSXJF=3(Nm7E+cOS-a+CX zGP-fqLN;zuJYkUO$Pe?8*!!c@Cs7-9acg-lepBnhTzY6xYO%j_c7U>D_iXkq#cI*b zyVJbsarLA#MP_~v{Ti8MPJieoX8}BkYWvp}*nI(r(jV}D-h@fas;%0?Cq*l#7aWQB zCX{9_K|#RQSm0Qd#~kN4YBYXbr-tfaOKQ1cAmrZlr-BEM{)WytB4eSisBxaad=Y%Q zrf+MEJ=EVn1NyUldm}!z)Q8pv8g~tutJNJW_>6-A@qiryf*or*JM()lipINa zo?WZh(~h?Aw10RM-~&vZi>vEd$a06kXK>ZSNBX0`KSfu6Fg$Ok{s@%EK_UVPJl+Ax zDQ#wna>u#>)CxHpkg%K&t7I67OsSDMWPC@BkDr6`%-b(pv{Nt^|37E(|A4cIi;FWk zy3WQ1^B5s$Gt$ipA#1}WbSf+L$TU+2ehQ+ZZ%p(KV0nV5VGwnwB5C^IT=$0ZYA6uM zS8su9qKJ6x>7XnY;tHUh%ip5%z!Mk?prj)OY}pxtHgSs~rvV79R41^lt2m+Z^Of`* z9=YkPf#?xQoed2YhxmmE*9Y>-nAJOa^+e!6RY{vJ%!xnz)ztL3s2e6MWEq20Y

    W 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 literal 0 HcmV?d00001 diff --git a/docs/img/storage.diagram b/docs/img/storage.diagram new file mode 100644 index 0000000..086fa84 --- /dev/null +++ b/docs/img/storage.diagram @@ -0,0 +1,35 @@ ++------------------------+ +| "disk_interface" | +| | +-----------------+ +| "customization point" |<--->| "counters" | +| "for disk I/O for" | | "to keep stats" | +| "whole session" | +-----------------+ +| |<-- "async_read()" +| |<-- "async_write()" +| |<-- "async_hash()" +| |<-- "async_hash2()" +| |<-- "async_move_storage()" +| |<-- "async_check_files()" +| |<-- "async_stop_torrent()" +| |<-- "remove_torrent()" +| |<-- "async_rename_file()" +| |<-- "async_set_file_priority()" +| |<-- "async_delete_files()" +| |<-- "async_clear_piece()" ++------------------------+ + ^ + | + | "new_torrent()" + | "save-path, file_storage," + | "info_hash, allocation_mode" + | "file-priorities" + | "returns storage_holder" + | + v ++----------------------------+ +| "storage_holder" |+ +| "opaque handle to storage" || +-------------------+ +| "for a torrent" |<---+ "file_storage" | ++----------------------------+| | "standard piece" | + +----------------------------+ | "to file mapping" | + +-------------------+ diff --git a/docs/img/troubleshooting.dot b/docs/img/troubleshooting.dot new file mode 100644 index 0000000..c26931e --- /dev/null +++ b/docs/img/troubleshooting.dot @@ -0,0 +1,108 @@ +digraph no_download { + + node [shape=box]; + + node_peers [label="Do you have any peers?\n(torrent_status::num_peers)"]; + node_unchoked [label="Have any of the peers unchoked you?\n(peer_info::flags & peer_info::remote_unchoked)"]; + node_error [label="Does the torrent have an error state?\n(torrent_status::error)"]; + node_paused [label="Is the torrent paused?\n(torrent_status::paused)"]; + node_end_error [label="The error string in the torrent describes what\nwent wrong in the torrent. This is typically\nindicative of a fatal error that, once resolved,\nrequires you to call torrent_handle::clear_error()\nto clear before resuming"]; + node_tracker [label="Do you have any trackers in the torrent?\n(torrent_handle::trackers())"]; + node_auto [label="Is the torrent auto managed?\n(torrent_status::auto_managed)"]; + node_outstanding_reqs [label="Do you have any outstanding requests to any peer?\n(peer_info::download_queue_length)"]; + node_interested [label="Are you interested in any peers?\n(peer_info::flags & peer_info::interesting)"]; + node_tracker_peers [label="Did any tracker return any peers?\n(tracker_reply_alert::num_peers)"]; + + node_dht_enabled [label="Is DHT enabled?\n(session::is_dht_running())"]; + + node_dht_nodes [label="Do you see more than 5 DHT nodes?\n(session_status::dht_nodes)"] + + node_peers_for_sure [label="Do you know for sure the torrent has peers?"]; + + node_peers_connected [label="Were any of the peers connected to?\n(peer_info::flags & peer_info::connecting)"]; + + node_end_wireshark_tracker [label="Wireshark the tracker announce\nfrom all your peers and make sure\nthey all send identical info-hashes."]; + + node_connect_speed [label="connect_speed, connection_limit, ip-filter"]; + + node_upload_mode [label="Is the torrent in upload mode?\n(torrent_status::upload_mode)"]; + node_bwstate [label="What is the peer read_state set to?\n(peer_info::read_state)"]; + + node_dl_limit [label="There is a download rate limit in affect on your peers.\nDo you have a download rate limit set?\n(settings_pack::download_rate_limit)"]; + + node_dl_disk [label="Peers are blocked waiting on the disk.\nThis typically means your disk is overloaded"]; + + node_dl_socket [label="The peer is listening on the socket,\nbut no data is coming down"]; + +// end states + + node_end_queued [label="This means the torrent is 'queued'. i.e. it will\nbe started once the torrents in front of it\ncompletes downloading. To know the queue\norder, see torrent_status::queue_position. To\nconfigure the number of simultaneous downloads,\nsee settings_pack::active_limit and\nsettings_pack::active_downloads."]; + + node_end_stopped [label="This means the torrent is\n'stopped'. To start it call\ntorrent_handle::resume()."]; + + node_end_no_peer_source [label="You don't have any peers and you don't\nhave a way to acquire peers. You either\nneed a torrent with a tracker or you need\nDHT to be enabled. The DHT will not help\nfor private torrents"]; + + node_end_dht_broken [label="The DHT is probably not working correctly.\nYou might want to add a DHT bootstrap node\nthrough session::add_dht_router(), or have a\ntorrent with DHT nodes in it, or peer\nconnections of peers that are part of the DHT\nnetwork."]; + + node_end_no_peers [label="The torrent you are trying to\ndownload may not have any peers,\n and all peers you may see are stale\nand not responding anymore."]; + + node_end_supply_demand [label="There might be a much higher demand\nthan supply in this torrent. Waiting\naround will probably get you unchoked\neventually."]; + + node_end_flash_crowd [label="The torrent might have a very large number\nof peers and only very few seeds. This sometimes\nhappens with torrents that gain popularity\nvery fast, much faster than the initial seed\nand early peers can keep up distributing\nto. Typically when this happens all your peers,\nand you, will get pieces in lock-step."]; + + node_end_no_download [label="This means the torrent is configured to not\ndownload anything. If you want to download,\ntake the torrent out of upload only mode. If\nthe disk the torrent is downloading to is full,\nor if writing to the disk failed in some way, the\ntorrent may have switched into upload mode\nautomatically"]; + + node_peers -> node_error [label="no"]; + node_peers -> node_unchoked [label="yes"]; + + node_error -> node_end_error [label="yes"]; + node_error -> node_paused [label="no"]; + + node_paused -> node_auto [label="yes"]; + node_paused -> node_tracker [label="no"]; + + node_auto -> node_end_queued [label="yes"]; + node_auto -> node_end_stopped [label="no"]; + + node_tracker -> node_tracker_peers [label="yes"]; + node_tracker -> node_dht_enabled [label="no"]; + + node_tracker_peers -> node_peers_for_sure [label="no"]; + node_tracker_peers -> node_peers_connected [label="yes"]; + + node_peers_for_sure -> node_end_wireshark_tracker [label="yes"]; + node_peers_for_sure -> node_dht_enabled [label="no"]; + + node_peers_connected -> node_connect_speed [label="no"]; + node_peers_connected -> node_end_no_peers [label="yes"]; + + node_dht_enabled -> node_end_no_peer_source [label="no"]; + node_dht_enabled -> node_dht_nodes [label="yes"]; + + node_dht_nodes -> node_end_dht_broken [label="no"]; + node_dht_nodes -> node_end_no_peers [label="yes"]; + + node_unchoked -> node_outstanding_reqs [label="yes"]; + node_unchoked -> node_interested [label="no"]; + + node_outstanding_reqs -> node_bwstate [label="yes"]; + node_outstanding_reqs -> node_upload_mode [label="no"]; + + node_upload_mode -> node_end_flash_crowd [label="no"]; + node_upload_mode -> node_end_no_download [label="yes"]; + + node_interested -> node_upload_mode [label="no"]; + node_interested -> node_end_supply_demand [label="yes"]; + + node_bwstate -> "?" [label="peer_info::bw_idle"]; + node_bwstate -> node_dl_limit [label="peer_info::bw_limit"]; + node_bwstate -> node_dl_socket [label="peer_info::bw_network"]; + node_bwstate -> node_dl_disk [label="peer_info::bw_disk"]; + + node_dl_limit -> "Your download rate limit may be set too low.\nKeep in mind that it is specified in bytes\nper second." [label="yes"]; + node_dl_limit -> "mixed mode?" [label="no"]; + + node_dl_disk -> "e" [label="yes"]; + node_dl_disk -> "f" [label="no"]; +} + diff --git a/docs/img/utp_stack.diagram b/docs/img/utp_stack.diagram new file mode 100644 index 0000000..b3b8085 --- /dev/null +++ b/docs/img/utp_stack.diagram @@ -0,0 +1,9 @@ ++------------------------+ +| "BitTorrent protocol" | ++------------------------+ +| "SSL" | ++------------+-----------+ +| "TCP" | "uTP" | +| +-----------+ +| | "UDP" | ++------------+-----------+ diff --git a/docs/img/write_disk_buffers.diagram b/docs/img/write_disk_buffers.diagram new file mode 100644 index 0000000..349cb8e --- /dev/null +++ b/docs/img/write_disk_buffers.diagram @@ -0,0 +1,15 @@ + + "decrypt in place" "move buffer ref." ++------------------+ "(no copy)" +--------------+ "(no copy)" +----------------+ +| "receive buffer" +--=------------>| "plain text" +--=------------->| "store buffer" | ++------------------+ | "buffer" | +------+---------+ + ^ +--------------+ | + | "read() on socket" "write() to file" | + | "(copy)" "(copy)" | +---=----|---------------------------------=---------------------------------|--=---- + | "kernel space" memcpy() into | + | mmap v ++-------+---------+ +---------------------+ +| "socket kernel" | | "kernel page cache" | +| "buffer" | | | ++-----------------+ +---------------------+ diff --git a/docs/include/libtorrent/add_torrent_params.hpp b/docs/include/libtorrent/add_torrent_params.hpp new file mode 100644 index 0000000..db5ffb8 --- /dev/null +++ b/docs/include/libtorrent/add_torrent_params.hpp @@ -0,0 +1,414 @@ +/* + +Copyright (c) 2009-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2019, ghbplayer +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ADD_TORRENT_PARAMS_HPP_INCLUDED +#define TORRENT_ADD_TORRENT_PARAMS_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/version.hpp" +#include "libtorrent/socket.hpp" // for tcp::endpoint +#include "libtorrent/bitfield.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/torrent_flags.hpp" +#include "libtorrent/info_hash.hpp" +#include "libtorrent/download_priority.hpp" +#include "libtorrent/client_data.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" +#include "libtorrent/fwd.hpp" + +namespace libtorrent { + +TORRENT_VERSION_NAMESPACE_3 + + // The add_torrent_params contains all the information in a .torrent file + // along with all information necessary to add that torrent to a session. + // The key fields when adding a torrent are: + // + // * ti - the immutable info-dict part of the torrent + // * info_hash - when you don't have the metadata (.torrent file). This + // uniquely identifies the torrent and can validate the info-dict when + // received from the swarm. + // + // In order to add a torrent to a session, one of those fields must be set + // in addition to ``save_path``. The add_torrent_params object can then be + // passed into one of the ``session::add_torrent()`` overloads or + // ``session::async_add_torrent()``. + // + // If you only specify the info-hash, the torrent file will be downloaded + // from peers, which requires them to support the metadata extension. For + // the metadata extension to work, libtorrent must be built with extensions + // enabled (``TORRENT_DISABLE_EXTENSIONS`` must not be defined). It also + // takes an optional ``name`` argument. This may be left empty in case no + // name should be assigned to the torrent. In case it's not, the name is + // used for the torrent as long as it doesn't have metadata. See + // ``torrent_handle::name``. + // + // The ``add_torrent_params`` is also used when requesting resume data for a + // torrent. It can be saved to and restored from a file and added back to a + // new session. For serialization and de-serialization of + // ``add_torrent_params`` objects, see read_resume_data() and + // write_resume_data(). + // + // The ``add_torrent_params`` is also used to represent a parsed .torrent + // file. It can be loaded via load_torrent_file(), load_torrent_buffer() and + // load_torrent_parsed(). It can be saved via write_torrent_file(). +#include "libtorrent/aux_/disable_warnings_push.hpp" + struct TORRENT_EXPORT add_torrent_params + { + // hidden + add_torrent_params(); + ~add_torrent_params(); + add_torrent_params(add_torrent_params&&) noexcept; + add_torrent_params& operator=(add_torrent_params&&) &; + add_torrent_params(add_torrent_params const&); + add_torrent_params& operator=(add_torrent_params const&) &; + + // These are all deprecated. use torrent_flags_t instead (in + // libtorrent/torrent_flags.hpp) +#if TORRENT_ABI_VERSION == 1 + + using flags_t = torrent_flags_t; + +#define DECL_FLAG(name) \ + TORRENT_DEPRECATED static constexpr torrent_flags_t flag_##name = torrent_flags::name + + DECL_FLAG(seed_mode); + DECL_FLAG(upload_mode); + DECL_FLAG(share_mode); + DECL_FLAG(apply_ip_filter); + DECL_FLAG(paused); + DECL_FLAG(auto_managed); + DECL_FLAG(duplicate_is_error); + DECL_FLAG(update_subscribe); + DECL_FLAG(super_seeding); + DECL_FLAG(sequential_download); + DECL_FLAG(pinned); + DECL_FLAG(stop_when_ready); + DECL_FLAG(override_trackers); + DECL_FLAG(override_web_seeds); + DECL_FLAG(need_save_resume); + DECL_FLAG(override_resume_data); + DECL_FLAG(merge_resume_trackers); + DECL_FLAG(use_resume_save_path); + DECL_FLAG(merge_resume_http_seeds); + DECL_FLAG(default_flags); +#undef DECL_FLAG +#endif // TORRENT_ABI_VERSION + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // filled in by the constructor and should be left untouched. It is used + // for forward binary compatibility. + int version = LIBTORRENT_VERSION_NUM; + + // torrent_info object with the torrent to add. Unless the + // info_hash is set, this is required to be initialized. + std::shared_ptr ti; + + // If the torrent doesn't have a tracker, but relies on the DHT to find + // peers, the ``trackers`` can specify tracker URLs for the torrent. + aux::noexcept_movable> trackers; + + // the tiers the URLs in ``trackers`` belong to. Trackers belonging to + // different tiers may be treated differently, as defined by the multi + // tracker extension. This is optional, if not specified trackers are + // assumed to be part of tier 0, or whichever the last tier was as + // iterating over the trackers. + aux::noexcept_movable> tracker_tiers; + + // a list of hostname and port pairs, representing DHT nodes to be added + // to the session (if DHT is enabled). The hostname may be an IP address. + aux::noexcept_movable>> dht_nodes; + + // in case there's no other name in this torrent, this name will be used. + // The name out of the torrent_info object takes precedence if available. + std::string name; + + // the path where the torrent is or will be stored. + // + // .. note:: + // On windows this path (and other paths) are interpreted as UNC + // paths. This means they must use backslashes as directory separators + // and may not contain the special directories "." or "..". + // + // Setting this to an absolute path performs slightly better than a + // relative path. + std::string save_path; + + // One of the values from storage_mode_t. For more information, see + // storage-allocation_. + storage_mode_t storage_mode = storage_mode_sparse; + + // The ``userdata`` parameter is optional and will be passed on to the + // extension constructor functions, if any + // (see torrent_handle::add_extension()). It will also be stored in the + // torrent object and can be retrieved by calling userdata(). + client_data_t userdata; + + // can be set to control the initial file priorities when adding a + // torrent. The semantics are the same as for + // ``torrent_handle::prioritize_files()``. The file priorities specified + // in here take precedence over those specified in the resume data, if + // any. + // If this vector of file priorities is shorter than the number of files + // in the torrent, the remaining files (not covered by this) will still + // have the default download priority. This default can be changed by + // setting the default_dont_download torrent_flag. + aux::noexcept_movable> file_priorities; + + // torrent extension construction functions can be added to this vector + // to have them be added immediately when the torrent is constructed. + // This may be desired over the torrent_handle::add_extension() in order + // to avoid race conditions. For instance it may be important to have the + // plugin catch events that happen very early on after the torrent is + // created. + aux::noexcept_movable(torrent_handle const&, client_data_t)>>> + extensions; + + // the default tracker id to be used when announcing to trackers. By + // default this is empty, and no tracker ID is used, since this is an + // optional argument. If a tracker returns a tracker ID, that ID is used + // instead of this. + std::string trackerid; + + // flags controlling aspects of this torrent and how it's added. See + // torrent_flags_t for details. + // + // .. note:: + // The ``flags`` field is initialized with default flags by the + // constructor. In order to preserve default behavior when clearing or + // setting other flags, make sure to bitwise OR or in a flag or bitwise + // AND the inverse of a flag to clear it. + torrent_flags_t flags = torrent_flags::default_flags; + +#if TORRENT_ABI_VERSION < 3 + // backwards compatible v1 hash, or truncated v2 + TORRENT_DEPRECATED + sha1_hash info_hash; +#endif + + // set this to the info hash of the torrent to add in case the info-hash + // is the only known property of the torrent. i.e. you don't have a + // .torrent file nor a magnet link. + // To add a magnet link, use parse_magnet_uri() to populate fields in the + // add_torrent_params object. + info_hash_t info_hashes; + + // ``max_uploads``, ``max_connections``, ``upload_limit``, + // ``download_limit`` correspond to the ``set_max_uploads()``, + // ``set_max_connections()``, ``set_upload_limit()`` and + // ``set_download_limit()`` functions on torrent_handle. These values let + // you initialize these settings when the torrent is added, instead of + // calling these functions immediately following adding it. + // + // -1 means unlimited on these settings just like their counterpart + // functions on torrent_handle + // + // For fine grained control over rate limits, including making them apply + // to local peers, see peer-classes_. + int max_uploads = -1; + int max_connections = -1; + + // the upload and download rate limits for this torrent, specified in + // bytes per second. -1 means unlimited. + int upload_limit = -1; + int download_limit = -1; + + // the total number of bytes uploaded and downloaded by this torrent so + // far. + std::int64_t total_uploaded = 0; + std::int64_t total_downloaded = 0; + + // the number of seconds this torrent has spent in started, finished and + // seeding state so far, respectively. + int active_time = 0; + int finished_time = 0; + int seeding_time = 0; + + // if set to a non-zero value, this is the posix time of when this torrent + // was first added, including previous runs/sessions. If set to zero, the + // internal added_time will be set to the time of when add_torrent() is + // called. + std::time_t added_time = 0; + std::time_t completed_time = 0; + + // if set to non-zero, initializes the time (expressed in posix time) when + // we last saw a seed or peers that together formed a complete copy of the + // torrent. If left set to zero, the internal counterpart to this field + // will be updated when we see a seed or a distributed copies >= 1.0. + std::time_t last_seen_complete = 0; + + // these field can be used to initialize the torrent's cached scrape data. + // The scrape data is high level metadata about the current state of the + // swarm, as returned by the tracker (either when announcing to it or by + // sending a specific scrape request). ``num_complete`` is the number of + // peers in the swarm that are seeds, or have every piece in the torrent. + // ``num_incomplete`` is the number of peers in the swarm that do not have + // every piece. ``num_downloaded`` is the number of times the torrent has + // been downloaded (not initiated, but the number of times a download has + // completed). + // + // Leaving any of these values set to -1 indicates we don't know, or we + // have not received any scrape data. + int num_complete = -1; + int num_incomplete = -1; + int num_downloaded = -1; + + // URLs can be added to these two lists to specify additional web + // seeds to be used by the torrent. If the ``flag_override_web_seeds`` + // is set, these will be the _only_ ones to be used. i.e. any web seeds + // found in the .torrent file will be overridden. + // + // http_seeds expects URLs to web servers implementing the original HTTP + // seed specification `BEP 17`_. + // + // url_seeds expects URLs to regular web servers, aka "get right" style, + // specified in `BEP 19`_. + aux::noexcept_movable> http_seeds; + aux::noexcept_movable> url_seeds; + + // peers to add to the torrent, to be tried to be connected to as + // bittorrent peers. + aux::noexcept_movable> peers; + + // peers banned from this torrent. The will not be connected to + aux::noexcept_movable> banned_peers; + + // this is a map of partially downloaded piece. The key is the piece index + // and the value is a bitfield where each bit represents a 16 kiB block. + // A set bit means we have that block. + aux::noexcept_movable> unfinished_pieces; + + // this is a bitfield indicating which pieces we already have of this + // torrent. + typed_bitfield have_pieces; + + // when in seed_mode, pieces with a set bit in this bitfield have been + // verified to be valid. Other pieces will be verified the first time a + // peer requests it. + typed_bitfield verified_pieces; + + // this sets the priorities for each individual piece in the torrent. Each + // element in the vector represent the piece with the same index. If you + // set both file- and piece priorities, file priorities will take + // precedence. + aux::noexcept_movable> piece_priorities; + +#if TORRENT_ABI_VERSION <= 2 + // support for BEP 30 merkle torrents has been removed + + // if this is a merkle tree torrent, and you're seeding, this field must + // be set. It is all the hashes in the binary tree, with the root as the + // first entry. See torrent_info::set_merkle_tree() for more info. + TORRENT_DEPRECATED aux::noexcept_movable> merkle_tree; +#endif + + // v2 hashes, if known + aux::vector, file_index_t> merkle_trees; + + // if set, indicates which hashes are included in the corresponding + // vector of ``merkle_trees``. These bitmasks always cover the full + // tree, a cleared bit means the hash is all zeros (i.e. not set) and + // set bit means the next hash in the corresponding vector in + // ``merkle_trees`` is the hash for that node. This is an optimization + // to avoid storing a lot of zeros. + aux::vector, file_index_t> merkle_tree_mask; + + // bit-fields indicating which v2 leaf hashes have been verified + // against the root hash. If this vector is empty and merkle_trees is + // non-empty it implies that all hashes in merkle_trees are verified. + aux::vector, file_index_t> verified_leaf_hashes; + + // this is a map of file indices in the torrent and new filenames to be + // applied before the torrent is added. + aux::noexcept_movable> renamed_files; + + // the posix time of the last time payload was received or sent for this + // torrent, respectively. A value of 0 means we don't know when we last + // uploaded or downloaded, or we have never uploaded or downloaded any + // payload for this torrent. + std::time_t last_download = 0; + std::time_t last_upload = 0; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + + // ``url`` can be set to a magnet link, in order to download the .torrent + // file (also known as the metadata), specifically the info-dictionary, + // from the bittorrent swarm. This may require access to the DHT, in case + // the magnet link does not come with trackers. + // + // In earlier versions of libtorrent, the URL could be an HTTP or file:// + // url. These uses are deprecated and discouraged. When adding a torrent + // by magnet link, it will be set to the ``downloading_metadata`` state + // until the .torrent file has been downloaded. If there is any error + // while downloading, the torrent will be stopped and the torrent error + // state (``torrent_status::error``) will indicate what went wrong. + TORRENT_DEPRECATED std::string url; + + // The optional parameter, ``resume_data`` can be given if up to date + // fast-resume data is available. The fast-resume data can be acquired + // from a running torrent by calling save_resume_data() on + // torrent_handle. See fast-resume_. The ``vector`` that is passed in + // will be swapped into the running torrent instance with + // ``std::vector::swap()``. + TORRENT_DEPRECATED aux::noexcept_movable> resume_data; + + // to support the deprecated use case of reading the resume data into + // resume_data field and getting a reject alert, any parse failure is + // communicated forward into libtorrent via this field. If this is set, a + // fastresume_rejected_alert will be posted. + error_code internal_resume_data_error; +#endif // TORRENT_ABI_VERSION + + }; + +TORRENT_VERSION_NAMESPACE_3_END + +namespace aux { + TORRENT_EXTRA_EXPORT bool contains_resume_data(add_torrent_params const&); +} + +} + +#endif diff --git a/docs/include/libtorrent/address.hpp b/docs/include/libtorrent/address.hpp new file mode 100644 index 0000000..0639c7b --- /dev/null +++ b/docs/include/libtorrent/address.hpp @@ -0,0 +1,79 @@ +/* + +Copyright (c) 2006, 2009, 2015, 2017-2020, Arvid Norberg +Copyright (c) 2016, 2018, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ADDRESS_HPP_INCLUDED +#define TORRENT_ADDRESS_HPP_INCLUDED + +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/string_view.hpp" + +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#else +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // SIMULATOR + +namespace libtorrent { + +#if defined TORRENT_BUILD_SIMULATOR + using sim::asio::ip::address; + using sim::asio::ip::address_v4; + using sim::asio::ip::address_v6; +#else + using boost::asio::ip::address; + using boost::asio::ip::address_v4; + using boost::asio::ip::address_v6; +#endif // SIMULATOR + + using boost::asio::ip::network_v4; + using boost::asio::ip::make_network_v4; + using boost::asio::ip::v4_mapped; + +#if defined TORRENT_BUILD_SIMULATOR + using sim::asio::ip::make_address; + using sim::asio::ip::make_address_v4; + using sim::asio::ip::make_address_v6; +#else + using boost::asio::ip::make_address; + using boost::asio::ip::make_address_v4; + using boost::asio::ip::make_address_v6; +#endif + +} + +#endif diff --git a/docs/include/libtorrent/alert.hpp b/docs/include/libtorrent/alert.hpp new file mode 100644 index 0000000..9afef63 --- /dev/null +++ b/docs/include/libtorrent/alert.hpp @@ -0,0 +1,333 @@ +/* + +Copyright (c) 2003, Daniel Wallin +Copyright (c) 2004-2005, 2008-2009, 2013-2020, 2022, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALERT_HPP_INCLUDED +#define TORRENT_ALERT_HPP_INCLUDED + +#include + +// OVERVIEW +// +// The pop_alerts() function on session is the main interface for retrieving +// alerts (warnings, messages and errors from libtorrent). If no alerts have +// been posted by libtorrent pop_alerts() will return an empty list. +// +// By default, only errors are reported. settings_pack::alert_mask can be +// used to specify which kinds of events should be reported. The alert mask is +// a combination of the alert_category_t flags in the alert class. +// +// Every alert belongs to one or more category. There is a cost associated with +// posting alerts. Only alerts that belong to an enabled category are +// posted. Setting the alert bitmask to 0 will disable all alerts (except those +// that are non-discardable). Alerts that are responses to API calls such as +// save_resume_data() and post_session_stats() are non-discardable and will be +// posted even if their category is disabled. +// +// There are other alert base classes that some alerts derive from, all the +// alerts that are generated for a specific torrent are derived from +// torrent_alert, and tracker events derive from tracker_alert. +// +// Alerts returned by pop_alerts() are only valid until the next call to +// pop_alerts(). You may not copy an alert object to access it after the next +// call to pop_alerts(). Internal members of alerts also become invalid once +// pop_alerts() is called again. + +#include "libtorrent/time.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/flags.hpp" + +namespace libtorrent { + +// bitmask type used to define alert categories. Categories can be enabled +// and disabled by the settings_pack::alert_mask setting. Constants are defined +// in the lt::alert_category namespace +using alert_category_t = flags::bitfield_flag; + +namespace alert_category { + + // Enables alerts that report an error. This includes: + // + // * tracker errors + // * tracker warnings + // * file errors + // * resume data failures + // * web seed errors + // * .torrent files errors + // * listen socket errors + // * port mapping errors + constexpr alert_category_t error = 0_bit; + + // Enables alerts when peers send invalid requests, get banned or + // snubbed. + constexpr alert_category_t peer = 1_bit; + + // Enables alerts for port mapping events. For NAT-PMP and UPnP. + constexpr alert_category_t port_mapping = 2_bit; + + // Enables alerts for events related to the storage. File errors and + // synchronization events for moving the storage, renaming files etc. + constexpr alert_category_t storage = 3_bit; + + // Enables all tracker events. Includes announcing to trackers, + // receiving responses, warnings and errors. + constexpr alert_category_t tracker = 4_bit; + + // Low level alerts for when peers are connected and disconnected. + constexpr alert_category_t connect = 5_bit; + + // Enables alerts for when a torrent or the session changes state. + constexpr alert_category_t status = 6_bit; + + // Alerts when a peer is blocked by the ip blocker or port blocker. + constexpr alert_category_t ip_block = 8_bit; + + // Alerts when some limit is reached that might limit the download + // or upload rate. + constexpr alert_category_t performance_warning = 9_bit; + + // Alerts on events in the DHT node. For incoming searches or + // bootstrapping being done etc. + constexpr alert_category_t dht = 10_bit; + + // If you enable these alerts, you will receive a stats_alert + // approximately once every second, for every active torrent. + // These alerts contain all statistics counters for the interval since + // the lasts stats alert. + constexpr alert_category_t stats = 11_bit; + + // Enables debug logging alerts. These are available unless libtorrent + // was built with logging disabled (``TORRENT_DISABLE_LOGGING``). The + // alerts being posted are log_alert and are session wide. + constexpr alert_category_t session_log = 13_bit; + + // Enables debug logging alerts for torrents. These are available + // unless libtorrent was built with logging disabled + // (``TORRENT_DISABLE_LOGGING``). The alerts being posted are + // torrent_log_alert and are torrent wide debug events. + constexpr alert_category_t torrent_log = 14_bit; + + // Enables debug logging alerts for peers. These are available unless + // libtorrent was built with logging disabled + // (``TORRENT_DISABLE_LOGGING``). The alerts being posted are + // peer_log_alert and low-level peer events and messages. + constexpr alert_category_t peer_log = 15_bit; + + // enables the incoming_request_alert. + constexpr alert_category_t incoming_request = 16_bit; + + // enables dht_log_alert, debug logging for the DHT + constexpr alert_category_t dht_log = 17_bit; + + // enable events from pure dht operations not related to torrents + constexpr alert_category_t dht_operation = 18_bit; + + // enables port mapping log events. This log is useful + // for debugging the UPnP or NAT-PMP implementation + constexpr alert_category_t port_mapping_log = 19_bit; + + // enables verbose logging from the piece picker. + constexpr alert_category_t picker_log = 20_bit; + + // alerts when files complete downloading + constexpr alert_category_t file_progress = 21_bit; + + // alerts when pieces complete downloading or fail hash check + constexpr alert_category_t piece_progress = 22_bit; + + // alerts when we upload blocks to other peers + constexpr alert_category_t upload = 23_bit; + + // alerts on individual blocks being requested, downloading, finished, + // rejected, time-out and cancelled. This is likely to post alerts at a + // high rate. + constexpr alert_category_t block_progress = 24_bit; + + // The full bitmask, representing all available categories. + // + // since the enum is signed, make sure this isn't + // interpreted as -1. For instance, boost.python + // does that and fails when assigning it to an + // unsigned parameter. + constexpr alert_category_t all = alert_category_t::all(); + +} // namespace alert_category + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // The ``alert`` class is the base class that specific messages are derived from. + // alert types are not copyable, and cannot be constructed by the client. The + // pointers returned by libtorrent are short lived (the details are described + // under session_handle::pop_alerts()) + struct TORRENT_EXPORT alert + { +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // hidden + TORRENT_UNEXPORT alert(alert const& rhs) = delete; + alert& operator=(alert const&) = delete; + alert(alert&& rhs) noexcept = default; + +#if TORRENT_ABI_VERSION == 1 + using category_t = alert_category_t; +#endif + + static constexpr alert_category_t error_notification = 0_bit; + static constexpr alert_category_t peer_notification = 1_bit; + static constexpr alert_category_t port_mapping_notification = 2_bit; + static constexpr alert_category_t storage_notification = 3_bit; + static constexpr alert_category_t tracker_notification = 4_bit; + static constexpr alert_category_t connect_notification = 5_bit; +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + static constexpr alert_category_t debug_notification = connect_notification; +#endif + static constexpr alert_category_t status_notification = 6_bit; +#if TORRENT_ABI_VERSION == 1 + // Alerts for when blocks are requested and completed. Also when + // pieces are completed. + TORRENT_DEPRECATED + static constexpr alert_category_t progress_notification = 7_bit; +#endif + static constexpr alert_category_t ip_block_notification = 8_bit; + static constexpr alert_category_t performance_warning = 9_bit; + static constexpr alert_category_t dht_notification = 10_bit; + +#if TORRENT_ABI_VERSION <= 2 + // If you enable these alerts, you will receive a stats_alert + // approximately once every second, for every active torrent. + // These alerts contain all statistics counters for the interval since + // the lasts stats alert. + TORRENT_DEPRECATED + static constexpr alert_category_t stats_notification = 11_bit; +#endif + static constexpr alert_category_t session_log_notification = 13_bit; + static constexpr alert_category_t torrent_log_notification = 14_bit; + static constexpr alert_category_t peer_log_notification = 15_bit; + static constexpr alert_category_t incoming_request_notification = 16_bit; + static constexpr alert_category_t dht_log_notification = 17_bit; + static constexpr alert_category_t dht_operation_notification = 18_bit; + static constexpr alert_category_t port_mapping_log_notification = 19_bit; + static constexpr alert_category_t picker_log_notification = 20_bit; + static constexpr alert_category_t file_progress_notification = 21_bit; + static constexpr alert_category_t piece_progress_notification = 22_bit; + static constexpr alert_category_t upload_notification = 23_bit; + static constexpr alert_category_t block_progress_notification = 24_bit; + static constexpr alert_category_t all_categories = alert_category_t::all(); + + // hidden + TORRENT_UNEXPORT alert(); + // hidden + virtual ~alert(); + + // a timestamp is automatically created in the constructor + time_point timestamp() const; + + // returns an integer that is unique to this alert type. It can be + // compared against a specific alert by querying a static constant called ``alert_type`` + // in the alert. It can be used to determine the run-time type of an alert* in + // order to cast to that alert type and access specific members. + // + // e.g: + // + // .. code:: c++ + // + // std::vector alerts; + // ses.pop_alerts(&alerts); + // for (alert* a : alerts) { + // switch (a->type()) { + // + // case read_piece_alert::alert_type: + // { + // auto* p = static_cast(a); + // if (p->ec) { + // // read_piece failed + // break; + // } + // // use p + // break; + // } + // case file_renamed_alert::alert_type: + // { + // // etc... + // } + // } + // } + virtual int type() const noexcept = 0; + + // returns a string literal describing the type of the alert. It does + // not include any information that might be bundled with the alert. + virtual char const* what() const noexcept = 0; + + // generate a string describing the alert and the information bundled + // with it. This is mainly intended for debug and development use. It is not suitable + // to use this for applications that may be localized. Instead, handle each alert + // type individually and extract and render the information from the alert depending + // on the locale. + virtual std::string message() const = 0; + + // returns a bitmask specifying which categories this alert belong to. + virtual alert_category_t category() const noexcept = 0; + + private: + time_point const m_timestamp; + }; + +// When you get an alert, you can use ``alert_cast<>`` to attempt to cast the +// pointer to a specific alert type, in order to query it for more +// information. +// +// .. note:: +// ``alert_cast<>`` can only cast to an exact alert type, not a base class +template T* alert_cast(alert* a) +{ + static_assert(std::is_base_of::value + , "alert_cast<> can only be used with alert types (deriving from lt::alert)"); + + if (a == nullptr) return nullptr; + if (a->type() == T::alert_type) return static_cast(a); + return nullptr; +} +template T const* alert_cast(alert const* a) +{ + static_assert(std::is_base_of::value + , "alert_cast<> can only be used with alert types (deriving from lt::alert)"); + if (a == nullptr) return nullptr; + if (a->type() == T::alert_type) return static_cast(a); + return nullptr; +} + +} // namespace libtorrent + +#endif // TORRENT_ALERT_HPP_INCLUDED diff --git a/docs/include/libtorrent/alert_types.hpp b/docs/include/libtorrent/alert_types.hpp new file mode 100644 index 0000000..baa133f --- /dev/null +++ b/docs/include/libtorrent/alert_types.hpp @@ -0,0 +1,3126 @@ +/* + +Copyright (c) 2017, toinetoine +Copyright (c) 2015, Thomas +Copyright (c) 2004-2022, Arvid Norberg +Copyright (c) 2008, Andrew Resch +Copyright (c) 2014-2018, Steven Siloti +Copyright (c) 2015-2018, Alden Torres +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2017, Antoine Dahan +Copyright (c) 2018, d-komarov +Copyright (c) 2019, ghbplayer +Copyright (c) 2020, AllSeeingEyeTolledEweSew +Copyright (c) 2020, Fonic +Copyright (c) 2020, Viktor Elofsson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALERT_TYPES_HPP_INCLUDED +#define TORRENT_ALERT_TYPES_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/torrent_status.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/operations.hpp" // for operation_t enum +#include "libtorrent/close_reason.hpp" +#include "libtorrent/piece_block.hpp" +#include "libtorrent/aux_/escape_string.hpp" // for convert_from_native +#include "libtorrent/string_view.hpp" +#include "libtorrent/stack_allocator.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" +#include "libtorrent/portmap.hpp" // for portmap_transport +#include "libtorrent/tracker_manager.hpp" // for event_t +#include "libtorrent/socket_type.hpp" +#include "libtorrent/client_data.hpp" +#include "libtorrent/peer_info.hpp" // for peer_info +#include "libtorrent/aux_/deprecated.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include // for va_list + +#if TORRENT_ABI_VERSION == 1 +#define PROGRESS_NOTIFICATION | alert::progress_notification +#else +#define PROGRESS_NOTIFICATION +#endif + + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED_EXPORT char const* operation_name(int op); +#endif + + // internal + TORRENT_EXPORT char const* alert_name(int alert_type); + + // user defined alerts should use IDs greater than this + constexpr int user_alert_id = 10000; + + // this constant represents "max_alert_index" + 1 + constexpr int num_alert_types = 105; + + // internal + constexpr int abi_alert_count = 128; + + // internal + enum class alert_priority : std::uint8_t + { + // the order matters here. Lower value means lower priority, and will + // start getting dropped earlier when the alert queue is filling up + normal = 0, + high, + critical, + meta + }; + + // struct to hold information about a single DHT routing table bucket + struct TORRENT_EXPORT dht_routing_bucket + { + // the total number of nodes and replacement nodes + // in the routing table + int num_nodes; + int num_replacements; + + // number of seconds since last activity + int last_active; + }; + +TORRENT_VERSION_NAMESPACE_3 + + // This is a base class for alerts that are associated with a + // specific torrent. It contains a handle to the torrent. + // + // Note that by the time the client receives a torrent_alert, its + // ``handle`` member may be invalid. + struct TORRENT_EXPORT torrent_alert : alert + { + // internal + TORRENT_UNEXPORT torrent_alert(aux::stack_allocator& alloc, torrent_handle const& h); + TORRENT_UNEXPORT torrent_alert(torrent_alert&&) noexcept = default; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED static int const alert_type = 0; +#endif + + // returns the message associated with this alert + std::string message() const override; + + // The torrent_handle pointing to the torrent this + // alert is associated with. + torrent_handle handle; + + char const* torrent_name() const; + + protected: + std::reference_wrapper m_alloc; + private: + aux::allocation_slot m_name_idx; +#if TORRENT_ABI_VERSION == 1 + public: + TORRENT_DEPRECATED std::string name; +#endif + }; + + // The peer alert is a base class for alerts that refer to a specific peer. It includes all + // the information to identify the peer. i.e. ``ip`` and ``peer-id``. + struct TORRENT_EXPORT peer_alert : torrent_alert + { + // internal + TORRENT_UNEXPORT peer_alert(aux::stack_allocator& alloc, torrent_handle const& h, + tcp::endpoint const& i, peer_id const& pi); + TORRENT_UNEXPORT peer_alert(peer_alert&& rhs) noexcept = default; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED static int const alert_type = 1; +#endif + + std::string message() const override; + + // The peer's IP address and port. + aux::noexcept_movable endpoint; + + // the peer ID, if known. + peer_id pid; + +#if TORRENT_ABI_VERSION == 1 + // The peer's IP address and port. + TORRENT_DEPRECATED aux::noexcept_movable ip; +#endif + }; + + // This is a base class used for alerts that are associated with a + // specific tracker. It derives from torrent_alert since a tracker + // is also associated with a specific torrent. + struct TORRENT_EXPORT tracker_alert : torrent_alert + { + // internal + TORRENT_UNEXPORT tracker_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep, string_view u); + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED static int const alert_type = 2; +#endif + + std::string message() const override; + + // endpoint of the listen interface being announced + aux::noexcept_movable local_endpoint; + + // returns a 0-terminated string of the tracker's URL + char const* tracker_url() const; + + private: + aux::allocation_slot m_url_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // The tracker URL + TORRENT_DEPRECATED std::string url; +#endif + }; + +#define TORRENT_DEFINE_ALERT_IMPL(name, seq, prio) \ + name(name&&) noexcept = default; \ + static alert_priority const priority = prio; \ + static int const alert_type = seq; \ + virtual int type() const noexcept override { return alert_type; } \ + virtual alert_category_t category() const noexcept override { return static_category; } \ + virtual char const* what() const noexcept override { return alert_name(alert_type); } + +#define TORRENT_DEFINE_ALERT(name, seq) \ + TORRENT_DEFINE_ALERT_IMPL(name, seq, alert_priority::normal) + +#define TORRENT_DEFINE_ALERT_PRIO(name, seq, prio) \ + TORRENT_DEFINE_ALERT_IMPL(name, seq, prio) + +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // The ``torrent_added_alert`` is posted once every time a torrent is successfully + // added. It doesn't contain any members of its own, but inherits the torrent handle + // from its base class. + // It's posted when the ``alert_category::status`` bit is set in the alert_mask. + // deprecated in 1.1.3 + // use add_torrent_alert instead + struct TORRENT_DEPRECATED_EXPORT torrent_added_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_added_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + TORRENT_DEFINE_ALERT(torrent_added_alert, 3) + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + }; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif + + // The ``torrent_removed_alert`` is posted whenever a torrent is removed. Since + // the torrent handle in its base class will usually be invalid (since the torrent + // is already removed) it has the info hash as a member, to identify it. + // It's posted when the ``alert_category::status`` bit is set in the alert_mask. + // + // Note that the ``handle`` remains valid for some time after + // torrent_removed_alert is posted, as long as some internal libtorrent + // task (such as an I/O task) refers to it. Additionally, other alerts like + // save_resume_data_alert may be posted after torrent_removed_alert. + // To synchronize on whether the torrent has been removed or not, call + // torrent_handle::in_session(). This will return true before + // torrent_removed_alert is posted, and false afterward. + // + // Even though the ``handle`` member doesn't point to an existing torrent anymore, + // it is still useful for comparing to other handles, which may also no + // longer point to existing torrents, but to the same non-existing torrents. + // + // The ``torrent_handle`` acts as a ``weak_ptr``, even though its object no + // longer exists, it can still compare equal to another weak pointer which + // points to the same non-existent object. + struct TORRENT_EXPORT torrent_removed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_removed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, info_hash_t const& ih, client_data_t userdata); + + TORRENT_DEFINE_ALERT_PRIO(torrent_removed_alert, 4, alert_priority::critical) + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; +#if TORRENT_ABI_VERSION < 3 + TORRENT_DEPRECATED sha1_hash info_hash; +#endif + info_hash_t info_hashes; + + // '`userdata`` as set in add_torrent_params at torrent creation. + // This can be used to associate this torrent with related data + // in the client application more efficiently than info_hashes. + client_data_t userdata; + }; + + // This alert is posted when the asynchronous read operation initiated by + // a call to torrent_handle::read_piece() is completed. If the read failed, the torrent + // is paused and an error state is set and the buffer member of the alert + // is 0. If successful, ``buffer`` points to a buffer containing all the data + // of the piece. ``piece`` is the piece index that was read. ``size`` is the + // number of bytes that was read. + // + // If the operation fails, ``error`` will indicate what went wrong. + struct TORRENT_EXPORT read_piece_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT read_piece_alert(aux::stack_allocator& alloc, torrent_handle const& h + , piece_index_t p, boost::shared_array d, int s); + TORRENT_UNEXPORT read_piece_alert(aux::stack_allocator& alloc, torrent_handle h + , piece_index_t p, error_code e); + + TORRENT_DEFINE_ALERT_PRIO(read_piece_alert, 5, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + error_code const error; + boost::shared_array const buffer; + piece_index_t const piece; + int const size; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED error_code ec; +#endif + }; + + // This is posted whenever an individual file completes its download. i.e. + // All pieces overlapping this file have passed their hash check. + struct TORRENT_EXPORT file_completed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT file_completed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , file_index_t idx); + + TORRENT_DEFINE_ALERT_PRIO(file_completed_alert, 6, alert_priority::normal) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + static constexpr alert_category_t static_category = + alert_category::file_progress + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + std::string message() const override; + + // refers to the index of the file that completed. + file_index_t const index; + }; + + // This is posted as a response to a torrent_handle::rename_file() call, if the rename + // operation succeeds. + struct TORRENT_EXPORT file_renamed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT file_renamed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , string_view n, string_view old, file_index_t idx); + + TORRENT_DEFINE_ALERT_PRIO(file_renamed_alert, 7, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + // returns the new and previous file name, respectively. + char const* new_name() const; + char const* old_name() const; + + // refers to the index of the file that was renamed, + file_index_t const index; + private: + aux::allocation_slot m_name_idx; + aux::allocation_slot m_old_name_idx; +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/aux_/disable_warnings_push.hpp" + + public: + TORRENT_DEPRECATED std::string name; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif + }; + + // This is posted as a response to a torrent_handle::rename_file() call, if the rename + // operation failed. + struct TORRENT_EXPORT file_rename_failed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT file_rename_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, file_index_t idx + , error_code ec); + + TORRENT_DEFINE_ALERT_PRIO(file_rename_failed_alert, 8, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::storage; + + std::string message() const override; + + // refers to the index of the file that was supposed to be renamed, + // ``error`` is the error code returned from the filesystem. + file_index_t const index; + error_code const error; + }; + + // This alert is generated when a limit is reached that might have a negative impact on + // upload or download rate performance. + struct TORRENT_EXPORT performance_alert final : torrent_alert + { + enum performance_warning_t + { + + // This warning means that the number of bytes queued to be written to disk + // exceeds the max disk byte queue setting (``settings_pack::max_queued_disk_bytes``). + // This might restrict the download rate, by not queuing up enough write jobs + // to the disk I/O thread. When this alert is posted, peer connections are + // temporarily stopped from downloading, until the queued disk bytes have fallen + // below the limit again. Unless your ``max_queued_disk_bytes`` setting is already + // high, you might want to increase it to get better performance. + outstanding_disk_buffer_limit_reached, + + // This is posted when libtorrent would like to send more requests to a peer, + // but it's limited by ``settings_pack::max_out_request_queue``. The queue length + // libtorrent is trying to achieve is determined by the download rate and the + // assumed round-trip-time (``settings_pack::request_queue_time``). The assumed + // round-trip-time is not limited to just the network RTT, but also the remote disk + // access time and message handling time. It defaults to 3 seconds. The target number + // of outstanding requests is set to fill the bandwidth-delay product (assumed RTT + // times download rate divided by number of bytes per request). When this alert + // is posted, there is a risk that the number of outstanding requests is too low + // and limits the download rate. You might want to increase the ``max_out_request_queue`` + // setting. + outstanding_request_limit_reached, + + // This warning is posted when the amount of TCP/IP overhead is greater than the + // upload rate limit. When this happens, the TCP/IP overhead is caused by a much + // faster download rate, triggering TCP ACK packets. These packets eat into the + // rate limit specified to libtorrent. When the overhead traffic is greater than + // the rate limit, libtorrent will not be able to send any actual payload, such + // as piece requests. This means the download rate will suffer, and new requests + // can be sent again. There will be an equilibrium where the download rate, on + // average, is about 20 times the upload rate limit. If you want to maximize the + // download rate, increase the upload rate limit above 5% of your download capacity. + upload_limit_too_low, + + // This is the same warning as ``upload_limit_too_low`` but referring to the download + // limit instead of upload. This suggests that your download rate limit is much lower + // than your upload capacity. Your upload rate will suffer. To maximize upload rate, + // make sure your download rate limit is above 5% of your upload capacity. + download_limit_too_low, + + // We're stalled on the disk. We want to write to the socket, and we can write + // but our send buffer is empty, waiting to be refilled from the disk. + // This either means the disk is slower than the network connection + // or that our send buffer watermark is too small, because we can + // send it all before the disk gets back to us. + // The number of bytes that we keep outstanding, requested from the disk, is calculated + // as follows: + // + // .. code:: C++ + // + // min(512, max(upload_rate * send_buffer_watermark_factor / 100, send_buffer_watermark)) + // + // If you receive this alert, you might want to either increase your ``send_buffer_watermark`` + // or ``send_buffer_watermark_factor``. + send_buffer_watermark_too_low, + + // If the half (or more) of all upload slots are set as optimistic unchoke slots, this + // warning is issued. You probably want more regular (rate based) unchoke slots. + too_many_optimistic_unchoke_slots, + + // If the disk write queue ever grows larger than half of the cache size, this warning + // is posted. The disk write queue eats into the total disk cache and leaves very little + // left for the actual cache. This causes the disk cache to oscillate in evicting large + // portions of the cache before allowing peers to download any more, onto the disk write + // queue. Either lower ``max_queued_disk_bytes`` or increase ``cache_size``. + too_high_disk_queue_limit, + + aio_limit_reached, +#if TORRENT_ABI_VERSION == 1 + bittyrant_with_no_uplimit TORRENT_DEPRECATED_ENUM, +#else + deprecated_bittyrant_with_no_uplimit, +#endif + + // This is generated if outgoing peer connections are failing because of *address in use* + // errors, indicating that ``settings_pack::outgoing_ports`` is set and is too small of + // a range. Consider not using the ``outgoing_ports`` setting at all, or widen the range to + // include more ports. + too_few_outgoing_ports, + + too_few_file_descriptors, + + num_warnings + }; + + // internal + TORRENT_UNEXPORT performance_alert(aux::stack_allocator& alloc, torrent_handle const& h + , performance_warning_t w); + + TORRENT_DEFINE_ALERT(performance_alert, 9) + + static constexpr alert_category_t static_category = alert_category::performance_warning; + + std::string message() const override; + + performance_warning_t const warning_code; + }; + + // Generated whenever a torrent changes its state. + struct TORRENT_EXPORT state_changed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT state_changed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , torrent_status::state_t st + , torrent_status::state_t prev_st); + + TORRENT_DEFINE_ALERT_PRIO(state_changed_alert, 10, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::status; + + std::string message() const override; + + // the new state of the torrent. + torrent_status::state_t const state; + + // the previous state. + torrent_status::state_t const prev_state; + }; + + // This alert is generated on tracker time outs, premature disconnects, + // invalid response or a HTTP response other than "200 OK". From the alert + // you can get the handle to the torrent the tracker belongs to. + struct TORRENT_EXPORT tracker_error_alert final : tracker_alert + { + // internal + TORRENT_UNEXPORT tracker_error_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , int times, protocol_version v, string_view u + , operation_t op, error_code const& e, string_view m); + + TORRENT_DEFINE_ALERT_PRIO(tracker_error_alert, 11, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::tracker | alert_category::error; + std::string message() const override; + + // This member says how many times in a row this tracker has failed. + int const times_in_row; + + // the error code indicating why the tracker announce failed. If it is + // is ``lt::errors::tracker_failure`` the failure_reason() might contain + // a more detailed description of why the tracker rejected the request. + // HTTP status codes indicating errors are also set in this field. + error_code const error; + + operation_t op; + + // if the tracker sent a "failure reason" string, it will be returned + // here. + char const* failure_reason() const; + + // hidden + char const* error_message() const { return failure_reason(); } + + private: + aux::allocation_slot m_msg_idx; +#if TORRENT_ABI_VERSION == 1 + public: + TORRENT_DEPRECATED int const status_code; + TORRENT_DEPRECATED std::string msg; +#endif + public: + // the bittorrent protocol version that was announced + protocol_version version; + }; + + // This alert is triggered if the tracker reply contains a warning field. + // Usually this means that the tracker announce was successful, but the + // tracker has a message to the client. + struct TORRENT_EXPORT tracker_warning_alert final : tracker_alert + { + // internal + TORRENT_UNEXPORT tracker_warning_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , string_view u, protocol_version v, string_view m); + + TORRENT_DEFINE_ALERT(tracker_warning_alert, 12) + + static constexpr alert_category_t static_category = alert_category::tracker | alert_category::error; + std::string message() const override; + + // the message associated with this warning + char const* warning_message() const; + + private: + aux::allocation_slot m_msg_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // contains the warning message from the tracker. + TORRENT_DEPRECATED std::string msg; +#endif + public: + // the bittorrent protocol version that was announced + protocol_version version; + }; + + // This alert is generated when a scrape request succeeds. + struct TORRENT_EXPORT scrape_reply_alert final : tracker_alert + { + // internal + TORRENT_UNEXPORT scrape_reply_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , int incomp, int comp, string_view u, protocol_version v); + + TORRENT_DEFINE_ALERT_PRIO(scrape_reply_alert, 13, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::tracker; + std::string message() const override; + + // the data returned in the scrape response. These numbers + // may be -1 if the response was malformed. + int const incomplete; + int const complete; + + // the bittorrent protocol version that was scraped + protocol_version version; + }; + + // If a scrape request fails, this alert is generated. This might be due + // to the tracker timing out, refusing connection or returning an http response + // code indicating an error. + struct TORRENT_EXPORT scrape_failed_alert final : tracker_alert + { + // internal + TORRENT_UNEXPORT scrape_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , string_view u, protocol_version v, error_code const& e); + TORRENT_UNEXPORT scrape_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , string_view u, string_view m); + + TORRENT_DEFINE_ALERT_PRIO(scrape_failed_alert, 14, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::tracker | alert_category::error; + std::string message() const override; + + // the error itself. This may indicate that the tracker sent an error + // message (``error::tracker_failure``), in which case it can be + // retrieved by calling ``error_message()``. + error_code const error; + + // if the error indicates there is an associated message, this returns + // that message. Otherwise and empty string. + char const* error_message() const; + + private: + aux::allocation_slot m_msg_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // contains a message describing the error. + TORRENT_DEPRECATED std::string msg; +#endif + public: + // the bittorrent protocol version that was scraped + protocol_version version; + }; + + // This alert is only for informational purpose. It is generated when a tracker announce + // succeeds. It is generated regardless what kind of tracker was used, be it UDP, HTTP or + // the DHT. + struct TORRENT_EXPORT tracker_reply_alert final : tracker_alert + { + // internal + TORRENT_UNEXPORT tracker_reply_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , int np, protocol_version v, string_view u); + + TORRENT_DEFINE_ALERT(tracker_reply_alert, 15) + + static constexpr alert_category_t static_category = alert_category::tracker; + std::string message() const override; + + // tells how many peers the tracker returned in this response. This is + // not expected to be greater than the ``num_want`` settings. These are not necessarily + // all new peers, some of them may already be connected. + int const num_peers; + + // the bittorrent protocol version that was announced + protocol_version version; + }; + + // This alert is generated each time the DHT receives peers from a node. ``num_peers`` + // is the number of peers we received in this packet. Typically these packets are + // received from multiple DHT nodes, and so the alerts are typically generated + // a few at a time. + struct TORRENT_EXPORT dht_reply_alert final : tracker_alert + { + // internal + TORRENT_UNEXPORT dht_reply_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , int np); + + TORRENT_DEFINE_ALERT(dht_reply_alert, 16) + + static constexpr alert_category_t static_category = alert_category::dht | alert_category::tracker; + std::string message() const override; + + int const num_peers; + }; + + // This alert is generated each time a tracker announce is sent (or attempted to be sent). + // There are no extra data members in this alert. The url can be found in the base class + // however. + struct TORRENT_EXPORT tracker_announce_alert final : tracker_alert + { + // internal + TORRENT_UNEXPORT tracker_announce_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , string_view u, protocol_version v, event_t e); + + TORRENT_DEFINE_ALERT(tracker_announce_alert, 17) + + static constexpr alert_category_t static_category = alert_category::tracker; + std::string message() const override; + + // specifies what event was sent to the tracker. See event_t. + event_t const event; + + // the bittorrent protocol version that is announced + protocol_version version; + }; + + // This alert is generated when a finished piece fails its hash check. You can get the handle + // to the torrent which got the failed piece and the index of the piece itself from the alert. + struct TORRENT_EXPORT hash_failed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT hash_failed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , piece_index_t index); + + TORRENT_DEFINE_ALERT(hash_failed_alert, 18) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + piece_index_t const piece_index; + }; + + // This alert is generated when a peer is banned because it has sent too many corrupt pieces + // to us. ``ip`` is the endpoint to the peer that was banned. + struct TORRENT_EXPORT peer_ban_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT peer_ban_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id); + + TORRENT_DEFINE_ALERT(peer_ban_alert, 19) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + }; + + // This alert is generated when a peer is un-snubbed. Essentially when it was snubbed for stalling + // sending data, and now it started sending data again. + struct TORRENT_EXPORT peer_unsnubbed_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT peer_unsnubbed_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id); + + TORRENT_DEFINE_ALERT(peer_unsnubbed_alert, 20) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + }; + + // This alert is generated when a peer is snubbed, when it stops sending data when we request + // it. + struct TORRENT_EXPORT peer_snubbed_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT peer_snubbed_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id); + + TORRENT_DEFINE_ALERT(peer_snubbed_alert, 21) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + }; + + // This alert is generated when a peer sends invalid data over the peer-peer protocol. The peer + // will be disconnected, but you get its ip address from the alert, to identify it. + struct TORRENT_EXPORT peer_error_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT peer_error_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep, peer_id const& peer_id, operation_t op + , error_code const& e); + + TORRENT_DEFINE_ALERT(peer_error_alert, 22) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + + // a 0-terminated string of the low-level operation that failed, or nullptr if + // there was no low level disk operation. + operation_t op; + + // tells you what error caused this alert. + error_code const error; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED int const operation; + TORRENT_DEPRECATED std::string msg; +#endif + }; + + // This alert is posted every time an incoming peer connection both + // successfully passes the protocol handshake and is associated with a + // torrent, or an outgoing peer connection attempt succeeds. For arbitrary + // incoming connections, see incoming_connection_alert. + struct TORRENT_EXPORT peer_connect_alert final : peer_alert + { + enum class direction_t { in, out }; + + // internal + TORRENT_UNEXPORT peer_connect_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, socket_type_t type, direction_t direction); + + TORRENT_DEFINE_ALERT(peer_connect_alert, 23) + + static constexpr alert_category_t static_category = alert_category::connect; + std::string message() const override; + + // Tells you if the peer was incoming or outgoing + direction_t direction; + + socket_type_t socket_type; + }; + + // This alert is generated when a peer is disconnected for any reason (other than the ones + // covered by peer_error_alert ). + struct TORRENT_EXPORT peer_disconnected_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT peer_disconnected_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , peer_id const& peer_id, operation_t op, socket_type_t type, error_code const& e + , close_reason_t r); + + TORRENT_DEFINE_ALERT(peer_disconnected_alert, 24) + + static constexpr alert_category_t static_category = alert_category::connect; + std::string message() const override; + + // the kind of socket this peer was connected over + socket_type_t const socket_type; + + // the operation or level where the error occurred. Specified as an + // value from the operation_t enum. Defined in operations.hpp. + operation_t const op; + + // tells you what error caused peer to disconnect. + error_code const error; + + // the reason the peer disconnected (if specified) + close_reason_t const reason; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED int const operation; + TORRENT_DEPRECATED std::string msg; +#endif + }; + + // This is a debug alert that is generated by an incoming invalid piece request. + // ``ip`` is the address of the peer and the ``request`` is the actual incoming + // request from the peer. See peer_request for more info. + struct TORRENT_EXPORT invalid_request_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT invalid_request_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , peer_id const& peer_id, peer_request const& r + , bool we_have, bool peer_interested, bool withheld); + + TORRENT_DEFINE_ALERT(invalid_request_alert, 25) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + + // the request we received from the peer + peer_request const request; + + // true if we have this piece + bool const we_have; + + // true if the peer indicated that it was interested to download before + // sending the request + bool const peer_interested; + + // if this is true, the peer is not allowed to download this piece because + // of super-seeding rules. + bool const withheld; + }; + + // This alert is generated when a torrent switches from being a downloader to a seed. + // It will only be generated once per torrent. It contains a torrent_handle to the + // torrent in question. + struct TORRENT_EXPORT torrent_finished_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_finished_alert(aux::stack_allocator& alloc, + torrent_handle h); + + TORRENT_DEFINE_ALERT_PRIO(torrent_finished_alert, 26, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + }; + + // this alert is posted every time a piece completes downloading + // and passes the hash check. This alert derives from torrent_alert + // which contains the torrent_handle to the torrent the piece belongs to. + // Note that being downloaded and passing the hash check may happen before + // the piece is also fully flushed to disk. So torrent_handle::have_piece() + // may still return false + struct TORRENT_EXPORT piece_finished_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT piece_finished_alert(aux::stack_allocator& alloc, + torrent_handle const& h, piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(piece_finished_alert, 27) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + static constexpr alert_category_t static_category = + alert_category::piece_progress + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + std::string message() const override; + + // the index of the piece that finished + piece_index_t const piece_index; + }; + + // This alert is generated when a peer rejects or ignores a piece request. + struct TORRENT_EXPORT request_dropped_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT request_dropped_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(request_dropped_alert, 28) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + static constexpr alert_category_t static_category = + alert_category::block_progress + | alert_category::peer + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + std::string message() const override; + + int const block_index; + piece_index_t const piece_index; + }; + + // This alert is generated when a block request times out. + struct TORRENT_EXPORT block_timeout_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT block_timeout_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(block_timeout_alert, 29) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + static constexpr alert_category_t static_category = + alert_category::block_progress + | alert_category::peer + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + std::string message() const override; + + int const block_index; + piece_index_t const piece_index; + }; + + // This alert is generated when a block request receives a response. + struct TORRENT_EXPORT block_finished_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT block_finished_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(block_finished_alert, 30) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + static constexpr alert_category_t static_category = + alert_category::block_progress + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + std::string message() const override; + + int const block_index; + piece_index_t const piece_index; + }; + + // This alert is generated when a block request is sent to a peer. + struct TORRENT_EXPORT block_downloading_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT block_downloading_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep + , peer_id const& peer_id, int block_num, piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(block_downloading_alert, 31) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + static constexpr alert_category_t static_category = + alert_category::block_progress + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + std::string message() const override; + + int const block_index; + piece_index_t const piece_index; +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED char const* peer_speedmsg; +#endif + }; + + // This alert is generated when a block is received that was not requested or + // whose request timed out. + struct TORRENT_EXPORT unwanted_block_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT unwanted_block_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep + , peer_id const& peer_id, int block_num, piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(unwanted_block_alert, 32) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + + int const block_index; + piece_index_t const piece_index; + }; + + // The ``storage_moved_alert`` is generated when all the disk IO has + // completed and the files have been moved, as an effect of a call to + // ``torrent_handle::move_storage``. This is useful to synchronize with the + // actual disk. The ``storage_path()`` member return the new path of the + // storage. + struct TORRENT_EXPORT storage_moved_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT storage_moved_alert(aux::stack_allocator& alloc + , torrent_handle const& h, string_view p, string_view old); + + TORRENT_DEFINE_ALERT_PRIO(storage_moved_alert, 33, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + // the path the torrent was moved to and from, respectively. + char const* storage_path() const; + char const* old_path() const; + + private: + aux::allocation_slot m_path_idx; + aux::allocation_slot m_old_path_idx; +#if TORRENT_ABI_VERSION == 1 + public: + TORRENT_DEPRECATED std::string path; +#endif + }; + + // The ``storage_moved_failed_alert`` is generated when an attempt to move the storage, + // via torrent_handle::move_storage(), fails. + struct TORRENT_EXPORT storage_moved_failed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT storage_moved_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& e, string_view file + , operation_t op); + + TORRENT_DEFINE_ALERT_PRIO(storage_moved_failed_alert, 34, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + error_code const error; + + // If the error happened for a specific file, this returns its path. + char const* file_path() const; + + // this indicates what underlying operation caused the error + operation_t op; + private: + aux::allocation_slot m_file_idx; +#if TORRENT_ABI_VERSION == 1 + public: + TORRENT_DEPRECATED char const* operation; + // If the error happened for a specific file, ``file`` is its path. + TORRENT_DEPRECATED std::string file; +#endif + }; + + // This alert is generated when a request to delete the files of a torrent complete. + // + // This alert is posted in the ``alert_category::storage`` category, and that bit + // needs to be set in the alert_mask. + struct TORRENT_EXPORT torrent_deleted_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_deleted_alert(aux::stack_allocator& alloc + , torrent_handle const& h, info_hash_t const& ih); + + TORRENT_DEFINE_ALERT_PRIO(torrent_deleted_alert, 35, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + +#if TORRENT_ABI_VERSION < 3 + TORRENT_DEPRECATED sha1_hash info_hash; +#endif + + // The info-hash of the torrent that was just deleted. Most of + // the time the torrent_handle in the ``torrent_alert`` will be invalid by the time + // this alert arrives, since the torrent is being deleted. The ``info_hashes`` member + // is hence the main way of identifying which torrent just completed the delete. + info_hash_t info_hashes; + }; + + // This alert is generated when a request to delete the files of a torrent fails. + // Just removing a torrent from the session cannot fail + struct TORRENT_EXPORT torrent_delete_failed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_delete_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& e, info_hash_t const& ih); + + TORRENT_DEFINE_ALERT_PRIO(torrent_delete_failed_alert, 36, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::storage + | alert_category::error; + std::string message() const override; + + // tells you why it failed. + error_code const error; + +#if TORRENT_ABI_VERSION < 3 + TORRENT_DEPRECATED sha1_hash info_hash; +#endif + // the info hash of the torrent whose files failed to be deleted + info_hash_t info_hashes; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED std::string msg; +#endif + }; + + // This alert is generated as a response to a ``torrent_handle::save_resume_data`` request. + // It is generated once the disk IO thread is done writing the state for this torrent. + struct TORRENT_EXPORT save_resume_data_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT save_resume_data_alert(aux::stack_allocator& alloc + , add_torrent_params&& params + , torrent_handle const& h); + TORRENT_UNEXPORT save_resume_data_alert(aux::stack_allocator& alloc + , add_torrent_params const& params + , torrent_handle const& h) = delete; + + TORRENT_DEFINE_ALERT_PRIO(save_resume_data_alert, 37, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + // the ``params`` object is populated with the torrent file whose resume + // data was saved. It is suitable to be: + // + // * added to a session with add_torrent() or async_add_torrent() + // * saved to disk with write_resume_data() + // * turned into a magnet link with make_magnet_uri() + // * saved as a .torrent file with write_torrent_file() + add_torrent_params params; + +#if TORRENT_ABI_VERSION == 1 + // points to the resume data. + TORRENT_DEPRECATED std::shared_ptr resume_data; +#endif + }; + + // This alert is generated instead of ``save_resume_data_alert`` if there was an error + // generating the resume data. ``error`` describes what went wrong. + struct TORRENT_EXPORT save_resume_data_failed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT save_resume_data_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& e); + + TORRENT_DEFINE_ALERT_PRIO(save_resume_data_failed_alert, 38, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::storage + | alert_category::error; + std::string message() const override; + + // the error code from the resume_data failure + error_code const error; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED std::string msg; +#endif + }; + + // This alert is generated as a response to a ``torrent_handle::pause`` request. It is + // generated once all disk IO is complete and the files in the torrent have been closed. + // This is useful for synchronizing with the disk. + struct TORRENT_EXPORT torrent_paused_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_paused_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + TORRENT_DEFINE_ALERT_PRIO(torrent_paused_alert, 39, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + }; + + // This alert is generated as a response to a torrent_handle::resume() request. It is + // generated when a torrent goes from a paused state to an active state. + struct TORRENT_EXPORT torrent_resumed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_resumed_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + TORRENT_DEFINE_ALERT_PRIO(torrent_resumed_alert, 40, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + }; + + // This alert is posted when a torrent completes checking. i.e. when it transitions + // out of the ``checking files`` state into a state where it is ready to start downloading + struct TORRENT_EXPORT torrent_checked_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_checked_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + TORRENT_DEFINE_ALERT_PRIO(torrent_checked_alert, 41, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + }; + + // This alert is generated when a HTTP seed name lookup fails. + struct TORRENT_EXPORT url_seed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT url_seed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , string_view u, error_code const& e); + TORRENT_UNEXPORT url_seed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , string_view u, string_view m); + + TORRENT_DEFINE_ALERT(url_seed_alert, 42) + + static constexpr alert_category_t static_category = alert_category::peer | alert_category::error; + std::string message() const override; + + // the error the web seed encountered. If this is not set, the server + // sent an error message, call ``error_message()``. + error_code const error; + + // the URL the error is associated with + char const* server_url() const; + + // in case the web server sent an error message, this function returns + // it. + char const* error_message() const; + + private: + aux::allocation_slot m_url_idx; + aux::allocation_slot m_msg_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // the HTTP seed that failed + TORRENT_DEPRECATED std::string url; + + // the error message, potentially from the server + TORRENT_DEPRECATED std::string msg; +#endif + + }; + + // If the storage fails to read or write files that it needs access to, this alert is + // generated and the torrent is paused. + struct TORRENT_EXPORT file_error_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT file_error_alert(aux::stack_allocator& alloc, error_code const& ec + , string_view file, operation_t op, torrent_handle const& h); + + TORRENT_DEFINE_ALERT_PRIO(file_error_alert, 43, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::status + | alert_category::error + | alert_category::storage; + std::string message() const override; + + // the error code describing the error. + error_code const error; + + // indicates which underlying operation caused the error + operation_t op; + + // the file that experienced the error + char const* filename() const; + + private: + aux::allocation_slot m_file_idx; +#if TORRENT_ABI_VERSION == 1 + public: + TORRENT_DEPRECATED char const* operation; + // the path to the file that was accessed when the error occurred. + TORRENT_DEPRECATED std::string file; + TORRENT_DEPRECATED std::string msg; +#endif + }; + + // This alert is generated when the metadata has been completely received and the info-hash + // failed to match it. i.e. the metadata that was received was corrupt. libtorrent will + // automatically retry to fetch it in this case. This is only relevant when running a + // torrent-less download, with the metadata extension provided by libtorrent. + struct TORRENT_EXPORT metadata_failed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT metadata_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& ec); + + TORRENT_DEFINE_ALERT(metadata_failed_alert, 44) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // indicates what failed when parsing the metadata. This error is + // what's returned from lazy_bdecode(). + error_code const error; + }; + + // This alert is generated when the metadata has been completely received and the torrent + // can start downloading. It is not generated on torrents that are started with metadata, but + // only those that needs to download it from peers (when utilizing the libtorrent extension). + // + // There are no additional data members in this alert. + // + // Typically, when receiving this alert, you would want to save the torrent file in order + // to load it back up again when the session is restarted. Here's an example snippet of + // code to do that: + // + // .. code:: c++ + // + // torrent_handle h = alert->handle; + // std::shared_ptr ti = h.torrent_file(); + // create_torrent ct(*ti); + // entry te = ct.generate(); + // std::vector buffer; + // bencode(std::back_inserter(buffer), te); + // FILE* f = fopen((to_hex(ti->info_hashes().get_best().to_string()) + ".torrent").c_str(), "wb+"); + // if (f) { + // fwrite(&buffer[0], 1, buffer.size(), f); + // fclose(f); + // } + // + struct TORRENT_EXPORT metadata_received_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT metadata_received_alert(aux::stack_allocator& alloc + , torrent_handle const& h); + + TORRENT_DEFINE_ALERT(metadata_received_alert, 45) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + }; + + // This alert is posted when there is an error on a UDP socket. The + // UDP sockets are used for all uTP, DHT and UDP tracker traffic. They are + // global to the session. + struct TORRENT_EXPORT udp_error_alert final : alert + { + // internal + TORRENT_UNEXPORT udp_error_alert( + aux::stack_allocator& alloc + , udp::endpoint const& ep + , operation_t op + , error_code const& ec); + + TORRENT_DEFINE_ALERT(udp_error_alert, 46) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // the source address associated with the error (if any) + aux::noexcept_movable endpoint; + + // the operation that failed + operation_t operation; + + // the error code describing the error + error_code const error; + }; + + // Whenever libtorrent learns about the machines external IP, this alert is + // generated. The external IP address can be acquired from the tracker (if it + // supports that) or from peers that supports the extension protocol. + // The address can be accessed through the ``external_address`` member. + struct TORRENT_EXPORT external_ip_alert final : alert + { + // internal + TORRENT_UNEXPORT external_ip_alert(aux::stack_allocator& alloc, address const& ip); + + TORRENT_DEFINE_ALERT(external_ip_alert, 47) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // the IP address that is believed to be our external IP + aux::noexcept_movable

    external_address; + }; + + // This alert is generated when none of the ports, given in the port range, to + // session can be opened for listening. The ``listen_interface`` member is the + // interface that failed, ``error`` is the error code describing the failure. + // + // In the case an endpoint was created before generating the alert, it is + // represented by ``address`` and ``port``. The combinations of socket type + // and operation in which such address and port are not valid are: + // accept - i2p + // accept - socks5 + // enum_if - tcp + // + // libtorrent may sometimes try to listen on port 0, if all other ports failed. + // Port 0 asks the operating system to pick a port that's free). If that fails + // you may see a listen_failed_alert with port 0 even if you didn't ask to + // listen on it. + struct TORRENT_EXPORT listen_failed_alert final : alert + { +#if TORRENT_ABI_VERSION == 1 + enum socket_type_t : std::uint8_t + { + tcp TORRENT_DEPRECATED_ENUM, + tcp_ssl TORRENT_DEPRECATED_ENUM, + udp TORRENT_DEPRECATED_ENUM, + i2p TORRENT_DEPRECATED_ENUM, + socks5 TORRENT_DEPRECATED_ENUM, + utp_ssl TORRENT_DEPRECATED_ENUM + }; +#endif + + // internal + TORRENT_UNEXPORT listen_failed_alert(aux::stack_allocator& alloc, string_view iface + , lt::address const& listen_addr, int listen_port + , operation_t op, error_code const& ec, lt::socket_type_t t); + TORRENT_UNEXPORT listen_failed_alert(aux::stack_allocator& alloc, string_view iface + , tcp::endpoint const& ep, operation_t op, error_code const& ec + , lt::socket_type_t t); + TORRENT_UNEXPORT listen_failed_alert(aux::stack_allocator& alloc, string_view iface + , udp::endpoint const& ep, operation_t op, error_code const& ec + , lt::socket_type_t t); + TORRENT_UNEXPORT listen_failed_alert(aux::stack_allocator& alloc, string_view iface + , operation_t op, error_code const& ec, lt::socket_type_t t); + + TORRENT_DEFINE_ALERT_PRIO(listen_failed_alert, 48, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::status | alert_category::error; + std::string message() const override; + + // the network device libtorrent attempted to listen on, or the IP address + char const* listen_interface() const; + + // the error the system returned + error_code const error; + + // the underlying operation that failed + operation_t op; + + // the type of listen socket this alert refers to. + lt::socket_type_t const socket_type; + + // the address libtorrent attempted to listen on + // see alert documentation for validity of this value + aux::noexcept_movable address; + + // the port libtorrent attempted to listen on + // see alert documentation for validity of this value + int const port; + + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_interface_idx; +#if TORRENT_ABI_VERSION == 1 + public: + enum TORRENT_DEPRECATED_ENUM op_t + { + parse_addr TORRENT_DEPRECATED_ENUM, + open TORRENT_DEPRECATED_ENUM, + bind TORRENT_DEPRECATED_ENUM, + listen TORRENT_DEPRECATED_ENUM, + get_socket_name TORRENT_DEPRECATED_ENUM, + accept TORRENT_DEPRECATED_ENUM, + enum_if TORRENT_DEPRECATED_ENUM, + bind_to_device TORRENT_DEPRECATED_ENUM + }; + + // the specific low level operation that failed. See op_t. + TORRENT_DEPRECATED int const operation; + + // the address and port libtorrent attempted to listen on + TORRENT_DEPRECATED aux::noexcept_movable endpoint; + + // the type of listen socket this alert refers to. + TORRENT_DEPRECATED socket_type_t sock_type; +#endif + }; + + // This alert is posted when the listen port succeeds to be opened on a + // particular interface. ``address`` and ``port`` is the endpoint that + // successfully was opened for listening. + struct TORRENT_EXPORT listen_succeeded_alert final : alert + { +#if TORRENT_ABI_VERSION == 1 + enum socket_type_t : std::uint8_t + { + tcp TORRENT_DEPRECATED_ENUM, + tcp_ssl TORRENT_DEPRECATED_ENUM, + udp TORRENT_DEPRECATED_ENUM, + i2p TORRENT_DEPRECATED_ENUM, + socks5 TORRENT_DEPRECATED_ENUM, + utp_ssl TORRENT_DEPRECATED_ENUM + }; +#endif + + // internal + TORRENT_UNEXPORT listen_succeeded_alert(aux::stack_allocator& alloc + , lt::address const& listen_addr + , int listen_port + , lt::socket_type_t t); + TORRENT_UNEXPORT listen_succeeded_alert(aux::stack_allocator& alloc + , tcp::endpoint const& ep + , lt::socket_type_t t); + TORRENT_UNEXPORT listen_succeeded_alert(aux::stack_allocator& alloc + , udp::endpoint const& ep + , lt::socket_type_t t); + + TORRENT_DEFINE_ALERT_PRIO(listen_succeeded_alert, 49, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // the address libtorrent ended up listening on. This address + // refers to the local interface. + aux::noexcept_movable address; + + // the port libtorrent ended up listening on. + int const port; + + // the type of listen socket this alert refers to. + lt::socket_type_t const socket_type; + +#if TORRENT_ABI_VERSION == 1 + // the endpoint libtorrent ended up listening on. The address + // refers to the local interface and the port is the listen port. + TORRENT_DEPRECATED aux::noexcept_movable endpoint; + + // the type of listen socket this alert refers to. + TORRENT_DEPRECATED socket_type_t sock_type; +#endif + }; + + // This alert is generated when a NAT router was successfully found but some + // part of the port mapping request failed. It contains a text message that + // may help the user figure out what is wrong. This alert is not generated in + // case it appears the client is not running on a NAT:ed network or if it + // appears there is no NAT router that can be remote controlled to add port + // mappings. + struct TORRENT_EXPORT portmap_error_alert final : alert + { + // internal + TORRENT_UNEXPORT portmap_error_alert(aux::stack_allocator& alloc, port_mapping_t i + , portmap_transport t, error_code const& e, address const& local); + + TORRENT_DEFINE_ALERT(portmap_error_alert, 50) + + static constexpr alert_category_t static_category = alert_category::port_mapping + | alert_category::error; + std::string message() const override; + + // refers to the mapping index of the port map that failed, i.e. + // the index returned from add_mapping(). + port_mapping_t const mapping; + + // UPnP or NAT-PMP + portmap_transport map_transport; + + // the local network the port mapper is running on + aux::noexcept_movable
    local_address; + + // tells you what failed. + error_code const error; +#if TORRENT_ABI_VERSION == 1 + // is 0 for NAT-PMP and 1 for UPnP. + TORRENT_DEPRECATED int const map_type; + + TORRENT_DEPRECATED std::string msg; +#endif + }; + + // This alert is generated when a NAT router was successfully found and + // a port was successfully mapped on it. On a NAT:ed network with a NAT-PMP + // capable router, this is typically generated once when mapping the TCP + // port and, if DHT is enabled, when the UDP port is mapped. + struct TORRENT_EXPORT portmap_alert final : alert + { + // internal + TORRENT_UNEXPORT portmap_alert(aux::stack_allocator& alloc, port_mapping_t i, int port + , portmap_transport t, portmap_protocol protocol, address const& local); + + TORRENT_DEFINE_ALERT(portmap_alert, 51) + + static constexpr alert_category_t static_category = alert_category::port_mapping; + std::string message() const override; + + // refers to the mapping index of the port map that failed, i.e. + // the index returned from add_mapping(). + port_mapping_t const mapping; + + // the external port allocated for the mapping. + int const external_port; + + portmap_protocol const map_protocol; + + portmap_transport const map_transport; + + // the local network the port mapper is running on + aux::noexcept_movable
    local_address; + +#if TORRENT_ABI_VERSION == 1 + enum TORRENT_DEPRECATED_ENUM protocol_t + { + tcp, + udp + }; + + // the protocol this mapping was for. one of protocol_t enums + TORRENT_DEPRECATED int const protocol; + + // 0 for NAT-PMP and 1 for UPnP. + TORRENT_DEPRECATED int const map_type; +#endif + }; + + // This alert is generated to log informational events related to either + // UPnP or NAT-PMP. They contain a log line and the type (0 = NAT-PMP + // and 1 = UPnP). Displaying these messages to an end user is only useful + // for debugging the UPnP or NAT-PMP implementation. This alert is only + // posted if the alert_category::port_mapping_log flag is enabled in + // the alert mask. + struct TORRENT_EXPORT portmap_log_alert final : alert + { + // internal + TORRENT_UNEXPORT portmap_log_alert(aux::stack_allocator& alloc, portmap_transport t + , const char* m, address const& local); + + TORRENT_DEFINE_ALERT(portmap_log_alert, 52) + + static constexpr alert_category_t static_category = alert_category::port_mapping_log; + std::string message() const override; + + portmap_transport const map_transport; + + // the local network the port mapper is running on + aux::noexcept_movable
    local_address; + + // the message associated with this log line + char const* log_message() const; + + private: + + std::reference_wrapper m_alloc; + + aux::allocation_slot m_log_idx; +#if TORRENT_ABI_VERSION == 1 + public: + TORRENT_DEPRECATED int const map_type; + TORRENT_DEPRECATED std::string msg; +#endif + + }; + + // This alert is generated when a fast resume file has been passed to + // add_torrent() but the files on disk did not match the fast resume file. + // The error_code explains the reason why the resume file was rejected. + struct TORRENT_EXPORT fastresume_rejected_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT fastresume_rejected_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& ec, string_view file + , operation_t op); + + TORRENT_DEFINE_ALERT_PRIO(fastresume_rejected_alert, 53, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::status + | alert_category::error; + std::string message() const override; + + error_code error; + + // If the error happened to a specific file, this returns the path to it. + char const* file_path() const; + + // the underlying operation that failed + operation_t op; + + private: + aux::allocation_slot m_path_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // If the error happened in a disk operation. a 0-terminated string of + // the name of that operation. ``operation`` is nullptr otherwise. + TORRENT_DEPRECATED char const* operation; + + // If the error happened to a specific file, ``file`` is the path to it. + TORRENT_DEPRECATED std::string file; + TORRENT_DEPRECATED std::string msg; +#endif + }; + + // This alert is posted when an incoming peer connection, or a peer that's about to be added + // to our peer list, is blocked for some reason. This could be any of: + // + // * the IP filter + // * i2p mixed mode restrictions (a normal peer is not allowed on an i2p swarm) + // * the port filter + // * the peer has a low port and ``no_connect_privileged_ports`` is enabled + // * the protocol of the peer is blocked (uTP/TCP blocking) + struct TORRENT_EXPORT peer_blocked_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT peer_blocked_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep, int r); + + TORRENT_DEFINE_ALERT(peer_blocked_alert, 54) + + static constexpr alert_category_t static_category = alert_category::ip_block; + std::string message() const override; + + enum reason_t + { + ip_filter, + port_filter, + i2p_mixed, + privileged_ports, + utp_disabled, + tcp_disabled, + invalid_local_interface, + ssrf_mitigation + }; + + // the reason for the peer being blocked. Is one of the values from the + // reason_t enum. + int const reason; + }; + + // This alert is generated when a DHT node announces to an info-hash on our + // DHT node. It belongs to the ``alert_category::dht`` category. + struct TORRENT_EXPORT dht_announce_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_announce_alert(aux::stack_allocator& alloc, address const& i, int p + , sha1_hash const& ih); + + TORRENT_DEFINE_ALERT(dht_announce_alert, 55) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + aux::noexcept_movable
    ip; + int port; + sha1_hash info_hash; + }; + + // This alert is generated when a DHT node sends a ``get_peers`` message to + // our DHT node. It belongs to the ``alert_category::dht`` category. + struct TORRENT_EXPORT dht_get_peers_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_get_peers_alert(aux::stack_allocator& alloc, sha1_hash const& ih); + + TORRENT_DEFINE_ALERT(dht_get_peers_alert, 56) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + sha1_hash info_hash; + }; + +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // This alert is posted approximately once every second, and it contains + // byte counters of most statistics that's tracked for torrents. Each active + // torrent posts these alerts regularly. + // This alert has been superseded by calling ``post_torrent_updates()`` + // regularly on the session object. This alert will be removed + struct TORRENT_DEPRECATED_EXPORT stats_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT stats_alert(aux::stack_allocator& alloc, torrent_handle const& h, int interval + , stat const& s); + + TORRENT_DEFINE_ALERT(stats_alert, 57) + + static constexpr alert_category_t static_category = alert_category::stats; + std::string message() const override; + + enum stats_channel + { + upload_payload, + upload_protocol, + download_payload, + download_protocol, + upload_ip_protocol, +#if TORRENT_ABI_VERSION == 1 + upload_dht_protocol TORRENT_DEPRECATED_ENUM, + upload_tracker_protocol TORRENT_DEPRECATED_ENUM, +#else + deprecated1, + deprecated2, +#endif + download_ip_protocol, +#if TORRENT_ABI_VERSION == 1 + download_dht_protocol TORRENT_DEPRECATED_ENUM, + download_tracker_protocol TORRENT_DEPRECATED_ENUM, +#else + deprecated3, + deprecated4, +#endif + num_channels + }; + + // an array of samples. The enum describes what each sample is a + // measurement of. All of these are raw, and not smoothing is performed. + std::array const transferred; + + // the number of milliseconds during which these stats were collected. + // This is typically just above 1000, but if CPU is limited, it may be + // higher than that. + int const interval; + }; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif // TORRENT_ABI_VERSION + + // This alert is posted when the disk cache has been flushed for a specific + // torrent as a result of a call to torrent_handle::flush_cache(). This + // alert belongs to the ``alert_category::storage`` category, which must be + // enabled to let this alert through. The alert is also posted when removing + // a torrent from the session, once the outstanding cache flush is complete + // and the torrent does no longer have any files open. + struct TORRENT_EXPORT cache_flushed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT cache_flushed_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + TORRENT_DEFINE_ALERT_PRIO(cache_flushed_alert, 58, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::storage; + }; + +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // This alert is posted when a bittorrent feature is blocked because of the + // anonymous mode. For instance, if the tracker proxy is not set up, no + // trackers will be used, because trackers can only be used through proxies + // when in anonymous mode. + struct TORRENT_DEPRECATED_EXPORT anonymous_mode_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT anonymous_mode_alert(aux::stack_allocator& alloc, torrent_handle const& h + , int k, string_view s); + + TORRENT_DEFINE_ALERT(anonymous_mode_alert, 59) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + enum kind_t + { + // means that there's no proxy set up for tracker + // communication and the tracker will not be contacted. + // The tracker which this failed for is specified in the ``str`` member. + tracker_not_anonymous = 0 + }; + + // specifies what error this is, see kind_t. + int kind; + std::string str; + }; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif // TORRENT_ABI_VERSION + + // This alert is generated when we receive a local service discovery message + // from a peer for a torrent we're currently participating in. + struct TORRENT_EXPORT lsd_peer_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT lsd_peer_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& i); + + TORRENT_DEFINE_ALERT(lsd_peer_alert, 60) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + }; + + // This alert is posted whenever a tracker responds with a ``trackerid``. + // The tracker ID is like a cookie. libtorrent will store the tracker ID + // for this tracker and repeat it in subsequent announces. + struct TORRENT_EXPORT trackerid_alert final : tracker_alert + { + // internal + TORRENT_UNEXPORT trackerid_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep , string_view u, const std::string& id); + + TORRENT_DEFINE_ALERT(trackerid_alert, 61) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // The tracker ID returned by the tracker + char const* tracker_id() const; + + private: + aux::allocation_slot m_tracker_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // The tracker ID returned by the tracker + TORRENT_DEPRECATED std::string trackerid; +#endif + }; + + // This alert is posted when the initial DHT bootstrap is done. + struct TORRENT_EXPORT dht_bootstrap_alert final : alert + { + // internal + explicit TORRENT_UNEXPORT dht_bootstrap_alert(aux::stack_allocator& alloc); + + TORRENT_DEFINE_ALERT(dht_bootstrap_alert, 62) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + }; + + // This is posted whenever a torrent is transitioned into the error state. + // If the error code is duplicate_torrent (error_code_enum) error, it suggests two magnet + // links ended up resolving to the same hybrid torrent. For more details, + // see BitTorrent-v2-torrents_. + struct TORRENT_EXPORT torrent_error_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_error_alert(aux::stack_allocator& alloc, torrent_handle const& h + , error_code const& e, string_view f); + + TORRENT_DEFINE_ALERT_PRIO(torrent_error_alert, 64, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::error | alert_category::status; + std::string message() const override; + + // specifies which error the torrent encountered. + error_code const error; + + // the filename (or object) the error occurred on. + char const* filename() const; + + private: + aux::allocation_slot m_file_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // the filename (or object) the error occurred on. + TORRENT_DEPRECATED std::string error_file; +#endif + + }; + + // This is always posted for SSL torrents. This is a reminder to the client that + // the torrent won't work unless torrent_handle::set_ssl_certificate() is called with + // a valid certificate. Valid certificates MUST be signed by the SSL certificate + // in the .torrent file. + struct TORRENT_EXPORT torrent_need_cert_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_need_cert_alert(aux::stack_allocator& alloc + , torrent_handle const& h); + + TORRENT_DEFINE_ALERT_PRIO(torrent_need_cert_alert, 65, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED error_code const error; +#endif + }; + + // The incoming connection alert is posted every time we successfully accept + // an incoming connection, through any mean. The most straight-forward ways + // of accepting incoming connections are through the TCP listen socket and + // the UDP listen socket for uTP sockets. However, connections may also be + // accepted through a Socks5 or i2p listen socket, or via an SSL listen + // socket. + struct TORRENT_EXPORT incoming_connection_alert final : alert + { + // internal + TORRENT_UNEXPORT incoming_connection_alert(aux::stack_allocator& alloc + , socket_type_t t, tcp::endpoint const& i); + + TORRENT_DEFINE_ALERT(incoming_connection_alert, 66) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + + // tells you what kind of socket the connection was accepted + socket_type_t socket_type; + + // is the IP address and port the connection came from. + aux::noexcept_movable endpoint; + +#if TORRENT_ABI_VERSION == 1 + // is the IP address and port the connection came from. + TORRENT_DEPRECATED aux::noexcept_movable ip; +#endif + }; + + // This alert is always posted when a torrent was attempted to be added + // and contains the return status of the add operation. The torrent handle of the new + // torrent can be found as the ``handle`` member in the base class. If adding + // the torrent failed, ``error`` contains the error code. + struct TORRENT_EXPORT add_torrent_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT add_torrent_alert(aux::stack_allocator& alloc, torrent_handle const& h + , add_torrent_params p, error_code const& ec); + + TORRENT_DEFINE_ALERT_PRIO(add_torrent_alert, 67, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // This contains copies of the most important fields from the original + // add_torrent_params object, passed to add_torrent() or + // async_add_torrent(). Specifically, these fields are copied: + // + // * version + // * ti + // * name + // * save_path + // * userdata + // * tracker_id + // * flags + // * info_hash + // + // the info_hash field will be updated with the info-hash of the torrent + // specified by ``ti``. + add_torrent_params params; + + // set to the error, if one occurred while adding the torrent. + error_code error; + }; + + // This alert is only posted when requested by the user, by calling + // session::post_torrent_updates() on the session. It contains the torrent + // status of all torrents that changed since last time this message was + // posted. Its category is ``alert_category::status``, but it's not subject to + // filtering, since it's only manually posted anyway. + struct TORRENT_EXPORT state_update_alert final : alert + { + // internal + TORRENT_UNEXPORT state_update_alert(aux::stack_allocator& alloc + , std::vector st); + + TORRENT_DEFINE_ALERT_PRIO(state_update_alert, 68, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // contains the torrent status of all torrents that changed since last + // time this message was posted. Note that you can map a torrent status + // to a specific torrent via its ``handle`` member. The receiving end is + // suggested to have all torrents sorted by the torrent_handle or hashed + // by it, for efficient updates. + std::vector status; + }; + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + struct TORRENT_DEPRECATED_EXPORT mmap_cache_alert final : alert + { + mmap_cache_alert(aux::stack_allocator& alloc + , error_code const& ec); + TORRENT_DEFINE_ALERT(mmap_cache_alert, 69) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + error_code const error; + }; +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // TORRENT_ABI_VERSION + + // The session_stats_alert is posted when the user requests session statistics by + // calling post_session_stats() on the session object. This alert does not + // have a category, since it's only posted in response to an API call. It + // is not subject to the alert_mask filter. + // + // the ``message()`` member function returns a string representation of the values that + // properly match the line returned in ``session_stats_header_alert::message()``. + // + // this specific output is parsed by tools/parse_session_stats.py + // if this is changed, that parser should also be changed + struct TORRENT_EXPORT session_stats_alert final : alert + { + // internal + TORRENT_UNEXPORT session_stats_alert(aux::stack_allocator& alloc, counters const& cnt); + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" +#endif + + TORRENT_DEFINE_ALERT_PRIO(session_stats_alert, 70, alert_priority::critical) + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + + static constexpr alert_category_t static_category = {}; + std::string message() const override; + + // An array are a mix of *counters* and *gauges*, which meanings can be + // queries via the session_stats_metrics() function on the session. The + // mapping from a specific metric to an index into this array is constant + // for a specific version of libtorrent, but may differ for other + // versions. The intended usage is to request the mapping, i.e. call + // session_stats_metrics(), once on startup, and then use that mapping to + // interpret these values throughout the process' runtime. + // + // For more information, see the session-statistics_ section. + span counters() const; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED std::array const values; +#else + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_counters_idx; +#endif + }; + + // posted when something fails in the DHT. This is not necessarily a fatal + // error, but it could prevent proper operation + struct TORRENT_EXPORT dht_error_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_error_alert(aux::stack_allocator& alloc, operation_t op + , error_code const& ec); + + TORRENT_DEFINE_ALERT(dht_error_alert, 73) + + static constexpr alert_category_t static_category = alert_category::error | alert_category::dht; + std::string message() const override; + + // the error code + error_code error; + + // the operation that failed + operation_t op; + +#if TORRENT_ABI_VERSION == 1 + enum op_t + { + unknown TORRENT_DEPRECATED_ENUM, + hostname_lookup TORRENT_DEPRECATED_ENUM + }; + + // the operation that failed + TORRENT_DEPRECATED op_t const operation; +#endif + }; + + // this alert is posted as a response to a call to session::get_item(), + // specifically the overload for looking up immutable items in the DHT. + struct TORRENT_EXPORT dht_immutable_item_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_immutable_item_alert(aux::stack_allocator& alloc, sha1_hash const& t + , entry i); + + TORRENT_DEFINE_ALERT_PRIO(dht_immutable_item_alert, 74, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::dht; + + std::string message() const override; + + // the target hash of the immutable item. This must + // match the SHA-1 hash of the bencoded form of ``item``. + sha1_hash target; + + // the data for this item + entry item; + }; + + // this alert is posted as a response to a call to session::get_item(), + // specifically the overload for looking up mutable items in the DHT. + struct TORRENT_EXPORT dht_mutable_item_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_mutable_item_alert(aux::stack_allocator& alloc + , std::array const& k, std::array const& sig + , std::int64_t sequence, string_view s, entry i, bool a); + + TORRENT_DEFINE_ALERT_PRIO(dht_mutable_item_alert, 75, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + // the public key that was looked up + std::array key; + + // the signature of the data. This is not the signature of the + // plain encoded form of the item, but it includes the sequence number + // and possibly the hash as well. See the dht_store document for more + // information. This is primarily useful for echoing back in a store + // request. + std::array signature; + + // the sequence number of this item + std::int64_t seq; + + // the salt, if any, used to lookup and store this item. If no + // salt was used, this is an empty string + std::string salt; + + // the data for this item + entry item; + + // the last response for mutable data is authoritative. + bool authoritative; + }; + + // this is posted when a DHT put operation completes. This is useful if the + // client is waiting for a put to complete before shutting down for instance. + struct TORRENT_EXPORT dht_put_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_put_alert(aux::stack_allocator& alloc, sha1_hash const& t, int n); + TORRENT_UNEXPORT dht_put_alert(aux::stack_allocator& alloc, std::array const& key + , std::array const& sig + , std::string s + , std::int64_t sequence_number + , int n); + + TORRENT_DEFINE_ALERT(dht_put_alert, 76) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + // the target hash the item was stored under if this was an *immutable* + // item. + sha1_hash target; + + // if a mutable item was stored, these are the public key, signature, + // salt and sequence number the item was stored under. + std::array public_key; + std::array signature; + std::string salt; + std::int64_t seq; + + // DHT put operation usually writes item to k nodes, maybe the node + // is stale so no response, or the node doesn't support 'put', or the + // token for write is out of date, etc. num_success is the number of + // successful responses we got from the puts. + int num_success; + }; + + // this alert is used to report errors in the i2p SAM connection + struct TORRENT_EXPORT i2p_alert final : alert + { + // internal + TORRENT_UNEXPORT i2p_alert(aux::stack_allocator& alloc, error_code const& ec); + + TORRENT_DEFINE_ALERT(i2p_alert, 77) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // the error that occurred in the i2p SAM connection + error_code error; + }; + + // This alert is generated when we send a get_peers request + // It belongs to the ``alert_category::dht`` category. + struct TORRENT_EXPORT dht_outgoing_get_peers_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_outgoing_get_peers_alert(aux::stack_allocator& alloc + , sha1_hash const& ih, sha1_hash const& obfih + , udp::endpoint ep); + + TORRENT_DEFINE_ALERT(dht_outgoing_get_peers_alert, 78) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + // the info_hash of the torrent we're looking for peers for. + sha1_hash info_hash; + + // if this was an obfuscated lookup, this is the info-hash target + // actually sent to the node. + sha1_hash obfuscated_info_hash; + + // the endpoint we're sending this query to + aux::noexcept_movable endpoint; + +#if TORRENT_ABI_VERSION == 1 + // the endpoint we're sending this query to + TORRENT_DEPRECATED aux::noexcept_movable ip; +#endif + }; + + // This alert is posted by some session wide event. Its main purpose is + // trouble shooting and debugging. It's not enabled by the default alert + // mask and is enabled by the ``alert_category::session_log`` bit. + // Furthermore, it's by default disabled as a build configuration. + struct TORRENT_EXPORT log_alert final : alert + { + // internal + TORRENT_UNEXPORT log_alert(aux::stack_allocator& alloc, char const* log); + TORRENT_UNEXPORT log_alert(aux::stack_allocator& alloc, char const* fmt, va_list v); + + TORRENT_DEFINE_ALERT(log_alert, 79) + + static constexpr alert_category_t static_category = alert_category::session_log; + std::string message() const override; + + // returns the log message + char const* log_message() const; + +#if TORRENT_ABI_VERSION == 1 + // returns the log message + TORRENT_DEPRECATED + char const* msg() const; +#endif + + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_str_idx; + }; + + // This alert is posted by torrent wide events. It's meant to be used for + // trouble shooting and debugging. It's not enabled by the default alert + // mask and is enabled by the ``alert_category::torrent_log`` bit. By + // default it is disabled as a build configuration. + struct TORRENT_EXPORT torrent_log_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_log_alert(aux::stack_allocator& alloc, torrent_handle const& h + , char const* fmt, va_list v); + + TORRENT_DEFINE_ALERT(torrent_log_alert, 80) + + static constexpr alert_category_t static_category = alert_category::torrent_log; + std::string message() const override; + + // returns the log message + char const* log_message() const; + +#if TORRENT_ABI_VERSION == 1 + // returns the log message + TORRENT_DEPRECATED + char const* msg() const; +#endif + + private: + aux::allocation_slot m_str_idx; + }; + + // This alert is posted by events specific to a peer. It's meant to be used + // for trouble shooting and debugging. It's not enabled by the default alert + // mask and is enabled by the ``alert_category::peer_log`` bit. By + // default it is disabled as a build configuration. + struct TORRENT_EXPORT peer_log_alert final : peer_alert + { + // describes whether this log refers to in-flow or out-flow of the + // peer. The exception is ``info`` which is neither incoming or outgoing. + enum direction_t + { + incoming_message, + outgoing_message, + incoming, + outgoing, + info + }; + + // internal + TORRENT_UNEXPORT peer_log_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& i, peer_id const& pi + , peer_log_alert::direction_t dir + , char const* event, char const* fmt, va_list v); + + TORRENT_DEFINE_ALERT(peer_log_alert, 81) + + static constexpr alert_category_t static_category = alert_category::peer_log; + std::string message() const override; + + // string literal indicating the kind of event. For messages, this is the + // message name. + char const* event_type; + + direction_t direction; + + // returns the log message + char const* log_message() const; + +#if TORRENT_ABI_VERSION == 1 + // returns the log message + TORRENT_DEPRECATED + char const* msg() const; +#endif + + private: + aux::allocation_slot m_str_idx; + }; + + // posted if the local service discovery socket fails to start properly. + // it's categorized as ``alert_category::error``. + struct TORRENT_EXPORT lsd_error_alert final : alert + { + // internal + TORRENT_UNEXPORT lsd_error_alert(aux::stack_allocator& alloc, error_code const& ec + , address const& local); + + TORRENT_DEFINE_ALERT(lsd_error_alert, 82) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // the local network the corresponding local service discovery is running + // on + aux::noexcept_movable
    local_address; + + // The error code + error_code error; + }; + + // holds statistics about a current dht_lookup operation. + // a DHT lookup is the traversal of nodes, looking up a + // set of target nodes in the DHT for retrieving and possibly + // storing information in the DHT + struct TORRENT_EXPORT dht_lookup + { + // string literal indicating which kind of lookup this is + char const* type; + + // the number of outstanding request to individual nodes + // this lookup has right now + int outstanding_requests; + + // the total number of requests that have timed out so far + // for this lookup + int timeouts; + + // the total number of responses we have received for this + // lookup so far for this lookup + int responses; + + // the branch factor for this lookup. This is the number of + // nodes we keep outstanding requests to in parallel by default. + // when nodes time out we may increase this. + int branch_factor; + + // the number of nodes left that could be queries for this + // lookup. Many of these are likely to be part of the trail + // while performing the lookup and would never end up actually + // being queried. + int nodes_left; + + // the number of seconds ago the + // last message was sent that's still + // outstanding + int last_sent; + + // the number of outstanding requests + // that have exceeded the short timeout + // and are considered timed out in the + // sense that they increased the branch + // factor + int first_timeout; + + // the node-id or info-hash target for this lookup + sha1_hash target; + }; + + // contains current DHT state. Posted in response to session::post_dht_stats(). + struct TORRENT_EXPORT dht_stats_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_stats_alert(aux::stack_allocator& alloc + , std::vector table + , std::vector requests + , sha1_hash id, udp::endpoint ep); + + TORRENT_DEFINE_ALERT(dht_stats_alert, 83) + + static constexpr alert_category_t static_category = {}; + std::string message() const override; + + // a vector of the currently running DHT lookups. + std::vector active_requests; + + // contains information about every bucket in the DHT routing + // table. + std::vector routing_table; + + // the node ID of the DHT node instance + sha1_hash nid; + + // the local socket this DHT node is running on + aux::noexcept_movable local_endpoint; + }; + + // posted every time an incoming request from a peer is accepted and queued + // up for being serviced. This alert is only posted if + // the alert_category::incoming_request flag is enabled in the alert + // mask. + struct TORRENT_EXPORT incoming_request_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT incoming_request_alert(aux::stack_allocator& alloc + , peer_request r, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id); + + static constexpr alert_category_t static_category = alert_category::incoming_request; + TORRENT_DEFINE_ALERT(incoming_request_alert, 84) + + std::string message() const override; + + // the request this peer sent to us + peer_request req; + }; + + // debug logging of the DHT when alert_category::dht_log is set in the alert + // mask. + struct TORRENT_EXPORT dht_log_alert final : alert + { + enum dht_module_t + { + tracker, + node, + routing_table, + rpc_manager, + traversal + }; + + // internal + TORRENT_UNEXPORT dht_log_alert(aux::stack_allocator& alloc + , dht_module_t m, char const* fmt, va_list v); + + static constexpr alert_category_t static_category = alert_category::dht_log; + TORRENT_DEFINE_ALERT(dht_log_alert, 85) + + std::string message() const override; + + // the log message + char const* log_message() const; + + // the module, or part, of the DHT that produced this log message. + dht_module_t module; + + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_msg_idx; + }; + + // This alert is posted every time a DHT message is sent or received. It is + // only posted if the ``alert_category::dht_log`` alert category is + // enabled. It contains a verbatim copy of the message. + struct TORRENT_EXPORT dht_pkt_alert final : alert + { + enum direction_t + { incoming, outgoing }; + + // internal + TORRENT_UNEXPORT dht_pkt_alert(aux::stack_allocator& alloc, span buf + , dht_pkt_alert::direction_t d, udp::endpoint const& ep); + + static constexpr alert_category_t static_category = alert_category::dht_log; + TORRENT_DEFINE_ALERT(dht_pkt_alert, 86) + + std::string message() const override; + + // returns a pointer to the packet buffer and size of the packet, + // respectively. This buffer is only valid for as long as the alert itself + // is valid, which is owned by libtorrent and reclaimed whenever + // pop_alerts() is called on the session. + span pkt_buf() const; + + // whether this is an incoming or outgoing packet. + direction_t direction; + + // the DHT node we received this packet from, or sent this packet to + // (depending on ``direction``). + aux::noexcept_movable node; + + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_msg_idx; + int const m_size; +#if TORRENT_ABI_VERSION == 1 + public: + TORRENT_DEPRECATED direction_t dir; +#endif + + }; + + // Posted when we receive a response to a DHT get_peers request. + struct TORRENT_EXPORT dht_get_peers_reply_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_get_peers_reply_alert(aux::stack_allocator& alloc + , sha1_hash const& ih + , std::vector const& peers); + + static constexpr alert_category_t static_category = alert_category::dht_operation; + TORRENT_DEFINE_ALERT(dht_get_peers_reply_alert, 87) + + std::string message() const override; + + sha1_hash info_hash; + + int num_peers() const; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + void peers(std::vector& v) const; +#endif + std::vector peers() const; + + private: + std::reference_wrapper m_alloc; + int m_v4_num_peers = 0; + int m_v6_num_peers = 0; + aux::allocation_slot m_v4_peers_idx; + aux::allocation_slot m_v6_peers_idx; + }; + + // This is posted exactly once for every call to session_handle::dht_direct_request. + // If the request failed, response() will return a default constructed bdecode_node. + struct TORRENT_EXPORT dht_direct_response_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_direct_response_alert(aux::stack_allocator& alloc, client_data_t userdata + , udp::endpoint const& addr, bdecode_node const& response); + + // internal + // for when there was a timeout so we don't have a response + TORRENT_UNEXPORT dht_direct_response_alert(aux::stack_allocator& alloc, client_data_t userdata + , udp::endpoint const& addr); + + TORRENT_DEFINE_ALERT_PRIO(dht_direct_response_alert, 88, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + client_data_t userdata; + aux::noexcept_movable endpoint; + + bdecode_node response() const; + + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_response_idx; + int const m_response_size; +#if TORRENT_ABI_VERSION == 1 + public: + TORRENT_DEPRECATED aux::noexcept_movable addr; +#endif + }; + + // hidden + using picker_flags_t = flags::bitfield_flag; + + // this is posted when one or more blocks are picked by the piece picker, + // assuming the verbose piece picker logging is enabled (see + // alert_category::picker_log). + struct TORRENT_EXPORT picker_log_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT picker_log_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep, peer_id const& peer_id, picker_flags_t flags + , span blocks); + + TORRENT_DEFINE_ALERT(picker_log_alert, 89) + + static constexpr alert_category_t static_category = alert_category::picker_log; + std::string message() const override; + + static constexpr picker_flags_t partial_ratio = 0_bit; + static constexpr picker_flags_t prioritize_partials = 1_bit; + static constexpr picker_flags_t rarest_first_partials = 2_bit; + static constexpr picker_flags_t rarest_first = 3_bit; + static constexpr picker_flags_t reverse_rarest_first = 4_bit; + static constexpr picker_flags_t suggested_pieces = 5_bit; + static constexpr picker_flags_t prio_sequential_pieces = 6_bit; + static constexpr picker_flags_t sequential_pieces = 7_bit; + static constexpr picker_flags_t reverse_pieces = 8_bit; + static constexpr picker_flags_t time_critical = 9_bit; + static constexpr picker_flags_t random_pieces = 10_bit; + static constexpr picker_flags_t prefer_contiguous = 11_bit; + static constexpr picker_flags_t reverse_sequential = 12_bit; + static constexpr picker_flags_t backup1 = 13_bit; + static constexpr picker_flags_t backup2 = 14_bit; + static constexpr picker_flags_t end_game = 15_bit; + static constexpr picker_flags_t extent_affinity = 16_bit; + + // this is a bitmask of which features were enabled for this particular + // pick. The bits are defined in the picker_flags_t enum. + picker_flags_t const picker_flags; + + std::vector blocks() const; + + private: + aux::allocation_slot m_array_idx; + int const m_num_blocks; + }; + + // this alert is posted when the session encounters a serious error, + // potentially fatal + struct TORRENT_EXPORT session_error_alert final : alert + { + // internal + TORRENT_UNEXPORT session_error_alert(aux::stack_allocator& alloc, error_code err + , string_view error_str); + + TORRENT_DEFINE_ALERT(session_error_alert, 90) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // The error code, if one is associated with this error + error_code const error; + + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_msg_idx; + }; + + // posted in response to a call to session::dht_live_nodes(). It contains the + // live nodes from the DHT routing table of one of the DHT nodes running + // locally. + struct TORRENT_EXPORT dht_live_nodes_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_live_nodes_alert(aux::stack_allocator& alloc + , sha1_hash const& nid + , std::vector> const& nodes); + + TORRENT_DEFINE_ALERT(dht_live_nodes_alert, 91) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + // the local DHT node's node-ID this routing table belongs to + sha1_hash node_id; + + // the number of nodes in the routing table and the actual nodes. + int num_nodes() const; + std::vector> nodes() const; + + private: + std::reference_wrapper m_alloc; + int m_v4_num_nodes = 0; + int m_v6_num_nodes = 0; + aux::allocation_slot m_v4_nodes_idx; + aux::allocation_slot m_v6_nodes_idx; + }; + + // The session_stats_header alert is posted the first time + // post_session_stats() is called + // + // the ``message()`` member function returns a string representation of the + // header that properly match the stats values string returned in + // ``session_stats_alert::message()``. + // + // this specific output is parsed by tools/parse_session_stats.py + // if this is changed, that parser should also be changed + struct TORRENT_EXPORT session_stats_header_alert final : alert + { + // internal + explicit TORRENT_UNEXPORT session_stats_header_alert(aux::stack_allocator& alloc); + TORRENT_DEFINE_ALERT(session_stats_header_alert, 92) + + static constexpr alert_category_t static_category = {}; + std::string message() const override; + }; + + // posted as a response to a call to session::dht_sample_infohashes() with + // the information from the DHT response message. + struct TORRENT_EXPORT dht_sample_infohashes_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_sample_infohashes_alert(aux::stack_allocator& alloc + , sha1_hash const& nid + , udp::endpoint const& endp + , time_duration interval + , int num + , std::vector const& samples + , std::vector> const& nodes); + + static constexpr alert_category_t static_category = alert_category::dht_operation; + TORRENT_DEFINE_ALERT(dht_sample_infohashes_alert, 93) + + std::string message() const override; + + // id of the node the request was sent to (and this response was received from) + sha1_hash node_id; + + // the node the request was sent to (and this response was received from) + aux::noexcept_movable endpoint; + + // the interval to wait before making another request to this node + time_duration const interval; + + // This field indicates how many info-hash keys are currently in the node's storage. + // If the value is larger than the number of returned samples it indicates that the + // indexer may obtain additional samples after waiting out the interval. + int const num_infohashes; + + // returns the number of info-hashes returned by the node, as well as the + // actual info-hashes. ``num_samples()`` is more efficient than + // ``samples().size()``. + int num_samples() const; + std::vector samples() const; + + // The total number of nodes returned by ``nodes()``. + int num_nodes() const; + + // This is the set of more DHT nodes returned by the request. + // + // The information is included so that indexing nodes can perform a key + // space traversal with a single RPC per node by adjusting the target + // value for each RPC. + std::vector> nodes() const; + + private: + std::reference_wrapper m_alloc; + int const m_num_samples; + aux::allocation_slot m_samples_idx; + int m_v4_num_nodes = 0; + int m_v6_num_nodes = 0; + aux::allocation_slot m_v4_nodes_idx; + aux::allocation_slot m_v6_nodes_idx; + }; + + // This alert is posted when a block intended to be sent to a peer is placed in the + // send buffer. Note that if the connection is closed before the send buffer is sent, + // the alert may be posted without the bytes having been sent to the peer. + // It belongs to the ``alert_category::upload`` category. + struct TORRENT_EXPORT block_uploaded_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT block_uploaded_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(block_uploaded_alert, 94) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + static constexpr alert_category_t static_category = + alert_category::upload + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + std::string message() const override; + + int const block_index; + piece_index_t const piece_index; + }; + + // this alert is posted to indicate to the client that some alerts were + // dropped. Dropped meaning that the alert failed to be delivered to the + // client. The most common cause of such failure is that the internal alert + // queue grew too big (controlled by alert_queue_size). + struct TORRENT_EXPORT alerts_dropped_alert final : alert + { + // internal + explicit TORRENT_UNEXPORT alerts_dropped_alert(aux::stack_allocator& alloc + , std::bitset const&); + TORRENT_DEFINE_ALERT_PRIO(alerts_dropped_alert, 95, alert_priority::meta) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // a bitmask indicating which alerts were dropped. Each bit represents the + // alert type ID, where bit 0 represents whether any alert of type 0 has + // been dropped, and so on. + std::bitset dropped_alerts; + static_assert(num_alert_types <= abi_alert_count, "need to increase bitset. This is an ABI break"); + }; + + // this alert is posted with SOCKS5 related errors, when a SOCKS5 proxy is + // configured. It's enabled with the alert_category::error alert category. + struct TORRENT_EXPORT socks5_alert final : alert + { + // internal + explicit socks5_alert(aux::stack_allocator& alloc + , tcp::endpoint const& ep, operation_t operation, error_code const& ec); + TORRENT_DEFINE_ALERT(socks5_alert, 96) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // the error + error_code error; + + // the operation that failed + operation_t op; + + // the endpoint configured as the proxy + aux::noexcept_movable ip; + }; + + // posted when a prioritize_files() or file_priority() update of the file + // priorities complete, which requires a round-trip to the disk thread. + // + // If the disk operation fails this alert won't be posted, but a + // file_error_alert is posted instead, and the torrent is stopped. + struct TORRENT_EXPORT file_prio_alert final : torrent_alert + { + // internal + explicit file_prio_alert(aux::stack_allocator& alloc, torrent_handle h); + TORRENT_DEFINE_ALERT(file_prio_alert, 97) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + // the error + error_code error; + + // the operation that failed + operation_t op; + }; + +TORRENT_VERSION_NAMESPACE_3_END + + // this alert may be posted when the initial checking of resume data and files + // on disk (just existence, not piece hashes) completes. If a file belonging + // to the torrent is found on disk, but is larger than the file in the + // torrent, that's when this alert is posted. + // the client may want to call truncate_files() in that case, or perhaps + // interpret it as a sign that some other file is in the way, that shouldn't + // be overwritten. + struct TORRENT_EXPORT oversized_file_alert final : torrent_alert + { + // internal + explicit oversized_file_alert(aux::stack_allocator& alloc, torrent_handle h); + TORRENT_DEFINE_ALERT(oversized_file_alert, 98) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + // hidden + file_index_t reserved; + }; + + // this alert is posted when two separate torrents (magnet links) resolve to + // the same torrent, thus causing the same torrent being added twice. In + // that case, both torrents enter an error state with ``duplicate_torrent`` + // as the error code. This alert is posted containing the metadata. For more + // information, see BitTorrent-v2-torrents_. + // The torrent this alert originated from was the one that downloaded the + // + // metadata (i.e. the `handle` member from the torrent_alert base class). + struct TORRENT_EXPORT torrent_conflict_alert final : torrent_alert + { + // internal + explicit torrent_conflict_alert(aux::stack_allocator& alloc, torrent_handle h1 + , torrent_handle h2, std::shared_ptr ti); + TORRENT_DEFINE_ALERT_PRIO(torrent_conflict_alert, 99, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // the handle to the torrent in conflict. The swarm associated with this + // torrent handle did not download the metadata, but the downloaded + // metadata collided with this swarm's info-hash. + torrent_handle conflicting_torrent; + + // the metadata that was received by one of the torrents in conflict. + // One way to resolve the conflict is to remove both failing torrents + // and re-add it using this metadata + std::shared_ptr metadata; + }; + + // posted when torrent_handle::post_peer_info() is called + struct TORRENT_EXPORT peer_info_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT peer_info_alert(aux::stack_allocator& alloc, torrent_handle h + , std::vector p); + TORRENT_DEFINE_ALERT_PRIO(peer_info_alert, 100, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // the list of the currently connected peers + std::vector peer_info; + }; + + // posted when torrent_handle::post_file_progress() is called + struct TORRENT_EXPORT file_progress_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT file_progress_alert(aux::stack_allocator& alloc, torrent_handle h + , aux::vector fp); + TORRENT_DEFINE_ALERT_PRIO(file_progress_alert, 101, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::file_progress; + std::string message() const override; + + // the list of the files in the torrent + aux::vector files; + }; + + // posted when torrent_handle::post_download_queue() is called + struct TORRENT_EXPORT piece_info_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT piece_info_alert(aux::stack_allocator& alloc, torrent_handle h + , std::vector pi, std::vector&& bd); + TORRENT_DEFINE_ALERT_PRIO(piece_info_alert, 102, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::piece_progress; + std::string message() const override; + + // info about pieces being downloaded for the torrent + std::vector piece_info; + + // storage for block_info pointers in partial_piece_info objects + std::vector block_data; + }; + + // posted when torrent_handle::post_piece_availability() is called + struct TORRENT_EXPORT piece_availability_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT piece_availability_alert(aux::stack_allocator& alloc, torrent_handle h + , std::vector pa); + TORRENT_DEFINE_ALERT_PRIO(piece_availability_alert, 103, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // info about pieces being downloaded for the torrent + std::vector piece_availability; + }; + + // posted when torrent_handle::post_trackers() is called + struct TORRENT_EXPORT tracker_list_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT tracker_list_alert(aux::stack_allocator& alloc, torrent_handle h + , std::vector t); + TORRENT_DEFINE_ALERT_PRIO(tracker_list_alert, 104, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // list of trackers and their status for the torrent + std::vector trackers; + }; + + // internal + TORRENT_EXTRA_EXPORT char const* performance_warning_str(performance_alert::performance_warning_t i); + + +#undef TORRENT_DEFINE_ALERT_IMPL +#undef TORRENT_DEFINE_ALERT +#undef TORRENT_DEFINE_ALERT_PRIO +#undef PROGRESS_NOTIFICATION + +} // namespace libtorrent + +#endif diff --git a/docs/include/libtorrent/announce_entry.hpp b/docs/include/libtorrent/announce_entry.hpp new file mode 100644 index 0000000..1f3ce25 --- /dev/null +++ b/docs/include/libtorrent/announce_entry.hpp @@ -0,0 +1,291 @@ +/* + +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2016, 2018, 2020, Alden Torres +Copyright (c) 2017-2018, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ANNOUNCE_ENTRY_HPP_INCLUDED +#define TORRENT_ANNOUNCE_ENTRY_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/array.hpp" +#include "libtorrent/info_hash.hpp" + +#include +#include +#include + +namespace libtorrent { + + struct torrent; + +TORRENT_VERSION_NAMESPACE_2 + + struct TORRENT_EXPORT announce_infohash + { + // internal + TORRENT_UNEXPORT announce_infohash(); + + // if this tracker has returned an error or warning message + // that message is stored here + std::string message; + + // if this tracker failed the last time it was contacted + // this error code specifies what error occurred + error_code last_error; + + // the time of next tracker announce + time_point32 next_announce = (time_point32::min)(); + + // no announces before this time + time_point32 min_announce = (time_point32::min)(); + + // TODO: include the number of peers received from this tracker, at last + // announce + + // these are either -1 or the scrape information this tracker last + // responded with. *incomplete* is the current number of downloaders in + // the swarm, *complete* is the current number of seeds in the swarm and + // *downloaded* is the cumulative number of completed downloads of this + // torrent, since the beginning of time (from this tracker's point of + // view). + + // if this tracker has returned scrape data, these fields are filled in + // with valid numbers. Otherwise they are set to -1. ``incomplete`` counts + // the number of current downloaders. ``complete`` counts the number of + // current peers completed the download, or "seeds". ``downloaded`` is the + // cumulative number of completed downloads. + int scrape_incomplete = -1; + int scrape_complete = -1; + int scrape_downloaded = -1; + + // the number of times in a row we have failed to announce to this + // tracker. + std::uint8_t fails : 7; + + // true while we're waiting for a response from the tracker. + bool updating : 1; + + // set to true when we get a valid response from an announce + // with event=started. If it is set, we won't send start in the subsequent + // announces. + bool start_sent : 1; + + // set to true when we send a event=completed. + bool complete_sent : 1; + + // internal + bool triggered_manually : 1; + +#if TORRENT_ABI_VERSION <= 2 + // reset announce counters and clears the started sent flag. + // The announce_endpoint will look like we've never talked to + // the tracker. + TORRENT_DEPRECATED void reset(); + + // updates the failure counter and time-outs for re-trying. + // This is called when the tracker announce fails. + TORRENT_DEPRECATED void failed(int backoff_ratio, seconds32 retry_interval = seconds32(0)); + + // returns true if we can announce to this tracker now. + // The current time is passed in as ``now``. The ``is_seed`` + // argument is necessary because once we become a seed, we + // need to announce right away, even if the re-announce timer + // hasn't expired yet. + TORRENT_DEPRECATED bool can_announce(time_point now, bool is_seed, std::uint8_t fail_limit) const; + + // returns true if the last time we tried to announce to this + // tracker succeeded, or if we haven't tried yet. + TORRENT_DEPRECATED bool is_working() const { return fails == 0; } +#endif + }; + + // announces are sent to each tracker using every listen socket + // this class holds information about one listen socket for one tracker +#if TORRENT_ABI_VERSION <= 2 + // this is to suppress deprecation warnings from implicit move constructor +#include "libtorrent/aux_/disable_warnings_push.hpp" +#endif + struct TORRENT_EXPORT announce_endpoint + { +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + + announce_endpoint(); + + // the local endpoint of the listen interface associated with this endpoint + tcp::endpoint local_endpoint; + + // torrents can be announced using multiple info hashes + // for different protocol versions + + // info_hashes[0] is the v1 info hash (SHA1) + // info_hashes[1] is the v2 info hash (truncated SHA-256) + aux::array info_hashes; + +#if TORRENT_ABI_VERSION <= 2 + // reset announce counters and clears the started sent flag. + // The announce_endpoint will look like we've never talked to + // the tracker. + TORRENT_DEPRECATED void reset(); + + // deprecated in 2.0, use announce_infohash::can_announce + // returns true if we can announce to this tracker now. + // The current time is passed in as ``now``. The ``is_seed`` + // argument is necessary because once we become a seed, we + // need to announce right away, even if the re-announce timer + // hasn't expired yet. + TORRENT_DEPRECATED bool can_announce(time_point now, bool is_seed, std::uint8_t fail_limit) const; + + // deprecated in 2.0, use announce_infohash::is_working + // returns true if the last time we tried to announce to this + // tracker succeeded, or if we haven't tried yet. + TORRENT_DEPRECATED bool is_working() const; + + // for backwards compatibility + TORRENT_DEPRECATED time_point32 next_announce = (time_point32::min)(); + TORRENT_DEPRECATED time_point32 min_announce = (time_point32::min)(); + TORRENT_DEPRECATED std::string message; + TORRENT_DEPRECATED error_code last_error; + TORRENT_DEPRECATED int scrape_incomplete = -1; + TORRENT_DEPRECATED int scrape_complete = -1; + TORRENT_DEPRECATED int scrape_downloaded = -1; + TORRENT_DEPRECATED std::uint8_t fails : 7; + TORRENT_DEPRECATED bool updating : 1; + TORRENT_DEPRECATED bool start_sent : 1; + TORRENT_DEPRECATED bool complete_sent : 1; +#endif + + // set to false to not announce from this endpoint + bool enabled = true; + }; + + // this class holds information about one bittorrent tracker, as it + // relates to a specific torrent. + struct TORRENT_EXPORT announce_entry + { + // constructs a tracker announce entry with ``u`` as the URL. + explicit announce_entry(string_view u); + announce_entry(); + ~announce_entry(); + announce_entry(announce_entry const&); + announce_entry& operator=(announce_entry const&) &; + + // tracker URL as it appeared in the torrent file + std::string url; + + // the current ``&trackerid=`` argument passed to the tracker. + // this is optional and is normally empty (in which case no + // trackerid is sent). + std::string trackerid; + + // each local listen socket (endpoint) will announce to the tracker. This + // list contains state per endpoint. + std::vector endpoints; + + // the tier this tracker belongs to + std::uint8_t tier = 0; + + // the max number of failures to announce to this tracker in + // a row, before this tracker is not used anymore. 0 means unlimited + std::uint8_t fail_limit = 0; + + // flags for the source bitmask, each indicating where + // we heard about this tracker + enum tracker_source + { + // the tracker was part of the .torrent file + source_torrent = 1, + // the tracker was added programmatically via the add_tracker() function + source_client = 2, + // the tracker was part of a magnet link + source_magnet_link = 4, + // the tracker was received from the swarm via tracker exchange + source_tex = 8 + }; + + // a bitmask specifying which sources we got this tracker from. + std::uint8_t source:4; + + // set to true the first time we receive a valid response + // from this tracker. + bool verified:1; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + // all of these will be set to false or 0 + // use the corresponding members in announce_endpoint + TORRENT_DEPRECATED std::uint8_t fails:7; + TORRENT_DEPRECATED bool send_stats:1; + TORRENT_DEPRECATED bool start_sent:1; + TORRENT_DEPRECATED bool complete_sent:1; + // internal + TORRENT_DEPRECATED bool triggered_manually:1; + TORRENT_DEPRECATED bool updating:1; +#endif + +#if TORRENT_ABI_VERSION <= 2 + // reset announce counters and clears the started sent flag. + // The announce_entry will look like we've never talked to + // the tracker. + TORRENT_DEPRECATED void reset(); + + // trims whitespace characters from the beginning of the URL. + TORRENT_DEPRECATED void trim(); +#endif + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2, use announce_endpoint::can_announce + // returns true if we can announce to this tracker now. + // The current time is passed in as ``now``. The ``is_seed`` + // argument is necessary because once we become a seed, we + // need to announce right away, even if the re-announce timer + // hasn't expired yet. + TORRENT_DEPRECATED bool can_announce(time_point now, bool is_seed) const; + + // deprecated in 1.2, use announce_endpoint::is_working + // returns true if the last time we tried to announce to this + // tracker succeeded, or if we haven't tried yet. + TORRENT_DEPRECATED bool is_working() const; +#endif + }; + +TORRENT_VERSION_NAMESPACE_2_END + +} + +#endif diff --git a/docs/include/libtorrent/assert.hpp b/docs/include/libtorrent/assert.hpp new file mode 100644 index 0000000..8e09de3 --- /dev/null +++ b/docs/include/libtorrent/assert.hpp @@ -0,0 +1,135 @@ +/* + +Copyright (c) 2007-2008, 2010-2011, 2013-2019, 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ASSERT_HPP_INCLUDED +#define TORRENT_ASSERT_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/export.hpp" + +#if TORRENT_USE_ASSERTS \ + || defined TORRENT_ASIO_DEBUGGING \ + || defined TORRENT_PROFILE_CALLS \ + || defined TORRENT_DEBUG_BUFFERS + +#include +namespace libtorrent { +std::string demangle(char const* name); +TORRENT_EXPORT void print_backtrace(char* out, int len, int max_depth = 0, void* ctx = nullptr); +} +#endif + +// this is to disable the warning of conditional expressions +// being constant in msvc +#ifdef _MSC_VER +#define TORRENT_WHILE_0 \ + __pragma( warning(push) ) \ + __pragma( warning(disable:4127) ) \ + while (false) \ + __pragma( warning(pop) ) +#else +#define TORRENT_WHILE_0 while (false) +#endif + + +namespace libtorrent { +// declarations of the two functions + +// internal +TORRENT_EXPORT void assert_print(char const* fmt, ...) TORRENT_FORMAT(1,2); + +// internal +TORRENT_EXPORT void assert_fail(const char* expr, int line + , char const* file, char const* function, char const* val, int kind = 0); + +} + +#if TORRENT_USE_ASSERTS + +#ifdef TORRENT_PRODUCTION_ASSERTS +extern TORRENT_EXPORT char const* libtorrent_assert_log; +#endif + +#if TORRENT_USE_IOSTREAM +#include +#endif + +#ifndef TORRENT_USE_SYSTEM_ASSERTS + +#define TORRENT_ASSERT_PRECOND_MSG(x, msg) \ + do { if (x) {} else libtorrent::assert_fail(#x, __LINE__, __FILE__, __func__, msg, 1); } TORRENT_WHILE_0 + +#define TORRENT_ASSERT_PRECOND(x) \ + do { if (x) {} else libtorrent::assert_fail(#x, __LINE__, __FILE__, __func__, nullptr, 1); } TORRENT_WHILE_0 + +#define TORRENT_ASSERT(x) \ + do { if (x) {} else libtorrent::assert_fail(#x, __LINE__, __FILE__, __func__, nullptr, 0); } TORRENT_WHILE_0 + +#if TORRENT_USE_IOSTREAM +#define TORRENT_ASSERT_VAL(x, y) \ + do { if (x) {} else { std::stringstream _s; _s << #y ": " << y; \ + libtorrent::assert_fail(#x, __LINE__, __FILE__, __func__, _s.str().c_str(), 0); } } TORRENT_WHILE_0 + +#define TORRENT_ASSERT_FAIL_VAL(y) \ + do { std::stringstream _s; _s << #y ": " << y; \ + libtorrent::assert_fail("", __LINE__, __FILE__, __func__, _s.str().c_str(), 0); } TORRENT_WHILE_0 + +#else +#define TORRENT_ASSERT_VAL(x, y) TORRENT_ASSERT(x) +#define TORRENT_ASSERT_FAIL_VAL(x) TORRENT_ASSERT_FAIL() +#endif + +#define TORRENT_ASSERT_FAIL() \ + libtorrent::assert_fail("", __LINE__, __FILE__, __func__, nullptr, 0) + +#else +#include +#define TORRENT_ASSERT_PRECOND_MSG(x, msg) assert(x) +#define TORRENT_ASSERT_PRECOND(x) assert(x) +#define TORRENT_ASSERT(x) assert(x) +#define TORRENT_ASSERT_VAL(x, y) assert(x) +#define TORRENT_ASSERT_FAIL_VAL(x) assert(false) +#define TORRENT_ASSERT_FAIL() assert(false) +#endif + +#else // TORRENT_USE_ASSERTS + +#define TORRENT_ASSERT_PRECOND_MSG(a, msg) do {} TORRENT_WHILE_0 +#define TORRENT_ASSERT_PRECOND(a) do {} TORRENT_WHILE_0 +#define TORRENT_ASSERT(a) do {} TORRENT_WHILE_0 +#define TORRENT_ASSERT_VAL(a, b) do {} TORRENT_WHILE_0 +#define TORRENT_ASSERT_FAIL_VAL(a) do {} TORRENT_WHILE_0 +#define TORRENT_ASSERT_FAIL() do {} TORRENT_WHILE_0 + +#endif // TORRENT_USE_ASSERTS + +#endif // TORRENT_ASSERT_HPP_INCLUDED diff --git a/docs/include/libtorrent/bdecode.hpp b/docs/include/libtorrent/bdecode.hpp new file mode 100644 index 0000000..66d382b --- /dev/null +++ b/docs/include/libtorrent/bdecode.hpp @@ -0,0 +1,483 @@ +/* + +Copyright (c) 2015-2016, Alden Torres +Copyright (c) 2015-2020, 2022, Arvid Norberg +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2017, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BDECODE_HPP +#define TORRENT_BDECODE_HPP + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/assert.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" + +/* + +This is an efficient bdecoder. It decodes into a flat memory buffer of tokens. + +Each token has an offset into the bencoded buffer where the token came from +and a next pointer, which is a relative number of tokens to skip forward to +get to the logical next item in a container. + +strings and ints offset pointers point to the first character of the length +prefix or the 'i' character. This is to maintain uniformity with other types +and to allow easily calculating the span of a node by subtracting its offset +by the offset of the next node. + +example layout: + +{ + "a": { "b": 1, "c": "abcd" }, + "d": 3 +} + + /---------------------------------------------------------------------------------------\ + | | + | | + | /--------------------------------------------\ | + | | | | + | | | | + | /-----\ | /----\ /----\ /----\ /----\ | /----\ /----\ | + | next | | | | | | | | | | | | | | | | | + | pointers | v | | v | v | v | v v | v | v v ++-+-----+----+--+----+--+----+--+----+--+----+--+----+--+-------+----+--+----+--+------+ X +| dict | str | dict | str | int | str | str | end | str | int | end | +| | | | | | | | | | | | +| | | | | | | | | | | | ++-+-----+-+-----+-+-----+-+-----+-+-----+-+-----+-+-----+-+-----+-+-----+-+-----+-+----+ + | offset| | | | | | | | | | + | | | | | | | | | | | + |/------/ | | | | | | | | | + || /-----------/ | | | | | | | | + || |/------------------/ | | | | | | | + || || /-----------------------/ | | | | | | + || || | /----------------------------/ | | | | | + || || | | /---------------------------------/ | | | | + || || | | | /-----------------------------------/ | | | + || || | | | |/------------------------------------------/ | | + || || | | | || /-----------------------------------------------/ | + || || | | | || | /----------------------------------------------------/ + || || | | | || | | + vv vv v v v vv v v +``d1:ad1:bi1e1:c4:abcde1:di3ee`` + +*/ + +namespace libtorrent { + +TORRENT_EXPORT boost::system::error_category& bdecode_category(); + +#if TORRENT_ABI_VERSION == 1 +TORRENT_DEPRECATED +inline boost::system::error_category& get_bdecode_category() +{ return bdecode_category(); } +#endif + +namespace bdecode_errors { + // libtorrent uses boost.system's ``error_code`` class to represent + // errors. libtorrent has its own error category bdecode_category() + // with the error codes defined by error_code_enum. + enum error_code_enum + { + // Not an error + no_error = 0, + // expected digit in bencoded string + expected_digit, + // expected colon in bencoded string + expected_colon, + // unexpected end of file in bencoded string + unexpected_eof, + // expected value (list, dict, int or string) in bencoded string + expected_value, + // bencoded recursion depth limit exceeded + depth_exceeded, + // bencoded item count limit exceeded + limit_exceeded, + // integer overflow + overflow, + + // the number of error codes + error_code_max + }; + + // hidden + TORRENT_EXPORT boost::system::error_code make_error_code(error_code_enum e); +} +} // namespace libtorrent + +namespace boost { +namespace system { + + template<> struct is_error_code_enum + { static const bool value = true; }; + +} +} + +namespace libtorrent { + + using error_code = boost::system::error_code; + +TORRENT_EXTRA_EXPORT char const* parse_int(char const* start + , char const* end, char delimiter, std::int64_t& val + , bdecode_errors::error_code_enum& ec); + +namespace aux { + +// internal +void escape_string(std::string& ret, char const* str, int len); + +// internal +struct bdecode_token +{ + // the node with type 'end' is a logical node, pointing to the end + // of the bencoded buffer. The `long_string` type is for strings that are so + // long they need a length prefix that's longer than 8 decimal digits. + // these enum values need to be compatible with bdecode_node::type_t + enum type_t : std::uint8_t + { none, dict, list, string, integer, long_string, end }; + + enum limits_t + { + max_offset = (1 << 29) - 1, + max_next_item = (1 << 29) - 1, + short_string_max_header = (1 << 3) - 1 + 2, + long_string_max_header = 8 + (1 << 3) - 1 + 2 + }; + + bdecode_token(std::ptrdiff_t off, bdecode_token::type_t t) + : offset(std::uint32_t(off)) + , type(t) + , next_item(0) + , header(0) + { + TORRENT_ASSERT(off >= 0); + TORRENT_ASSERT(off <= max_offset); + TORRENT_ASSERT(t <= end); + static_assert(std::is_unsigned::type>::value + , "we need to assert t >= 0 here"); + } + + bdecode_token(std::ptrdiff_t const off, std::uint32_t const next + , bdecode_token::type_t const t, std::uint32_t const header_size = 0) + : offset(std::uint32_t(off)) + , type(t == string && header_size > aux::bdecode_token::short_string_max_header ? long_string : t) + , next_item(next) + , header(type == string ? std::uint32_t(header_size - 2) + : type == long_string ? std::uint32_t(header_size - 8 - 2) : 0) + { + TORRENT_ASSERT((type != string && type != long_string) || header_size >= 2); + TORRENT_ASSERT(off >= 0); + TORRENT_ASSERT(off <= max_offset); + TORRENT_ASSERT(next <= max_next_item); + // the string has 2 implied header bytes, to allow for longer prefixes + TORRENT_ASSERT(header_size < 8 + || (type == string && header_size < 10) + || (type == long_string && header_size < 18)); + TORRENT_ASSERT(t <= end); + static_assert(std::is_unsigned::type>::value + , "we need to assert t >= 0 here"); + } + + int start_offset() const + { + TORRENT_ASSERT(type == string || type == long_string); + if (type == string) + return int(header) + 2; + else + return int(header) + 8 + 2; + } + + // offset into the bdecoded buffer where this node is + std::uint32_t offset:29; + + // one of type_t enums + std::uint32_t type:3; + + // if this node is a member of a list, 'next_item' is the number of nodes + // to jump forward in th node array to get to the next item in the list. + // if it's a key in a dictionary, it's the number of step forwards to get + // to its corresponding value. If it's a value in a dictionary, it's the + // number of steps to the next key, or to the end node. + // this is the _relative_ offset to the next node + std::uint32_t next_item:29; + + // This field is only used for ``string`` and ``long_string`` type tokens. + // It is the number of bytes to skip forward from the offset to get to the + // first character of the string. This is essentially the length of the + // length prefix and the colon. Since a string always has at least one + // character of length prefix and always a colon, those 2 characters are + // implied. 3 bits gives us a maximum length of 7, plus one implied digit. + // if the string is 100'000'000 bytes long (100 megabytes), we need more + // digits. That's what the ``long_string`` type is used for. It has 8 + // implied digits in the length prefix (+ the colon). + std::uint32_t header:3; +}; +} + +// a ``bdecode_node`` is used to traverse and hold the tree structure defined +// by bencoded data after it has been parse by bdecode(). +// +// There are primarily two kinds of bdecode_nodes. The ones that own the tree +// structure, and defines its lifetime, and nodes that are child nodes in the +// tree, pointing back into the root's tree. +// +// The ``bdecode_node`` passed in to ``bdecode()`` becomes the one owning the +// tree structure. Make sure not to destruct that object for as long as you +// use any of its child nodes. Also, keep in mind that the buffer originally +// parsed also must remain valid while using it. (see switch_underlying_buffer()). +// +// Copying an owning node will create a copy of the whole tree, but will still +// point into the same parsed bencoded buffer as the first one. + +// Sometimes it's important to get a non-owning reference to the root node ( +// to be able to copy it as a reference for instance). For that, use the +// non_owning() member function. +// +// There are 5 different types of nodes, see type_t. +struct TORRENT_EXPORT bdecode_node +{ +#if TORRENT_ABI_VERSION == 1 + TORRENT_EXPORT friend int bdecode(char const* start, char const* end, bdecode_node& ret + , error_code& ec, int* error_pos, int depth_limit + , int token_limit); +#endif + + // hidden + TORRENT_EXPORT friend bdecode_node bdecode(span buffer + , error_code& ec, int* error_pos, int depth_limit, int token_limit); + + // creates a default constructed node, it will have the type ``none_t``. + bdecode_node() = default; + + // For owning nodes, the copy will create a copy of the tree, but the + // underlying buffer remains the same. + bdecode_node(bdecode_node const&); + bdecode_node& operator=(bdecode_node const&) &; + bdecode_node(bdecode_node&&) noexcept; + bdecode_node& operator=(bdecode_node&&) & = default; + + // the types of bdecoded nodes + enum type_t + { + // uninitialized or default constructed. This is also used + // to indicate that a node was not found in some cases. + none_t, + // a dictionary node. The ``dict_find_`` functions are valid. + dict_t, + // a list node. The ``list_`` functions are valid. + list_t, + // a string node, the ``string_`` functions are valid. + string_t, + // an integer node. The ``int_`` functions are valid. + int_t + }; + + // the type of this node. See type_t. + type_t type() const noexcept; + + // returns true if type() != none_t. + explicit operator bool() const noexcept; + + // return a non-owning reference to this node. This is useful to refer to + // the root node without copying it in assignments. + bdecode_node non_owning() const; + + // returns the buffer and length of the section in the original bencoded + // buffer where this node is defined. For a dictionary for instance, this + // starts with ``d`` and ends with ``e``, and has all the content of the + // dictionary in between. + // the ``data_offset()`` function returns the byte-offset to this node in, + // starting from the beginning of the buffer that was parsed. + span data_section() const noexcept; + std::ptrdiff_t data_offset() const noexcept; + + // functions with the ``list_`` prefix operate on lists. These functions are + // only valid if ``type()`` == ``list_t``. ``list_at()`` returns the item + // in the list at index ``i``. ``i`` may not be greater than or equal to the + // size of the list. ``size()`` returns the size of the list. + bdecode_node list_at(int i) const; + string_view list_string_value_at(int i + , string_view default_val = string_view()) const; + std::int64_t list_int_value_at(int i + , std::int64_t default_val = 0) const; + int list_size() const; + + // Functions with the ``dict_`` prefix operates on dictionaries. They are + // only valid if ``type()`` == ``dict_t``. In case a key you're looking up + // contains a 0 byte, you cannot use the 0-terminated string overloads, + // but have to use ``string_view`` instead. ``dict_find_list`` will return a + // valid ``bdecode_node`` if the key is found _and_ it is a list. Otherwise + // it will return a default-constructed bdecode_node. + // + // Functions with the ``_value`` suffix return the value of the node + // directly, rather than the nodes. In case the node is not found, or it has + // a different type, a default value is returned (which can be specified). + // + // ``dict_at()`` returns the (key, value)-pair at the specified index in a + // dictionary. Keys are only allowed to be strings. ``dict_at_node()`` also + // returns the (key, value)-pair, but the key is returned as a + // ``bdecode_node`` (and it will always be a string). + bdecode_node dict_find(string_view key) const; + std::pair dict_at(int i) const; + std::pair dict_at_node(int i) const; + bdecode_node dict_find_dict(string_view key) const; + bdecode_node dict_find_list(string_view key) const; + bdecode_node dict_find_string(string_view key) const; + bdecode_node dict_find_int(string_view key) const; + string_view dict_find_string_value(string_view key + , string_view default_value = string_view()) const; + std::int64_t dict_find_int_value(string_view key + , std::int64_t default_val = 0) const; + int dict_size() const; + + // this function is only valid if ``type()`` == ``int_t``. It returns the + // value of the integer. + std::int64_t int_value() const; + + // these functions are only valid if ``type()`` == ``string_t``. They return + // the string values. Note that ``string_ptr()`` is *not* 0-terminated. + // ``string_length()`` returns the number of bytes in the string. + // ``string_offset()`` returns the byte offset from the start of the parsed + // bencoded buffer this string can be found. + string_view string_value() const; + char const* string_ptr() const; + int string_length() const; + std::ptrdiff_t string_offset() const; + + // resets the ``bdecoded_node`` to a default constructed state. If this is + // an owning node, the tree is freed and all child nodes are invalidated. + void clear(); + + // Swap contents. + void swap(bdecode_node& n); + + // preallocate memory for the specified numbers of tokens. This is + // useful if you know approximately how many tokens are in the file + // you are about to parse. Doing so will save realloc operations + // while parsing. You should only call this on the root node, before + // passing it in to bdecode(). + void reserve(int tokens); + + // this buffer *MUST* be identical to the one originally parsed. This + // operation is only defined on owning root nodes, i.e. the one passed in to + // decode(). + void switch_underlying_buffer(char const* buf) noexcept; + + // returns true if there is a non-fatal error in the bencoding of this node + // or its children + bool has_soft_error(span error) const; + +private: + bdecode_node(aux::bdecode_token const* tokens, char const* buf + , int len, int idx); + + // if this is the root node, that owns all the tokens, they live in this + // vector. If this is a sub-node, this field is not used, instead the + // m_root_tokens pointer points to the root node's token. + aux::noexcept_movable> m_tokens; + + // this points to the root nodes token vector + // for the root node, this points to its own m_tokens member + aux::bdecode_token const* m_root_tokens = nullptr; + + // this points to the original buffer that was parsed + char const* m_buffer = nullptr; + int m_buffer_size = 0; + + // this is the index into m_root_tokens that this node refers to + // for the root node, it's 0. -1 means uninitialized. + int m_token_idx = -1; + + // this is a cache of the last element index looked up. This only applies + // to lists and dictionaries. If the next lookup is at m_last_index or + // greater, we can start iterating the tokens at m_last_token. + mutable int m_last_index = -1; + mutable int m_last_token = -1; + + // the number of elements in this list or dict (computed on the first + // call to dict_size() or list_size()) + mutable int m_size = -1; +}; + +// print the bencoded structure in a human-readable format to a string +// that's returned. +TORRENT_EXPORT std::string print_entry(bdecode_node const& e + , bool single_line = false, int indent = 0); + +// This function decodes/parses bdecoded data (for example a .torrent file). +// The data structure is returned in the ``ret`` argument. the buffer to parse +// is specified by the ``start`` of the buffer as well as the ``end``, i.e. one +// byte past the end. If the buffer fails to parse, the function returns a +// non-zero value and fills in ``ec`` with the error code. The optional +// argument ``error_pos``, if set to non-nullptr, will be set to the byte offset +// into the buffer where the parse failure occurred. +// +// ``depth_limit`` specifies the max number of nested lists or dictionaries are +// allowed in the data structure. (This affects the stack usage of the +// function, be careful not to set it too high). +// +// ``token_limit`` is the max number of tokens allowed to be parsed from the +// buffer. This is simply a sanity check to not have unbounded memory usage. +// +// The resulting ``bdecode_node`` is an *owning* node. That means it will +// be holding the whole parsed tree. When iterating lists and dictionaries, +// those ``bdecode_node`` objects will simply have references to the root or +// owning ``bdecode_node``. If the root node is destructed, all other nodes +// that refer to anything in that tree become invalid. +// +// However, the underlying buffer passed in to this function (``start``, ``end``) +// must also remain valid while the bdecoded tree is used. The parsed tree +// produced by this function does not copy any data out of the buffer, but +// simply produces references back into it. +TORRENT_EXPORT int bdecode(char const* start, char const* end, bdecode_node& ret + , error_code& ec, int* error_pos = nullptr, int depth_limit = 100 + , int token_limit = 2000000); +TORRENT_EXPORT bdecode_node bdecode(span buffer + , error_code& ec, int* error_pos = nullptr, int depth_limit = 100 + , int token_limit = 2000000); +TORRENT_EXPORT bdecode_node bdecode(span buffer + , int depth_limit = 100, int token_limit = 2000000); + +} + +#endif // TORRENT_BDECODE_HPP diff --git a/docs/include/libtorrent/bencode.hpp b/docs/include/libtorrent/bencode.hpp new file mode 100644 index 0000000..ceb93b4 --- /dev/null +++ b/docs/include/libtorrent/bencode.hpp @@ -0,0 +1,408 @@ +/* + +Copyright (c) 2003-2005, 2007-2009, 2012-2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2019, Amir Abrams +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BENCODE_HPP_INCLUDED +#define TORRENT_BENCODE_HPP_INCLUDED + +// OVERVIEW +// +// Bencoding is a common representation in bittorrent used for dictionary, +// list, int and string hierarchies. It's used to encode .torrent files and +// some messages in the network protocol. libtorrent also uses it to store +// settings, resume data and other session state. +// +// Strings in bencoded structures do not necessarily represent text. +// Strings are raw byte buffers of a certain length. If a string is meant to be +// interpreted as text, it is required to be UTF-8 encoded. See `BEP 3`_. +// +// The function for decoding bencoded data bdecode(), returning a bdecode_node. +// This function builds a tree that points back into the original buffer. The +// returned bdecode_node will not be valid once the buffer it was parsed out of +// is discarded. +// +// It's possible to construct an entry from a bdecode_node, if a structure needs +// to be altered and re-encoded. + +#include +#include // for distance + +#include "libtorrent/config.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/io.hpp" // for write_string +#include "libtorrent/string_util.hpp" // for is_digit + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 + using invalid_encoding = system_error; +#endif + +namespace aux { + + template ::value>::type> + int write_integer(OutIt& out, In data) + { + entry::integer_type const val = entry::integer_type(data); + TORRENT_ASSERT(data == In(val)); + // the stack allocated buffer for keeping the + // decimal representation of the number can + // not hold number bigger than this: + static_assert(sizeof(entry::integer_type) <= 8, "64 bit integers required"); + static_assert(sizeof(data) <= sizeof(entry::integer_type), "input data too big, see entry::integer_type"); + std::array buf; + auto const str = integer_to_str(buf, val); + for (char const c : str) + { + *out = c; + ++out; + } + return static_cast(str.size()); + } + + template + void write_char(OutIt& out, char c) + { + *out = c; + ++out; + } + + template + std::string read_until(InIt& in, InIt end, char end_token, bool& err) + { + std::string ret; + if (in == end) + { + err = true; + return ret; + } + while (*in != end_token) + { + ret += *in; + ++in; + if (in == end) + { + err = true; + return ret; + } + } + return ret; + } + + template + void read_string(InIt& in, InIt end, int len, std::string& str, bool& err) + { + TORRENT_ASSERT(len >= 0); + for (int i = 0; i < len; ++i) + { + if (in == end) + { + err = true; + return; + } + str += *in; + ++in; + } + } + + template + int bencode_recursive(OutIt& out, const entry& e) + { + int ret = 0; + switch(e.type()) + { + case entry::int_t: + write_char(out, 'i'); + ret += write_integer(out, e.integer()); + write_char(out, 'e'); + ret += 2; + break; + case entry::string_t: + ret += write_integer(out, e.string().length()); + write_char(out, ':'); + ret += write_string(e.string(), out); + ret += 1; + break; + case entry::list_t: + write_char(out, 'l'); + for (auto const& i : e.list()) + ret += bencode_recursive(out, i); + write_char(out, 'e'); + ret += 2; + break; + case entry::dictionary_t: + write_char(out, 'd'); + for (auto const& i : e.dict()) + { + // write key + ret += write_integer(out, i.first.length()); + write_char(out, ':'); + ret += write_string(i.first, out); + // write value + ret += bencode_recursive(out, i.second); + ret += 1; + } + write_char(out, 'e'); + ret += 2; + break; + case entry::preformatted_t: + std::copy(e.preformatted().begin(), e.preformatted().end(), out); + ret += static_cast(e.preformatted().size()); + break; + case entry::undefined_t: + + // empty string + write_char(out, '0'); + write_char(out, ':'); + + ret += 2; + break; + } + return ret; + } +#if TORRENT_ABI_VERSION == 1 + template + void bdecode_recursive(InIt& in, InIt end, entry& ret, bool& err, int depth) + { + if (depth >= 100) + { + err = true; + return; + } + + if (in == end) + { + err = true; +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + switch (*in) + { + + // ---------------------------------------------- + // integer + case 'i': + { + ++in; // 'i' + std::string const val = read_until(in, end, 'e', err); + if (err) return; + TORRENT_ASSERT(*in == 'e'); + ++in; // 'e' + ret = entry(entry::int_t); + char* end_pointer; + ret.integer() = std::strtoll(val.c_str(), &end_pointer, 10); +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + if (end_pointer == val.c_str()) + { + err = true; + return; + } + } + break; + + // ---------------------------------------------- + // list + case 'l': + ret = entry(entry::list_t); + ++in; // 'l' + while (*in != 'e') + { + ret.list().emplace_back(); + entry& e = ret.list().back(); + bdecode_recursive(in, end, e, err, depth + 1); + if (err) + { +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + if (in == end) + { + err = true; +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + } +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + TORRENT_ASSERT(*in == 'e'); + ++in; // 'e' + break; + + // ---------------------------------------------- + // dictionary + case 'd': + ret = entry(entry::dictionary_t); + ++in; // 'd' + while (*in != 'e') + { + entry key; + bdecode_recursive(in, end, key, err, depth + 1); + if (err || key.type() != entry::string_t) + { +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + entry& e = ret[key.string()]; + bdecode_recursive(in, end, e, err, depth + 1); + if (err) + { +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + if (in == end) + { + err = true; +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + } +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + TORRENT_ASSERT(*in == 'e'); + ++in; // 'e' + break; + + // ---------------------------------------------- + // string + default: + static_assert(sizeof(*in) == 1, "Input iterator to 8 bit data required"); + if (is_digit(char(*in))) + { + std::string len_s = read_until(in, end, ':', err); + if (err) + { +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + TORRENT_ASSERT(*in == ':'); + ++in; // ':' + int len = atoi(len_s.c_str()); + ret = entry(entry::string_t); + read_string(in, end, len, ret.string(), err); + if (err) + { +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + } + else + { + err = true; +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + } + } +#endif // TORRENT_ABI_VERSION +} + + // This function will encode data to bencoded form. + // + // The entry_ class is the internal representation of the bencoded data + // and it can be used to retrieve information, an entry_ can also be build by + // the program and given to ``bencode()`` to encode it into the ``OutIt`` + // iterator. + // + // ``OutIt`` is an OutputIterator_. It's a template and usually + // instantiated as ostream_iterator_ or back_insert_iterator_. This + // function assumes the value_type of the iterator is a ``char``. + // In order to encode entry ``e`` into a buffer, do:: + // + // std::vector buf; + // bencode(std::back_inserter(buf), e); + // + // .. _OutputIterator: https://en.cppreference.com/w/cpp/named_req/OutputIterator + // .. _ostream_iterator: https://en.cppreference.com/w/cpp/iterator/ostream_iterator + // .. _back_insert_iterator: https://en.cppreference.com/w/cpp/iterator/back_insert_iterator + template int bencode(OutIt out, const entry& e) + { + return aux::bencode_recursive(out, e); + } + +#if TORRENT_ABI_VERSION == 1 + template + TORRENT_DEPRECATED + entry bdecode(InIt start, InIt end) + { + entry e; + bool err = false; + aux::bdecode_recursive(start, end, e, err, 0); + TORRENT_ASSERT(e.m_type_queried == false); + if (err) return entry(); + return e; + } + template + TORRENT_DEPRECATED + entry bdecode(InIt start, InIt end + , typename std::iterator_traits::difference_type& len) + { + entry e; + bool err = false; + InIt s = start; + aux::bdecode_recursive(start, end, e, err, 0); + len = std::distance(s, start); + TORRENT_ASSERT(len >= 0); + if (err) return entry(); + return e; + } +#endif +} + +#endif // TORRENT_BENCODE_HPP_INCLUDED diff --git a/docs/include/libtorrent/bitfield.hpp b/docs/include/libtorrent/bitfield.hpp new file mode 100644 index 0000000..44a0829 --- /dev/null +++ b/docs/include/libtorrent/bitfield.hpp @@ -0,0 +1,335 @@ +/* + +Copyright (c) 2008-2009, 2012-2019, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Falcosc +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BITFIELD_HPP_INCLUDED +#define TORRENT_BITFIELD_HPP_INCLUDED + +#include "libtorrent/assert.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/index_range.hpp" +#include "libtorrent/aux_/unique_ptr.hpp" +#include "libtorrent/aux_/byteswap.hpp" +#include "libtorrent/aux_/ffs.hpp" + +#include // for memset and memcpy +#include // uint32_t + +namespace libtorrent { + + // The bitfield type stores any number of bits as a bitfield + // in a heap allocated array. + struct TORRENT_EXPORT bitfield + { + // constructs a new bitfield. The default constructor creates an empty + // bitfield. ``bits`` is the size of the bitfield (specified in bits). + // ``val`` is the value to initialize the bits to. If not specified + // all bits are initialized to 0. + // + // The constructor taking a pointer ``b`` and ``bits`` copies a bitfield + // from the specified buffer, and ``bits`` number of bits (rounded up to + // the nearest byte boundary). + bitfield() noexcept = default; + explicit bitfield(int bits) { resize(bits); } + bitfield(int bits, bool val) { resize(bits, val); } + bitfield(char const* b, int bits) { assign(b, bits); } + bitfield(bitfield const& rhs) { assign(rhs.data(), rhs.size()); } + bitfield(bitfield&& rhs) noexcept = default; + + // copy bitfield from buffer ``b`` of ``bits`` number of bits, rounded up to + // the nearest byte boundary. + void assign(char const* b, int const bits) + { + resize(bits); + if (bits > 0) + { + std::memcpy(buf(), b, std::size_t((bits + 7) / 8)); + clear_trailing_bits(); + } + } + + // query bit at ``index``. Returns true if bit is 1, otherwise false. + bool operator[](int index) const noexcept + { return get_bit(index); } + bool get_bit(int index) const noexcept + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < size()); + return (buf()[index / 32] & aux::host_to_network(0x80000000 >> (index & 31))) != 0; + } + + // set bit at ``index`` to 0 (clear_bit) or 1 (set_bit). + void clear_bit(int index) noexcept + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < size()); + buf()[index / 32] &= aux::host_to_network(~(0x80000000 >> (index & 31))); + } + void set_bit(int index) noexcept + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < size()); + buf()[index / 32] |= aux::host_to_network(0x80000000 >> (index & 31)); + } + + // returns true if all bits in the bitfield are set + bool all_set() const noexcept; + + // returns true if no bit in the bitfield is set + bool none_set() const noexcept + { + if(size() == 0) return true; + + const int words = num_words(); + std::uint32_t const* b = buf(); + for (int i = 0; i < words; ++i) + { + if (b[i] != 0) return false; + } + return true; + } + + // returns the size of the bitfield in bits. + int size() const noexcept + { + int const bits = m_buf == nullptr ? 0 : int(m_buf[0]); + TORRENT_ASSERT(bits >= 0); + return bits; + } + + // returns the number of 32 bit words are needed to represent all bits in + // this bitfield. + int num_words() const noexcept + { + return (size() + 31) / 32; + } + + // returns the number of bytes needed to represent all bits in this + // bitfield + int num_bytes() const noexcept + { + return (size() + 7) / 8; + } + + // returns true if the bitfield has zero size. + bool empty() const noexcept { return size() == 0; } + + // returns a pointer to the internal buffer of the bitfield, or + // ``nullptr`` if it's empty. + char const* data() const noexcept { return m_buf ? reinterpret_cast(&m_buf[1]) : nullptr; } + char* data() noexcept { return m_buf ? reinterpret_cast(&m_buf[1]) : nullptr; } + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + char const* bytes() const { return data(); } +#endif + + // hidden + bitfield& operator=(bitfield const& rhs) & + { + if (&rhs == this) return *this; + assign(rhs.data(), rhs.size()); + return *this; + } + bitfield& operator=(bitfield&& rhs) & noexcept = default; + + // swaps the bit-fields two variables refer to + void swap(bitfield& rhs) noexcept + { + std::swap(m_buf, rhs.m_buf); + } + + // count the number of bits in the bitfield that are set to 1. + int count() const noexcept; + + // returns the index of the first set bit in the bitfield, i.e. 1 bit. + int find_first_set() const noexcept; + + // returns the index to the last cleared bit in the bitfield, i.e. 0 bit. + int find_last_clear() const noexcept; + + bool operator==(lt::bitfield const& rhs) const; + + // internal + struct const_iterator + { + friend struct bitfield; + + using value_type = bool; + using difference_type = ptrdiff_t; + using pointer = bool const*; + using reference = bool&; + using iterator_category = std::forward_iterator_tag; + + bool operator*() noexcept { return (*buf & aux::host_to_network(bit)) != 0; } + const_iterator& operator++() noexcept { inc(); return *this; } + const_iterator operator++(int) noexcept + { const_iterator ret(*this); inc(); return ret; } + const_iterator& operator--() noexcept { dec(); return *this; } + const_iterator operator--(int) noexcept + { const_iterator ret(*this); dec(); return ret; } + + const_iterator() noexcept {} + bool operator==(const_iterator const& rhs) const noexcept + { return buf == rhs.buf && bit == rhs.bit; } + + bool operator!=(const_iterator const& rhs) const noexcept + { return buf != rhs.buf || bit != rhs.bit; } + + private: + void inc() + { + TORRENT_ASSERT(buf); + if (bit == 0x01) + { + bit = 0x80000000; + ++buf; + } + else + { + bit >>= 1; + } + } + void dec() + { + TORRENT_ASSERT(buf); + if (bit == 0x80000000) + { + bit = 0x01; + --buf; + } + else + { + bit <<= 1; + } + } + const_iterator(std::uint32_t const* ptr, int offset) + : buf(ptr), bit(0x80000000 >> offset) {} + std::uint32_t const* buf = nullptr; + std::uint32_t bit = 0x80000000; + }; + + // internal + const_iterator begin() const noexcept { return const_iterator(m_buf ? buf() : nullptr, 0); } + const_iterator end() const noexcept + { + if (m_buf) + return const_iterator(buf() + num_words() - (((size() & 31) == 0) ? 0 : 1), size() & 31); + else + return const_iterator(nullptr, size() & 31); + } + + // set the size of the bitfield to ``bits`` length. If the bitfield is extended, + // the new bits are initialized to ``val``. + void resize(int bits, bool val); + void resize(int bits); + + // set all bits in the bitfield to 1 (set_all) or 0 (clear_all). + void set_all() noexcept + { + if (size() == 0) return; + std::memset(buf(), 0xff, std::size_t(num_words()) * 4); + clear_trailing_bits(); + } + void clear_all() noexcept + { + if (size() == 0) return; + std::memset(buf(), 0x00, std::size_t(num_words()) * 4); + } + + // make the bitfield empty, of zero size. + void clear() noexcept { m_buf.reset(); } + + private: + + std::uint32_t const* buf() const noexcept { TORRENT_ASSERT(m_buf); return &m_buf[1]; } + std::uint32_t* buf() noexcept { TORRENT_ASSERT(m_buf); return &m_buf[1]; } + void clear_trailing_bits() noexcept + { + // clear the tail bits in the last byte + if (size() & 31) buf()[num_words() - 1] &= aux::host_to_network(0xffffffff << (32 - (size() & 31))); + } + + // the first element is not part of the bitfield, it's the + // number of bits. + aux::unique_ptr m_buf; + }; + + template + struct typed_bitfield : bitfield + { + typed_bitfield() noexcept {} + typed_bitfield(typed_bitfield&& rhs) noexcept + : bitfield(std::forward(rhs)) + {} + typed_bitfield(typed_bitfield const& rhs) + : bitfield(static_cast(rhs)) + {} + typed_bitfield(bitfield&& rhs) noexcept : bitfield(std::forward(rhs)) {} // NOLINT + typed_bitfield(bitfield const& rhs) : bitfield(rhs) {} // NOLINT + typed_bitfield& operator=(typed_bitfield&& rhs) & noexcept + { + this->bitfield::operator=(std::forward(rhs)); + return *this; + } + typed_bitfield& operator=(typed_bitfield const& rhs) & + { + this->bitfield::operator=(rhs); + return *this; + } + using bitfield::bitfield; + + // returns an object that can be used in a range-for to iterate over all + // indices in the bitfield + index_range range() const noexcept + { + return {IndexType{0}, end_index()}; + } + + bool operator[](IndexType const index) const + { return this->bitfield::get_bit(static_cast(index)); } + + bool get_bit(IndexType const index) const + { return this->bitfield::get_bit(static_cast(index)); } + + void clear_bit(IndexType const index) + { this->bitfield::clear_bit(static_cast(index)); } + + void set_bit(IndexType const index) + { this->bitfield::set_bit(static_cast(index)); } + + IndexType end_index() const noexcept { return IndexType(this->size()); } + }; +} + +#endif // TORRENT_BITFIELD_HPP_INCLUDED diff --git a/docs/include/libtorrent/bloom_filter.hpp b/docs/include/libtorrent/bloom_filter.hpp new file mode 100644 index 0000000..ce062a7 --- /dev/null +++ b/docs/include/libtorrent/bloom_filter.hpp @@ -0,0 +1,80 @@ +/* + +Copyright (c) 2010-2011, 2015-2017, 2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BLOOM_FILTER_HPP_INCLUDED +#define TORRENT_BLOOM_FILTER_HPP_INCLUDED + +#include "libtorrent/sha1_hash.hpp" + +#include // for log() +#include + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT void set_bits(std::uint8_t const* k, std::uint8_t* bits, int len); + TORRENT_EXTRA_EXPORT bool has_bits(std::uint8_t const* k, std::uint8_t const* bits, int len); + TORRENT_EXTRA_EXPORT int count_zero_bits(std::uint8_t const* bits, int len); + + template + struct bloom_filter + { + bool find(sha1_hash const& k) const + { return has_bits(&k[0], bits, N); } + + void set(sha1_hash const& k) + { set_bits(&k[0], bits, N); } + + std::string to_string() const + { return std::string(reinterpret_cast(&bits[0]), N); } + + void from_string(char const* str) + { std::memcpy(bits, str, N); } + + void clear() { std::memset(bits, 0, N); } + + float size() const + { + int const c = (std::min)(count_zero_bits(bits, N), (N * 8) - 1); + int const m = N * 8; + return std::log(c / float(m)) / (2.f * std::log(1.f - 1.f/m)); + } + + bloom_filter() { clear(); } + + private: + std::uint8_t bits[N]; + }; + +} + +#endif // TORRENT_BLOOM_FILTER_HPP_INCLUDED diff --git a/docs/include/libtorrent/bt_peer_connection.hpp b/docs/include/libtorrent/bt_peer_connection.hpp new file mode 100644 index 0000000..f5f87c3 --- /dev/null +++ b/docs/include/libtorrent/bt_peer_connection.hpp @@ -0,0 +1,519 @@ +/* + +Copyright (c) 2007, Un Shyam +Copyright (c) 2006-2020, 2022, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2018, Greg Hazel +Copyright (c) 2018-2019, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BT_PEER_CONNECTION_HPP_INCLUDED +#define TORRENT_BT_PEER_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/debug.hpp" +#include "libtorrent/aux_/buffer.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/pe_crypto.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/hash_picker.hpp" + +namespace libtorrent { + +#ifndef TORRENT_DISABLE_EXTENSIONS + struct TORRENT_EXTRA_EXPORT ut_pex_peer_store + { + // stores all peers this peer is connected to. These lists + // are updated with each pex message and are limited in size + // to protect against malicious clients. These lists are also + // used for looking up which peer a peer that supports holepunch + // came from. + // these are vectors to save memory and keep the items close + // together for performance. Inserting and removing is relatively + // cheap since the lists' size is limited + using peers4_t = std::vector>; + peers4_t m_peers; + using peers6_t = std::vector>; + peers6_t m_peers6; + + bool was_introduced_by(tcp::endpoint const& ep); + + virtual ~ut_pex_peer_store() {} + }; +#endif + + struct TORRENT_EXTRA_EXPORT bt_peer_connection + : peer_connection + { + friend struct invariant_access; + + // this is the constructor where the we are the active part. + // The peer_connection should handshake and verify that the + // other end has the correct id + explicit bt_peer_connection(peer_connection_args& pack); + + void start() override; + + enum + { + // pex_msg = 1, + // metadata_msg = 2, + upload_only_msg = 3, + holepunch_msg = 4, + // recommend_msg = 5, + // comment_msg = 6, + dont_have_msg = 7, + share_mode_msg = 8 + }; + + ~bt_peer_connection() override; + + peer_id our_pid() const override { return m_our_peer_id; } + +#if !defined TORRENT_DISABLE_ENCRYPTION + bool supports_encryption() const + { return m_encrypted; } + bool rc4_encrypted() const + { return m_rc4_encrypted; } + + void switch_send_crypto(std::shared_ptr crypto); + void switch_recv_crypto(std::shared_ptr crypto); +#endif + + connection_type type() const override + { return connection_type::bittorrent; } + + enum message_type + { + // standard messages + msg_choke = 0, + msg_unchoke, + msg_interested, + msg_not_interested, + msg_have, + msg_bitfield, + msg_request, + msg_piece, + msg_cancel, + // DHT extension + msg_dht_port, + // FAST extension + msg_suggest_piece = 0xd, + msg_have_all, + msg_have_none, + msg_reject_request, + msg_allowed_fast, + + // extension protocol message + msg_extended = 20, + + msg_hash_request = 21, + msg_hashes, + msg_hash_reject, + + num_supported_messages + }; + + enum class hp_message : std::uint8_t + { + // msg_types + rendezvous = 0, + connect = 1, + failed = 2 + }; + + enum class hp_error + { + // error codes + no_error = 0, + no_such_peer = 1, + not_connected = 2, + no_support = 3, + no_self = 4 + }; + + // called from the main loop when this connection has any + // work to do. + + void on_sent(error_code const& error + , std::size_t bytes_transferred) override; + void on_receive(error_code const& error + , std::size_t bytes_transferred) override; + void on_receive_impl(std::size_t bytes_transferred); + +#if !defined TORRENT_DISABLE_ENCRYPTION + // next_barrier, buffers-to-prepend + std::tuple>> + hit_send_barrier(span> iovec) override; +#endif + + void get_specific_peer_info(peer_info& p) const override; + bool in_handshake() const override; + bool packet_finished() const { return m_recv_buffer.packet_finished(); } + + bool supports_holepunch() const { return m_holepunch_id != 0; } +#ifndef TORRENT_DISABLE_EXTENSIONS + void set_ut_pex(std::weak_ptr ut_pex) + { m_ut_pex = std::move(ut_pex); } + bool was_introduced_by(tcp::endpoint const& ep) const + { auto p = m_ut_pex.lock(); return p && p->was_introduced_by(ep); } +#endif + + bool support_extensions() const { return m_supports_extensions; } + + // the message handlers are called + // each time a recv() returns some new + // data, the last time it will be called + // is when the entire packet has been + // received, then it will no longer + // be called. i.e. most handlers need + // to check how much of the packet they + // have received before any processing + void on_choke(int received); + void on_unchoke(int received); + void on_interested(int received); + void on_not_interested(int received); + void on_have(int received); + void on_bitfield(int received); + void on_request(int received); + void on_piece(int received); + void on_cancel(int received); + void on_hash_request(int received); + void on_hashes(int received); + void on_hash_reject(int received); + + // DHT extension + void on_dht_port(int received); + + // FAST extension + void on_suggest_piece(int received); + void on_have_all(int received); + void on_have_none(int received); + void on_reject_request(int received); + void on_allowed_fast(int received); + void on_holepunch(); + + void on_extended(int received); + + void on_extended_handshake(); + + template + void extension_notify(F message, Args... args); + + // the following functions appends messages + // to the send buffer + void write_choke() override; + void write_unchoke() override; + void write_interested() override; + void write_not_interested() override; + void write_request(peer_request const& r) override; + void write_cancel(peer_request const& r) override; + void write_bitfield() override; + void write_have(piece_index_t index) override; + void write_dont_have(piece_index_t index) override; + void write_piece(peer_request const& r, disk_buffer_holder buffer) override; + void write_keepalive() override; + void write_handshake(); + void write_upload_only(bool enabled) override; + void write_extensions(); + void write_share_mode(); + void write_holepunch_msg(hp_message type, tcp::endpoint const& ep + , hp_error error = hp_error::no_error); + void write_hash_request(hash_request const& req); + void write_hashes(hash_request const& req, span hashes); + void write_hash_reject(hash_request const& req, sha256_hash const& file_root); + + void maybe_send_hash_request(); + + // DHT extension + void write_dht_port(int listen_port); + + // FAST extension + void write_have_all(); + void write_have_none(); + void write_reject_request(peer_request const&) override; + void write_allow_fast(piece_index_t piece) override; + void write_suggest(piece_index_t piece) override; + + void on_connected() override; + void on_metadata() override; + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; + time_point m_last_choke; +#endif + + private: + + template + void send_message(message_type const type + , counters::stats_counter_t const counter + , Args... args) + { + TORRENT_ASSERT(m_sent_handshake); + TORRENT_ASSERT(m_sent_bitfield); + + char msg[5 + sizeof...(Args) * 4] + = { 0,0,0,1 + sizeof...(Args) * 4, static_cast(type) }; + char* ptr = msg + 5; + TORRENT_UNUSED(ptr); + + int tmp[] = {0, (aux::write_int32(args, ptr), 0)...}; + TORRENT_UNUSED(tmp); + + send_buffer(msg); + + stats_counters().inc_stats_counter(counter); + } + + void write_dht_port(); + + bool dispatch_message(int received); + // returns the block currently being + // downloaded. And the progress of that + // block. If the peer isn't downloading + // a piece for the moment, the boost::optional + // will be invalid. + piece_block_progress downloading_piece_progress() const override; + +#if !defined TORRENT_DISABLE_ENCRYPTION + + // if (is_local()), we are 'a' otherwise 'b' + // + // 1. a -> b dhkey, pad + // 2. b -> a dhkey, pad + // 3. a -> b sync, payload + // 4. b -> a sync, payload + // 5. a -> b payload + + void write_pe1_2_dhkey(); + void write_pe3_sync(); + void write_pe4_sync(int crypto_select); + + void write_pe_vc_cryptofield(span write_buf + , int crypto_field, int pad_size); + + // helper to cut down on boilerplate + void rc4_decrypt(span buf); +#endif + + public: + + // these functions encrypt the send buffer if m_rc4_encrypted + // is true, otherwise it passes the call to the + // peer_connection functions of the same names + template + void append_const_send_buffer(Holder holder, int size) + { +#if !defined TORRENT_DISABLE_ENCRYPTION + if (!m_enc_handler.is_send_plaintext()) + { + // if we're encrypting this buffer, we need to make a copy + // since we'll mutate it + aux::buffer buf(size, {holder.data(), size}); + append_send_buffer(std::move(buf), size); + } + else +#endif + { + append_send_buffer(std::move(holder), size); + } + } + + private: + +#if !defined TORRENT_DISABLE_ENCRYPTION + void init_bt_handshake(); +#endif + + enum class state_t : std::uint8_t + { +#if !defined TORRENT_DISABLE_ENCRYPTION + read_pe_dhkey, + read_pe_syncvc, + read_pe_synchash, + read_pe_skey_vc, + read_pe_cryptofield, + read_pe_pad, + read_pe_ia, +#endif + read_protocol_identifier, + read_info_hash, + read_peer_id, + + // handshake complete + read_packet_size, + read_packet + }; + + // state of on_receive. one of the enums in state_t + state_t m_state = state_t::read_protocol_identifier; + + // this is set to true if the handshake from + // the peer indicated that it supports the + // extension protocol + bool m_supports_extensions:1; + bool m_supports_dht_port:1; + bool m_supports_fast:1; + + // this is set to true when we send the bitfield message. + // for magnet links we can't do that right away, + // since we don't know how many pieces there are in + // the torrent. + bool m_sent_bitfield:1; + + // true if we're done sending the bittorrent handshake, + // and can send bittorrent messages + bool m_sent_handshake:1; + + // set to true once we send the allowed-fast messages. This is + // only done once per connection + bool m_sent_allowed_fast:1; + +#if !defined TORRENT_DISABLE_ENCRYPTION + // this is set to true after the encryption method has been + // successfully negotiated (either plaintext or rc4), to signal + // automatic encryption/decryption. + bool m_encrypted:1; + + // true if rc4, false if plaintext + bool m_rc4_encrypted:1; + +// this is a legitimate use of a shadow field +#ifdef __clang__ +#pragma clang diagnostic push +// macOS clang doesn't have -Wshadow-field +#pragma clang diagnostic ignored "-Wunknown-pragmas" +// Xcode 9 needs this +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wshadow-field" +#endif + aux::crypto_receive_buffer m_recv_buffer; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif + + std::string m_client_version; + + // the peer ID we advertise for ourself + peer_id const m_our_peer_id; + + // this is a queue of ranges that describes + // where in the send buffer actual payload + // data is located. This is currently + // only used to be able to gather statistics + // separately on payload and protocol data. + struct range + { + range(int s, int l) + : start(s) + , length(l) + { + TORRENT_ASSERT(s >= 0); + TORRENT_ASSERT(l > 0); + } + int start; + int length; + }; + + std::vector m_payloads; + + std::vector m_hash_requests; + +#if !defined TORRENT_DISABLE_ENCRYPTION + // initialized during write_pe1_2_dhkey, and destroyed on + // creation of m_enc_handler. Cannot reinitialize once + // initialized. + std::unique_ptr m_dh_key_exchange; + + // used during an encrypted handshake then moved + // into m_enc_handler if rc4 encryption is negotiated + // otherwise it is destroyed when the handshake completes + std::shared_ptr m_rc4; + + // if encryption is negotiated, this is used for + // encryption/decryption during the entire session. + encryption_handler m_enc_handler; + + // (outgoing only) synchronize verification constant with + // remote peer, this will hold rc4_decrypt(vc). Destroyed + // after the sync step. + std::unique_ptr m_sync_vc; + + // (incoming only) synchronize hash with remote peer, holds + // the sync hash (hash("req1",secret)). Destroyed after the + // sync step. + std::unique_ptr m_sync_hash; + + // used to disconnect peer if sync points are not found within + // the maximum number of bytes + int m_sync_bytes_read = 0; +#endif + + // the message ID for upload only message + // 0 if not supported + std::uint8_t m_upload_only_id = 0; + + // the message ID for holepunch messages + std::uint8_t m_holepunch_id = 0; + + // the message ID for don't-have message + std::uint8_t m_dont_have_id = 0; + + // the message ID for share mode message + // 0 if not supported + std::uint8_t m_share_mode_id = 0; + +#ifndef TORRENT_DISABLE_EXTENSIONS + std::weak_ptr m_ut_pex; +#endif + + std::array m_reserved_bits; + }; +} + +#endif // TORRENT_BT_PEER_CONNECTION_HPP_INCLUDED diff --git a/docs/include/libtorrent/choker.hpp b/docs/include/libtorrent/choker.hpp new file mode 100644 index 0000000..e593ca0 --- /dev/null +++ b/docs/include/libtorrent/choker.hpp @@ -0,0 +1,60 @@ +/* + +Copyright (c) 2014, 2017-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CHOKER_HPP_INCLUDED +#define TORRENT_CHOKER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/time.hpp" // for time_duration +#include + +namespace libtorrent { + +namespace aux { + struct session_settings; +} + struct peer_connection; + + // sorts the vector of peers in-place. When returning, the top unchoke slots + // elements are the peers we should unchoke. This is similar to a partial + // sort. Only the unchoke slots first elements are sorted. + // the return value are the number of peers that should be unchoked. This + // is also the number of elements that are valid at the beginning of the + // peer list. Peers beyond this initial range are not sorted. + TORRENT_EXTRA_EXPORT int unchoke_sort(std::vector& peers + , time_duration unchoke_interval + , aux::session_settings const& sett); + +} + +#endif // TORRENT_CHOKER_INCLUDED diff --git a/docs/include/libtorrent/client_data.hpp b/docs/include/libtorrent/client_data.hpp new file mode 100644 index 0000000..885fdd4 --- /dev/null +++ b/docs/include/libtorrent/client_data.hpp @@ -0,0 +1,104 @@ +/* + +Copyright (c) 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CLIENT_DATA_HPP_INCLUDED +#define TORRENT_CLIENT_DATA_HPP_INCLUDED + +#include "libtorrent/aux_/export.hpp" +#include + +namespace libtorrent { + +// A thin wrapper around a void pointer used as "user data". i.e. an opaque +// cookie passed in to libtorrent and returned on demand. It adds type-safety by +// requiring the same type be requested out of it as was assigned to it. +struct TORRENT_EXPORT client_data_t +{ + // construct a nullptr client data + client_data_t() = default; + + // initialize the client data with the specified pointer + template + explicit client_data_t(T* v) + : m_type_ptr(type()) + , m_client_ptr(v) + {} + + // assigns a new pointer to the client data + template + client_data_t& operator=(T* v) + { + m_type_ptr = type(); + m_client_ptr = v; + return *this; + } + + // request to retrieve the pointer back again. The type ``T`` must be + // identical to the type of the pointer assigned earlier, including + // cv-qualifiers. + template + T* get() const + { + if (m_type_ptr != type()) return nullptr; + return static_cast(m_client_ptr); + } + template ::value>::type> + explicit operator T() const + { + if (m_type_ptr != type::type>()) return nullptr; + return static_cast(m_client_ptr); + } + +#if TORRENT_ABI_VERSION > 2 + // we don't allow type-unsafe operations + operator void*() const = delete; + operator void const*() const = delete; + client_data_t& operator=(void*) = delete; + client_data_t& operator=(void const*) = delete; +#endif + +private: + template + char const* type() const + { + // each unique T will instantiate a unique "instance", and have a unique + // address + static const char instance = 0; + return &instance; + } + char const* m_type_ptr = nullptr; + void* m_client_ptr = nullptr; +}; + +} + +#endif diff --git a/docs/include/libtorrent/close_reason.hpp b/docs/include/libtorrent/close_reason.hpp new file mode 100644 index 0000000..8a22b29 --- /dev/null +++ b/docs/include/libtorrent/close_reason.hpp @@ -0,0 +1,156 @@ +/* + +Copyright (c) 2015, 2017, 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CLOSE_REASON_HPP +#define TORRENT_CLOSE_REASON_HPP + +#include "libtorrent/error_code.hpp" + +namespace libtorrent { + + // internal: these are all the reasons to disconnect a peer + // all reasons caused by the peer sending unexpected data + // are 256 and up. + enum class close_reason_t : std::uint16_t + { + // no reason specified. Generic close. + none = 0, + + // we're already connected to + duplicate_peer_id, + + // this torrent has been removed, paused or stopped from this client. + torrent_removed, + + // client failed to allocate necessary memory for this peer connection + no_memory, + + // the source port of this peer is blocked + port_blocked, + + // the source IP has been blocked + blocked, + + // both ends of the connection are upload-only. staying connected would + // be redundant + upload_to_upload, + + // connection was closed because the other end is upload only and does + // not have any pieces we're interested in + not_interested_upload_only, + + // peer connection timed out (generic timeout) + timeout, + + // the peers have not been interested in each other for a very long time. + // disconnect + timed_out_interest, + + // the peer has not sent any message in a long time. + timed_out_activity, + + // the peer did not complete the handshake in too long + timed_out_handshake, + + // the peer sent an interested message, but did not send a request + // after a very long time after being unchoked. + timed_out_request, + + // the encryption mode is blocked + protocol_blocked, + + // the peer was disconnected in the hopes of finding a better peer + // in the swarm + peer_churn, + + // we have too many peers connected + too_many_connections, + + // we have too many file-descriptors open + too_many_files, + + // the encryption handshake failed + encryption_error = 256, + + // the info hash sent as part of the handshake was not what we expected + invalid_info_hash, + + self_connection, + + // the metadata received matched the info-hash, but failed to parse. + // this is either someone finding a SHA1 collision, or the author of + // the magnet link creating it from an invalid torrent + invalid_metadata, + + // the advertised metadata size + metadata_too_big, + + // invalid bittorrent messages + message_too_big, + invalid_message_id, + invalid_message, + invalid_piece_message, + invalid_have_message, + invalid_bitfield_message, + invalid_choke_message, + invalid_unchoke_message, + invalid_interested_message, + invalid_not_interested_message, + invalid_request_message, + invalid_reject_message, + invalid_allow_fast_message, + invalid_extended_message, + invalid_cancel_message, + invalid_dht_port_message, + invalid_suggest_message, + invalid_have_all_message, + invalid_dont_have_message, + invalid_have_none_message, + invalid_pex_message, + invalid_metadata_request_message, + invalid_metadata_message, + invalid_metadata_offset, + + // the peer sent a request while being choked + request_when_choked, + + // the peer sent corrupt data + corrupt_pieces, + + pex_message_too_big, + pex_too_frequent + }; + + close_reason_t error_to_close_reason(error_code const& ec); +} + +#endif diff --git a/docs/include/libtorrent/config.hpp b/docs/include/libtorrent/config.hpp new file mode 100644 index 0000000..6752a5e --- /dev/null +++ b/docs/include/libtorrent/config.hpp @@ -0,0 +1,682 @@ +/* + +Copyright (c) 2005, 2008-2022, Arvid Norberg +Copyright (c) 2009, Daniel Wallin +Copyright (c) 2015, John Sebastian Peterson +Copyright (c) 2016-2017, 2019, 2021, Alden Torres +Copyright (c) 2016, 2019, Andrei Kurushin +Copyright (c) 2016, terry zhao +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, Pavel Pimenov +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CONFIG_HPP_INCLUDED +#define TORRENT_CONFIG_HPP_INCLUDED + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#if !defined __MINGW64__ && !defined __MINGW32__ +#define _FILE_OFFSET_BITS 64 +#endif + +#include + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#ifdef __linux__ +#include // for LINUX_VERSION_CODE and KERNEL_VERSION +#endif // __linux + +#if defined __MINGW64__ || defined __MINGW32__ +// GCC warns on format codes that are incompatible with glibc, which the windows +// format codes are. So we need to disable those for mingw targets +#pragma GCC diagnostic ignored "-Wformat" +#pragma GCC diagnostic ignored "-Wformat-extra-args" +// Mingw does not like friend declarations of dllexport functions. This +// suppresses those warnings +#pragma GCC diagnostic ignored "-Wattributes" +#endif + +// This is the GCC indication of building with address sanitizer +#if defined __SANITIZE_ADDRESS__ && __SANITIZE_ADDRESS__ +#define TORRENT_ADDRESS_SANITIZER 1 +#endif + +// This is the clang indication of building with address sanitizer +#if defined(__has_feature) +#if __has_feature(address_sanitizer) +#define TORRENT_ADDRESS_SANITIZER 1 +#endif +#endif + +#if defined __GNUC__ + +#ifdef _GLIBCXX_CONCEPT_CHECKS +#define TORRENT_COMPLETE_TYPES_REQUIRED 1 +#endif + +// ======= SUNPRO ========= + +#elif defined __SUNPRO_CC + +#define TORRENT_COMPLETE_TYPES_REQUIRED 1 + +// ======= MSVC ========= + +#elif defined BOOST_MSVC + +// class X needs to have dll-interface to be used by clients of class Y +#pragma warning(disable:4251) + +#endif + + +// ======= PLATFORMS ========= + + +// set up defines for target environments +// ==== AMIGA === +#if defined __AMIGA__ || defined __amigaos__ || defined __AROS__ +#define TORRENT_AMIGA +#define TORRENT_USE_IOSTREAM 0 +// set this to 1 to disable all floating point operations +// (disables some float-dependent APIs) +#define TORRENT_NO_FPU 1 +#define TORRENT_USE_I2P 0 + +// ==== Darwin/BSD === +#elif (defined __APPLE__ && defined __MACH__) || defined __FreeBSD__ || defined __NetBSD__ \ + || defined __OpenBSD__ || defined __bsdi__ || defined __DragonFly__ \ + || defined __FreeBSD_kernel__ +#define TORRENT_BSD + +#if defined __APPLE__ + +#include +#include + +#if defined __MACH__ && MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 +#define TORRENT_HAS_COPYFILE 1 +#endif + +#define TORRENT_NATIVE_UTF8 1 + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 +// on OSX, use the built-in common crypto for built-in +# if !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT +# define TORRENT_USE_COMMONCRYPTO 1 +# endif +#endif // MAC_OS_X_VERSION_MIN_REQUIRED + +// execinfo.h is available in the MacOS X 10.5 SDK. +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 +#define TORRENT_USE_EXECINFO 1 +#endif + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 +// this is used for the ip_change_notifier on macOS, which isn't supported on +// 10.6 and earlier +#define TORRENT_USE_SYSTEMCONFIGURATION 1 +#endif + +#if TARGET_OS_IPHONE +#define TORRENT_USE_SC_NETWORK_REACHABILITY 1 +#endif + +#define TORRENT_USE_DEV_RANDOM 1 + +#else + +// non-Apple BSD +#define TORRENT_USE_GETRANDOM 1 +#define TORRENT_HAS_PTHREAD_SET_NAME 1 + +#endif // __APPLE__ + +#define TORRENT_HAS_SYMLINK 1 + +#ifndef TORRENT_HAVE_MMAP +#define TORRENT_HAVE_MMAP 1 +#endif +#define TORRENT_USE_MADVISE 1 + +#define TORRENT_HAS_FALLOCATE 0 + +#define TORRENT_USE_IFADDRS 1 +#define TORRENT_USE_SYSCTL 1 +#define TORRENT_USE_IFCONF 1 + + +// ==== LINUX === +#elif defined __linux__ +#define TORRENT_LINUX + +#ifndef TORRENT_HAVE_MMAP +#define TORRENT_HAVE_MMAP 1 +#endif + +#if defined __GLIBC__ && (__GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 27)) +#define TORRENT_HAS_COPY_FILE_RANGE 0 +#elif defined __ANDROID__ +#define TORRENT_HAS_COPY_FILE_RANGE 0 +#else +#define TORRENT_HAS_COPY_FILE_RANGE 1 +#endif + +#define TORRENT_HAS_PTHREAD_SET_NAME 1 +#define TORRENT_HAS_SYMLINK 1 +#define TORRENT_USE_MADVISE 1 +#define TORRENT_USE_NETLINK 1 +#define TORRENT_USE_IFADDRS 0 +#define TORRENT_USE_IFCONF 1 +#define TORRENT_HAS_SALEN 0 +#define TORRENT_USE_FDATASYNC 1 + +#if defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ > 24)) +#define TORRENT_USE_GETRANDOM 1 +#endif + +// ===== ANDROID ===== (almost linux, sort of) +#if defined __ANDROID__ +#define TORRENT_ANDROID +#define TORRENT_HAS_PTHREAD_SET_NAME 1 + +#if __ANDROID_API__ < 21 +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_HAS_FADVISE 0 +#endif // API < 21 + +// android 32 bits has real problems with fseeko +#if (__ANDROID_API__ < 24) || defined __arm__ || defined __i386__ +#define TORRENT_HAS_FSEEKO 0 +#endif + +#if __ANDROID_API__ < 24 +#define TORRENT_HAS_FTELLO 0 +#endif // API < 24 + +// Starting Android 11 (API >= 30), the enum_routes using NETLINK +// is not possible anymore. For other functions, it's not clear +// that IFADDRS is working as expected for API >= 30, but at least +// it is supported. +// See https://developer.android.com/training/articles/user-data-ids#mac-11-plus +#if __ANDROID_API__ >= 24 +#undef TORRENT_USE_NETLINK +#undef TORRENT_USE_IFADDRS +#define TORRENT_USE_NETLINK 0 +#define TORRENT_USE_IFADDRS 1 +#endif // API >= 24 + +#else // ANDROID + +// posix_fallocate() is not available in glibc under these condition +#if defined _XOPEN_SOURCE && _XOPEN_SOURCE < 600 +#define TORRENT_HAS_FALLOCATE 0 +#elif defined _POSIX_C_SOURCE && _POSIX_C_SOURCE < 200112L +#define TORRENT_HAS_FALLOCATE 0 +#endif + +#define TORRENT_USE_SYNC_FILE_RANGE 1 + +#endif // ANDROID + +#if defined __GLIBC__ && ( defined __x86_64__ || defined __i386 \ + || defined _M_X64 || defined _M_IX86 ) +#define TORRENT_USE_EXECINFO 1 +#endif + +// ==== MINGW === +#elif defined __MINGW32__ || defined __MINGW64__ +#define TORRENT_MINGW +#define TORRENT_WINDOWS +#ifndef TORRENT_HAVE_MAP_VIEW_OF_FILE +#define TORRENT_HAVE_MAP_VIEW_OF_FILE 1 +#endif +#define TORRENT_USE_RLIMIT 0 +#define TORRENT_USE_NETLINK 0 +#define TORRENT_USE_GETADAPTERSADDRESSES 1 +#define TORRENT_HAS_SALEN 0 +#define TORRENT_USE_GETIPFORWARDTABLE 1 +#define TORRENT_USE_UNC_PATHS 1 + +// mingw doesn't implement random_device. +#define TORRENT_BROKEN_RANDOM_DEVICE 1 + +# if !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT + +#ifdef NTDDI_VERSION +# if (NTDDI_VERSION >= NTDDI_VISTA) +# define TORRENT_USE_CNG 1 +# endif +#else // NTDDI_VERSION not defined so use simple _WIN32_WINNT check +# if _WIN32_WINNT >= 0x0600 +# define TORRENT_USE_CNG 1 +# endif +#endif + +# if !defined TORRENT_USE_CNG +// unless some other crypto library has been specified, default to the native +// windows CryptoAPI +#define TORRENT_USE_CRYPTOAPI 1 +#define TORRENT_USE_DEV_RANDOM 0 + +#ifdef NTDDI_VERSION +# if (NTDDI_VERSION > NTDDI_WINXPSP2) +# define TORRENT_USE_CRYPTOAPI_SHA_512 1 +# endif +#else // NTDDI_VERSION not defined so use simple _WIN32_WINNT check +# if _WIN32_WINNT >= 0x0600 +# define TORRENT_USE_CRYPTOAPI_SHA_512 1 +# endif +#endif + +#endif // !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT + +#endif +// ==== WINDOWS === +#elif defined _WIN32 +#define TORRENT_WINDOWS +#ifndef TORRENT_USE_GETIPFORWARDTABLE +# define TORRENT_USE_GETIPFORWARDTABLE 1 +#endif + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#ifndef TORRENT_HAVE_MAP_VIEW_OF_FILE +#define TORRENT_HAVE_MAP_VIEW_OF_FILE 1 +#endif + +# if !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT + +#ifdef NTDDI_VERSION +# if (NTDDI_VERSION >= NTDDI_VISTA) +# define TORRENT_USE_CNG 1 +# endif +#else // NTDDI_VERSION not defined so use simple _WIN32_WINNT check +# if _WIN32_WINNT >= 0x0600 +# define TORRENT_USE_CNG 1 +# endif +#endif + +# if !defined TORRENT_USE_CNG +// unless some other crypto library has been specified, default to the native +// windows CryptoAPI +#define TORRENT_USE_CRYPTOAPI 1 +#define TORRENT_USE_DEV_RANDOM 0 + +#ifdef NTDDI_VERSION +# if (NTDDI_VERSION > NTDDI_WINXPSP2) +# define TORRENT_USE_CRYPTOAPI_SHA_512 1 +# endif +#else // NTDDI_VERSION not defined so use simple _WIN32_WINNT check +# if _WIN32_WINNT >= 0x0600 +# define TORRENT_USE_CRYPTOAPI_SHA_512 1 +# endif +#endif + +#endif // !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT + +#endif + +#define TORRENT_USE_GETADAPTERSADDRESSES 1 +#define TORRENT_HAS_SALEN 0 +#define TORRENT_USE_RLIMIT 0 +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_USE_UNC_PATHS 1 + +// ==== WINRT === +#if defined(WINAPI_FAMILY_PARTITION) +# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) \ + && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +# define TORRENT_WINRT +# endif +#endif + +// ==== SOLARIS === +#elif defined sun || defined __sun +#define TORRENT_SOLARIS +#define TORRENT_USE_IFCONF 1 +#define TORRENT_HAS_SALEN 0 +#ifndef TORRENT_HAVE_MMAP +#define TORRENT_HAVE_MMAP 1 +#endif +#define TORRENT_USE_MADVISE 1 +#define TORRENT_HAS_SYMLINK 1 +#define TORRENT_USE_GETRANDOM 1 + +// ==== BEOS === +#elif defined __BEOS__ || defined __HAIKU__ +#define TORRENT_BEOS +#include // B_PATH_NAME_LENGTH +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_NATIVE_UTF8 1 +#define TORRENT_USE_IFCONF 1 +#define TORRENT_USE_GRTTABLE 1 + +// ==== GNU/Hurd === +#elif defined __GNU__ +#define TORRENT_HURD +#define TORRENT_USE_IFADDRS 1 +#define TORRENT_USE_IFCONF 1 +#define TORRENT_HAS_SYMLINK 1 +#define TORRENT_USE_GETRANDOM 1 + +// ==== eCS(OS/2) === +#elif defined __OS2__ +#define TORRENT_OS2 +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_USE_IFCONF 1 +#define TORRENT_USE_SYSCTL 1 + +#else + +#ifdef _MSC_VER +#pragma message ( "unknown OS, assuming BSD" ) +#else +#warning "unknown OS, assuming BSD" +#endif + +#define TORRENT_BSD +#endif + +#define TORRENT_UNUSED(x) (void)(x) + +#if defined __GNUC__ || defined __clang__ +#define TORRENT_FORMAT(fmt, ellipsis) __attribute__((__format__(__printf__, fmt, ellipsis))) +#else +#define TORRENT_FORMAT(fmt, ellipsis) +#endif + +#ifndef TORRENT_BROKEN_RANDOM_DEVICE +#define TORRENT_BROKEN_RANDOM_DEVICE 0 +#endif + +#ifndef TORRENT_HAS_SALEN +#define TORRENT_HAS_SALEN 1 +#endif + +#ifndef TORRENT_USE_GETADAPTERSADDRESSES +#define TORRENT_USE_GETADAPTERSADDRESSES 0 +#endif + +#ifndef TORRENT_USE_NETLINK +#define TORRENT_USE_NETLINK 0 +#endif + +#ifndef TORRENT_USE_EXECINFO +#define TORRENT_USE_EXECINFO 0 +#endif + +#ifndef TORRENT_USE_SYSCTL +#define TORRENT_USE_SYSCTL 0 +#endif + +#ifndef TORRENT_USE_GETIPFORWARDTABLE +#define TORRENT_USE_GETIPFORWARDTABLE 0 +#endif + +#if defined BOOST_NO_STD_WSTRING +#error your C++ standard library appears to be missing std::wstring. This type is required on windows +#endif + +#ifndef TORRENT_HAS_FALLOCATE +#define TORRENT_HAS_FALLOCATE 1 +#endif + +#ifndef TORRENT_HAS_FADVISE +#define TORRENT_HAS_FADVISE 1 +#endif + +#ifndef TORRENT_HAS_FSEEKO +#define TORRENT_HAS_FSEEKO 1 +#endif + +#ifndef TORRENT_HAS_FTELLO +#define TORRENT_HAS_FTELLO 1 +#endif + +#ifndef TORRENT_USE_COMMONCRYPTO +#define TORRENT_USE_COMMONCRYPTO 0 +#endif + +#ifndef TORRENT_USE_SYSTEMCONFIGURATION +#define TORRENT_USE_SYSTEMCONFIGURATION 0 +#endif + +#ifndef TORRENT_USE_SC_NETWORK_REACHABILITY +#define TORRENT_USE_SC_NETWORK_REACHABILITY 0 +#endif + +#ifndef TORRENT_USE_CRYPTOAPI +#define TORRENT_USE_CRYPTOAPI 0 +#endif + +#ifndef TORRENT_USE_CRYPTOAPI_SHA_512 +#define TORRENT_USE_CRYPTOAPI_SHA_512 0 +#endif + +#ifndef TORRENT_USE_CNG +#define TORRENT_USE_CNG 0 +#endif + +#ifndef TORRENT_USE_DEV_RANDOM +#define TORRENT_USE_DEV_RANDOM 1 +#endif + +#ifndef TORRENT_HAVE_MMAP +#define TORRENT_HAVE_MMAP 0 +#endif + +#ifndef TORRENT_HAVE_MAP_VIEW_OF_FILE +#define TORRENT_HAVE_MAP_VIEW_OF_FILE 0 +#endif + +#ifndef TORRENT_USE_MADVISE +#define TORRENT_USE_MADVISE 0 +#endif + +#ifndef TORRENT_USE_SYNC_FILE_RANGE +#define TORRENT_USE_SYNC_FILE_RANGE 0 +#endif + + +#ifndef TORRENT_COMPLETE_TYPES_REQUIRED +#define TORRENT_COMPLETE_TYPES_REQUIRED 0 +#endif + +#ifndef TORRENT_USE_FDATASYNC +#define TORRENT_USE_FDATASYNC 0 +#endif + +#ifndef TORRENT_USE_UNC_PATHS +#define TORRENT_USE_UNC_PATHS 0 +#endif + +#ifndef TORRENT_USE_RLIMIT +#define TORRENT_USE_RLIMIT 1 +#endif + +#ifndef TORRENT_USE_IFADDRS +#define TORRENT_USE_IFADDRS 0 +#endif + +#ifndef TORRENT_NO_FPU +#define TORRENT_NO_FPU 0 +#endif + +#ifndef TORRENT_USE_IOSTREAM +#ifndef BOOST_NO_IOSTREAM +#define TORRENT_USE_IOSTREAM 1 +#else +#define TORRENT_USE_IOSTREAM 0 +#endif +#endif + +#ifndef TORRENT_USE_I2P +#define TORRENT_USE_I2P 1 +#endif + +#ifndef TORRENT_HAS_SYMLINK +#define TORRENT_HAS_SYMLINK 0 +#endif + +#ifndef TORRENT_USE_IFCONF +#define TORRENT_USE_IFCONF 0 +#endif + +#ifndef TORRENT_USE_GETRANDOM +#define TORRENT_USE_GETRANDOM 0 +#endif + +#ifndef TORRENT_NATIVE_UTF8 +#define TORRENT_NATIVE_UTF8 0 +#endif + +#ifndef TORRENT_HAS_PTHREAD_SET_NAME +#define TORRENT_HAS_PTHREAD_SET_NAME 0 +#endif + +#ifndef TORRENT_HAS_COPY_FILE_RANGE +#define TORRENT_HAS_COPY_FILE_RANGE 0 +#endif + +#ifndef TORRENT_HAS_COPYFILE +#define TORRENT_HAS_COPYFILE 0 +#endif + +// debug builds have asserts enabled by default, release +// builds have asserts if they are explicitly enabled by +// the release_asserts macro. +#ifndef TORRENT_USE_ASSERTS +#define TORRENT_USE_ASSERTS 0 +#endif // TORRENT_USE_ASSERTS + +#ifndef TORRENT_USE_INVARIANT_CHECKS +#define TORRENT_USE_INVARIANT_CHECKS 0 +#endif + +#if TORRENT_USE_INVARIANT_CHECKS && !TORRENT_USE_ASSERTS +#error "invariant checks cannot be enabled without asserts" +#endif + +// SSE is x86 / amd64 specific. On top of that, we only +// know how to access it on msvc and gcc (and gcc compatibles). +// GCC requires the user to enable SSE support in order for +// the program to have access to the intrinsics, this is +// indicated by the __SSE4_1__ macro +#ifndef TORRENT_HAS_SSE + +#if (defined _M_AMD64 || defined _M_IX86 || defined _M_X64 \ + || defined __amd64__ || defined __i386 || defined __i386__ \ + || defined __x86_64__ || defined __x86_64) \ + && (defined __GNUC__ || (defined _MSC_VER && _MSC_VER >= 1600)) +#define TORRENT_HAS_SSE 1 +#else +#define TORRENT_HAS_SSE 0 +#endif + +#endif // TORRENT_HAS_SSE + +#if (defined __arm__ || defined __aarch64__ || defined _M_ARM || defined _M_ARM64) +#define TORRENT_HAS_ARM 1 +#else +#define TORRENT_HAS_ARM 0 +#endif // TORRENT_HAS_ARM + +#ifndef __has_builtin +#define __has_builtin(x) 0 // for non-clang compilers +#endif + +#ifdef __cpp_guaranteed_copy_elision +#define TORRENT_RVO(x) x +#else +#define TORRENT_RVO(x) std::move(x) +#endif + +#if (TORRENT_HAS_SSE && defined __GNUC__) +# define TORRENT_HAS_BUILTIN_CLZ 1 +#elif (TORRENT_HAS_ARM && defined __GNUC__ && !defined __clang__) +# define TORRENT_HAS_BUILTIN_CLZ 1 +#elif (defined __clang__ && __has_builtin(__builtin_clz)) +# define TORRENT_HAS_BUILTIN_CLZ 1 +#else +# define TORRENT_HAS_BUILTIN_CLZ 0 +#endif // TORRENT_HAS_BUILTIN_CLZ + +#if (TORRENT_HAS_SSE && defined __GNUC__) +# define TORRENT_HAS_BUILTIN_CTZ 1 +#elif (TORRENT_HAS_ARM && defined __GNUC__ && !defined __clang__) +# define TORRENT_HAS_BUILTIN_CTZ 1 +#elif (defined __clang__ && __has_builtin(__builtin_ctz)) +# define TORRENT_HAS_BUILTIN_CTZ 1 +#else +# define TORRENT_HAS_BUILTIN_CTZ 0 +#endif // TORRENT_HAS_BUILTIN_CTZ + +#if TORRENT_HAS_ARM && defined __ARM_NEON +# define TORRENT_HAS_ARM_NEON 1 +#else +# define TORRENT_HAS_ARM_NEON 0 +#endif // TORRENT_HAS_ARM_NEON + +#if TORRENT_HAS_ARM && defined __ARM_FEATURE_CRC32 +# define TORRENT_HAS_ARM_CRC32 1 +#else +#if defined TORRENT_FORCE_ARM_CRC32 +# define TORRENT_HAS_ARM_CRC32 1 +#else +# define TORRENT_HAS_ARM_CRC32 0 +#endif +#endif // TORRENT_HAS_ARM_CRC32 + +#if defined TORRENT_USE_OPENSSL || defined TORRENT_USE_GNUTLS +#define TORRENT_USE_SSL 1 +#else +#define TORRENT_USE_SSL 0 +#endif + +#if defined TORRENT_SSL_PEERS && !TORRENT_USE_SSL +#error compiling with TORRENT_SSL_PEERS requires TORRENT_USE_OPENSSL or TORRENT_USE_GNUTLS +#endif + +#include "libtorrent/aux_/export.hpp" + +namespace libtorrent {} + +// create alias +namespace lt = libtorrent; + +#endif // TORRENT_CONFIG_HPP_INCLUDED diff --git a/docs/include/libtorrent/copy_ptr.hpp b/docs/include/libtorrent/copy_ptr.hpp new file mode 100644 index 0000000..c2cec73 --- /dev/null +++ b/docs/include/libtorrent/copy_ptr.hpp @@ -0,0 +1,68 @@ +/* + +Copyright (c) 2010, 2016-2019, Arvid Norberg +Copyright (c) 2017, Matthew Fioravante +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_COPY_PTR +#define TORRENT_COPY_PTR + +#include + +namespace libtorrent { + + template + struct copy_ptr + { + copy_ptr() = default; + explicit copy_ptr(T* t): m_ptr(t) {} + copy_ptr(copy_ptr const& p): m_ptr(p.m_ptr ? new T(*p.m_ptr) : nullptr) {} + copy_ptr(copy_ptr&& p) noexcept = default; + + void reset(T* t = nullptr) { m_ptr.reset(t); } + copy_ptr& operator=(copy_ptr const& p) & + { + if (m_ptr == p.m_ptr) return *this; + m_ptr.reset(p.m_ptr ? new T(*p.m_ptr) : nullptr); + return *this; + } + copy_ptr& operator=(copy_ptr&& p) & noexcept = default; + T* operator->() { return m_ptr.get(); } + T const* operator->() const { return m_ptr.get(); } + T& operator*() { return *m_ptr; } + T const& operator*() const { return *m_ptr; } + void swap(copy_ptr& p) { std::swap(*this, p); } + explicit operator bool() const { return m_ptr.get() != nullptr; } + private: + std::unique_ptr m_ptr; + }; +} + +#endif // TORRENT_COPY_PTR diff --git a/docs/include/libtorrent/crc32c.hpp b/docs/include/libtorrent/crc32c.hpp new file mode 100644 index 0000000..300fb8e --- /dev/null +++ b/docs/include/libtorrent/crc32c.hpp @@ -0,0 +1,46 @@ +/* + +Copyright (c) 2010, 2014, 2016-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CRC32C_HPP_INCLUDE +#define TORRENT_CRC32C_HPP_INCLUDE + +#include +#include "libtorrent/aux_/export.hpp" + +namespace libtorrent { + + // this is the crc32c (Castagnoli) polynomial + TORRENT_EXTRA_EXPORT std::uint32_t crc32c_32(std::uint32_t); + TORRENT_EXTRA_EXPORT std::uint32_t crc32c(std::uint64_t const*, int); +} + +#endif diff --git a/docs/include/libtorrent/create_torrent.hpp b/docs/include/libtorrent/create_torrent.hpp new file mode 100644 index 0000000..d9232c5 --- /dev/null +++ b/docs/include/libtorrent/create_torrent.hpp @@ -0,0 +1,577 @@ +/* + +Copyright (c) 2008-2022, Arvid Norberg +Copyright (c) 2016-2017, 2019, Alden Torres +Copyright (c) 2016, Markus +Copyright (c) 2017, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CREATE_TORRENT_HPP_INCLUDED +#define TORRENT_CREATE_TORRENT_HPP_INCLUDED + +#include "libtorrent/bencode.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/session_params.hpp" // for disk_io_constructor_type +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/path.hpp" // for combine_path etc. +#include "libtorrent/fwd.hpp" +#include "libtorrent/aux_/throw.hpp" +#include "libtorrent/index_range.hpp" + +#include +#include +#include + +// OVERVIEW +// +// This section describes the functions and classes that are used +// to create torrent files. It is a layered API with low level classes +// and higher level convenience functions. A torrent is created in 4 +// steps: +// +// 1. first the files that will be part of the torrent are determined. +// 2. the torrent properties are set, such as tracker url, web seeds, +// DHT nodes etc. +// 3. Read through all the files in the torrent, SHA-1 all the data +// and set the piece hashes. +// 4. The torrent is bencoded into a file or buffer. +// +// If there are a lot of files and or deep directory hierarchies to +// traverse, step one can be time consuming. +// +// Typically step 3 is by far the most time consuming step, since it +// requires to read all the bytes from all the files in the torrent. +// +// All of these classes and functions are declared by including +// ``libtorrent/create_torrent.hpp``. +// +// example: +// +// .. code:: c++ +// +// file_storage fs; +// +// // recursively adds files in directories +// add_files(fs, "./my_torrent"); +// +// create_torrent t(fs); +// t.add_tracker("http://my.tracker.com/announce"); +// t.set_creator("libtorrent example"); +// +// // reads the files and calculates the hashes +// set_piece_hashes(t, "."); +// +// ofstream out("my_torrent.torrent", std::ios_base::binary); +// std::vector buf = t.generate_buf(); +// out.write(buf.data(), buf.size()); +// +// // alternatively, generate an entry and encode it directly to an ostream +// // iterator +// bencode(std::ostream_iterator(out), t.generate()); +// +namespace libtorrent { + + // hidden + using create_flags_t = flags::bitfield_flag; + + // This class holds state for creating a torrent. After having added + // all information to it, call create_torrent::generate() to generate + // the torrent. The entry that's returned can then be bencoded into a + // .torrent file using bencode(). + struct TORRENT_EXPORT create_torrent + { +#if TORRENT_ABI_VERSION == 1 + using flags_t = create_flags_t; +#endif + +#if TORRENT_ABI_VERSION <= 2 + // This will insert pad files to align the files to piece boundaries, for + // optimized disk-I/O. This will minimize the number of bytes of pad- + // files, to keep the impact down for clients that don't support + // them. + // incompatible with v2 metadata, ignored + TORRENT_DEPRECATED static constexpr create_flags_t optimize_alignment = 0_bit; +#endif +#if TORRENT_ABI_VERSION == 1 + // same as optimize_alignment, for backwards compatibility + TORRENT_DEPRECATED static constexpr create_flags_t optimize = 0_bit; +#endif + +#if TORRENT_ABI_VERSION <= 2 + // This will create a merkle hash tree torrent. A merkle torrent cannot + // be opened in clients that don't specifically support merkle torrents. + // The benefit is that the resulting torrent file will be much smaller and + // not grow with more pieces. When this option is specified, it is + // recommended to have a fairly small piece size, say 64 kiB. + // When creating merkle torrents, the full hash tree is also generated + // and should be saved off separately. It is accessed through the + // create_torrent::merkle_tree() function. + // support for BEP 30 merkle torrents has been removed + TORRENT_DEPRECATED static constexpr create_flags_t merkle = 1_bit; +#endif + + // This will include the file modification time as part of the torrent. + // This is not enabled by default, as it might cause problems when you + // create a torrent from separate files with the same content, hoping to + // yield the same info-hash. If the files have different modification times, + // with this option enabled, you would get different info-hashes for the + // files. + static constexpr create_flags_t modification_time = 2_bit; + + // If this flag is set, files that are symlinks get a symlink attribute + // set on them and their data will not be included in the torrent. This + // is useful if you need to reconstruct a file hierarchy which contains + // symlinks. + static constexpr create_flags_t symlinks = 3_bit; + + // to create a torrent that can be updated via a *mutable torrent* + // (see `BEP 38`_). This also needs to be enabled for torrents that update + // another torrent. +#if TORRENT_ABI_VERSION <= 2 + // BEP 52 requires files to be piece aligned so all torrents are now compatible + // with BEP 38 + TORRENT_DEPRECATED static constexpr create_flags_t mutable_torrent_support = 4_bit; +#endif + + // Do not generate v1 metadata. The resulting torrent will only be usable by + // clients which support v2. This requires setting all v2 hashes, with + // set_hash2() before calling generate(). Setting v1 hashes (with + // set_hash()) is an error with this flag set. + static constexpr create_flags_t v2_only = 5_bit; + + // do not generate v2 metadata or enforce v2 alignment and padding rules + // this is mainly for tests, not recommended for production use. This + // requires setting all v1 hashes, with set_hash(), before calling + // generate(). Setting v2 hashes (with set_hash2()) is an error with + // this flag set. + static constexpr create_flags_t v1_only = 6_bit; + + // This flag only affects v1-only torrents, and is only relevant + // together with the v1_only_flag. This flag will force the + // same file order and padding as a v2 (or hybrid) torrent would have. + // It has the effect of ordering files and inserting pad files to align + // them with piece boundaries. + static constexpr create_flags_t canonical_files = 7_bit; + + // passing this flag to add_files() will ignore file attributes (such as + // executable or hidden) when adding the files to the file storage. + // Since not all filesystems and operating systems support all file + // attributes the resulting torrent may differ depending on where it's + // created. If it's important for torrents to be created consistently + // across systems, this flag should be set. + static constexpr create_flags_t no_attributes = 8_bit; + + // this flag enforces the file layout to be canonical according to the + // bittorrent v2 specification (just like the ``canonical_files`` flag) + // with the one exception that tail padding is not added to the last + // file. + // This behavior deviates from the specification but was the way + // libtorrent created torrents in version up to and including 2.0.7. + // This flag is here for backwards compatibility. + static constexpr create_flags_t canonical_files_no_tail_padding = 9_bit; + + // hidden + static constexpr create_flags_t allow_odd_piece_size = 31_bit; + + // The ``piece_size`` is the size of each piece in bytes. It must be a + // power of 2 and a minimum of 16 kiB. If a piece size of 0 is + // specified, a piece_size will be set automatically. + // Piece sizes greater than 128 MiB are considered unreasonable and will + // be rejected (with an lt::system_error exception). + // + // The ``flags`` arguments specifies options for the torrent creation. It can + // be any combination of the flags defined by create_flags_t. + // + // The file_storage (``fs``) parameter defines the files, sizes and + // their properties for the torrent to be created. Set this up first, + // before passing it to the create_torrent constructor. + // + // The overload that takes a ``torrent_info`` object will make a verbatim + // copy of its info dictionary (to preserve the info-hash). The copy of + // the info dictionary will be used by create_torrent::generate(). This means + // that none of the member functions of create_torrent that affects + // the content of the info dictionary (such as set_hash()), will + // have any affect. Instead of using this overload, consider using + // write_torrent_file() instead. + // + // .. warning:: + // The file_storage and torrent_info objects must stay alive for the + // entire duration of the create_torrent object. + // + explicit create_torrent(file_storage& fs, int piece_size = 0 + , create_flags_t flags = {}); + explicit create_torrent(torrent_info const& ti); + +#if TORRENT_ABI_VERSION <= 2 + TORRENT_DEPRECATED + explicit create_torrent(file_storage& fs, int piece_size + , int, create_flags_t flags = {}, int = -1) + : create_torrent(fs, piece_size, flags) {} +#endif + + // internal + ~create_torrent(); + + // This function will generate the .torrent file as a bencode tree, or a + // bencoded into a buffer. + // In order to encode the entry into a flat file, use the bencode() function. + // + // The function returning an entry may be useful to add custom entries + // to the torrent file before bencoding it and saving it to disk. + // + // Whether the resulting torrent object is v1, v2 or hybrid depends on + // whether any of the v1_only or v2_only flags were set on the + // constructor. If neither were set, the resulting torrent depends on + // which hashes were set. If both v1 and v2 hashes were set, a hybrid + // torrent is created. + // + // Any failure will cause this function to throw system_error, with an + // appropriate error message. These are the reasons this call may throw: + // + // * the file storage has 0 files + // * the total size of the file storage is 0 bytes (i.e. it only has + // empty files) + // * not all v1 hashes (set_hash()) and not all v2 hashes (set_hash2()) + // were set + // * for v2 torrents, you may not have a directory with the same name as + // a file. If that's encountered in the file storage, generate() + // fails. + entry generate() const; + std::vector generate_buf() const; + + // returns an immutable reference to the file_storage used to create + // the torrent from. + file_storage const& files() const { return m_files; } + + // Sets the comment for the torrent. The string ``str`` should be utf-8 encoded. + // The comment in a torrent file is optional. + void set_comment(char const* str); + + // Sets the creator of the torrent. The string ``str`` should be utf-8 encoded. + // This is optional. + void set_creator(char const* str); + + // sets the "creation time" field. Defaults to the system clock at the + // time of construction of the create_torrent object. The timestamp is + // specified in seconds, posix time. If the creation date is set to 0, + // the "creation date" field will be omitted from the generated torrent. + void set_creation_date(std::time_t timestamp); + + // This sets the SHA-1 hash for the specified piece (``index``). You are required + // to set the hash for every piece in the torrent before generating it. If you have + // the files on disk, you can use the high level convenience function to do this. + // See set_piece_hashes(). + // A SHA-1 hash of all zeros is internally used to indicate a hash that + // has not been set. Setting such hash will not be considered set when + // calling generate(). + // This function will throw ``std::system_error`` if it is called on an + // object constructed with the v2_only flag. + void set_hash(piece_index_t index, sha1_hash const& h); + + // sets the bittorrent v2 hash for file `file` of the piece `piece`. + // `piece` is relative to the first piece of the file, starting at 0. The + // first piece in the file can be computed with + // file_storage::file_index_at_piece(). + // The hash, `h`, is the root of the merkle tree formed by the piece's + // 16 kiB blocks. Note that piece sizes must be powers-of-2, so all + // per-piece merkle trees are complete. + // A SHA-256 hash of all zeros is internally used to indicate a hash + // that has not been set. Setting such hash will not be considered set + // when calling generate(). + // This function will throw ``std::system_error`` if it is called on an + // object constructed with the v1_only flag. + void set_hash2(file_index_t file, piece_index_t::diff_type piece, sha256_hash const& h); + +#if TORRENT_ABI_VERSION < 3 + // This sets the sha1 hash for this file. This hash will end up under the key ``sha1`` + // associated with this file (for multi-file torrents) or in the root info dictionary + // for single-file torrents. + // .. note:: + // + // with bittorrent v2, this feature is obsolete + TORRENT_DEPRECATED + void set_file_hash(file_index_t index, sha1_hash const& h); +#endif + + // This adds a url seed to the torrent. You can have any number of url seeds. For a + // single file torrent, this should be an HTTP url, pointing to a file with identical + // content as the file of the torrent. For a multi-file torrent, it should point to + // a directory containing a directory with the same name as this torrent, and all the + // files of the torrent in it. + // + // The second function, ``add_http_seed()`` adds an HTTP seed instead. + void add_url_seed(string_view url); + void add_http_seed(string_view url); + + // This adds a DHT node to the torrent. This especially useful if you're creating a + // tracker less torrent. It can be used by clients to bootstrap their DHT node from. + // The node is a hostname and a port number where there is a DHT node running. + // You can have any number of DHT nodes in a torrent. + void add_node(std::pair node); + + // Adds a tracker to the torrent. This is not strictly required, but most torrents + // use a tracker as their main source of peers. The url should be an http:// or udp:// + // url to a machine running a bittorrent tracker that accepts announces for this torrent's + // info-hash. The tier is the fallback priority of the tracker. All trackers with tier 0 are + // tried first (in any order). If all fail, trackers with tier 1 are tried. If all of those + // fail, trackers with tier 2 are tried, and so on. + void add_tracker(string_view url, int tier = 0); + + // This function sets an X.509 certificate in PEM format to the torrent. This makes the + // torrent an *SSL torrent*. An SSL torrent requires that each peer has a valid certificate + // signed by this root certificate. For SSL torrents, all peers are connecting over SSL + // connections. For more information, see the section on ssl-torrents_. + // + // The string is not the path to the cert, it's the actual content of the + // certificate. + void set_root_cert(string_view cert); + + // Sets and queries the private flag of the torrent. + // Torrents with the private flag set ask the client to not use any other + // sources than the tracker for peers, and to not use DHT to advertise itself publicly, + // only the tracker. + void set_priv(bool p) { m_private = p; } + bool priv() const { return m_private; } + + bool is_v2_only() const { return m_v2_only; } + bool is_v1_only() const { return m_v1_only; } + + // returns the number of pieces in the associated file_storage object. + int num_pieces() const { return m_files.num_pieces(); } + + piece_index_t end_piece() const { return m_files.end_piece(); } + + // all piece indices in the torrent to be created + index_range piece_range() const noexcept + { return {piece_index_t{0}, end_piece()}; } + + file_index_t end_file() const { return m_files.end_file(); } + + // all file indices in the torrent to be created + index_range file_range() const noexcept + { return m_files.file_range(); } + + // for v2 and hybrid torrents only, the pieces in the + // specified file, specified as delta from the first piece in the file. + // i.e. the first index is 0. + index_range file_piece_range(file_index_t f) + { return m_files.file_piece_range(f); } + + // the total number of bytes of all files and pad files + std::int64_t total_size() const { return m_files.total_size(); } + + // ``piece_length()`` returns the piece size of all pieces but the + // last one. ``piece_size()`` returns the size of the specified piece. + // these functions are just forwarding to the associated file_storage. + int piece_length() const { return m_files.piece_length(); } + int piece_size(piece_index_t i) const { return m_files.piece_size(i); } + +#if TORRENT_ABI_VERSION <= 2 + // support for BEP 30 merkle torrents has been removed + + // This function returns the merkle hash tree, if the torrent was created as a merkle + // torrent. The tree is created by ``generate()`` and won't be valid until that function + // has been called. When creating a merkle tree torrent, the actual tree itself has to + // be saved off separately and fed into libtorrent the first time you start seeding it, + // through the ``torrent_info::set_merkle_tree()`` function. From that point onwards, the + // tree will be saved in the resume data. + TORRENT_DEPRECATED + std::vector merkle_tree() const { return std::vector(); } +#endif + + // Add similar torrents (by info-hash) or collections of similar torrents. + // Similar torrents are expected to share some files with this torrent. + // Torrents sharing a collection name with this torrent are also expected + // to share files with this torrent. A torrent may have more than one + // collection and more than one similar torrents. For more information, + // see `BEP 38`_. + void add_similar_torrent(sha1_hash ih); + void add_collection(string_view c); + + private: + + file_storage const& m_files; + // if m_info_dict is initialized, it is + // used instead of m_files to generate + // the info dictionary + entry m_info_dict; + + // the URLs to the trackers + std::vector> m_urls; + + std::vector m_url_seeds; + std::vector m_http_seeds; + + aux::vector m_piece_hash; + + // leave this here for now, to preserve ABI between building with + // deprecated functions and without + aux::vector m_filehashes; + + mutable aux::vector m_fileroots; + aux::vector, file_index_t> m_file_piece_hash; + + std::vector m_similar; + std::vector m_collections; + + // dht nodes to add to the routing table/bootstrap from + std::vector> m_nodes; + + // if a creation date is found in the torrent file + // this will be set to that, otherwise it'll be + // 1970, Jan 1 + time_t m_creation_date; + + // if a comment is found in the torrent file + // this will be set to that comment + std::string m_comment; + + // an optional string naming the software used + // to create the torrent file + std::string m_created_by; + + // this is the root cert for SSL torrents + std::string m_root_cert; + + // this is used when creating a torrent. If there's + // only one file there are cases where it's impossible + // to know if it should be written as a multi-file torrent + // or not. e.g. test/test there's one file and one directory + // and they have the same name. + bool m_multifile:1; + + // this is true if the torrent is private. i.e., the client should not + // advertise itself on the DHT for this torrent + bool m_private:1; + + // if set, include the 'mtime' modification time in the + // torrent file + bool m_include_mtime:1; + + // if set, symbolic links are declared as such in + // the torrent file. The full data of the pointed-to + // file is still included + bool m_include_symlinks:1; + + bool m_v2_only:1; + + // only generate v1 metadata and do not enforce v2 padding rules + bool m_v1_only:1; + }; + +namespace aux { + inline void nop(piece_index_t) {} +} + + // Adds the file specified by ``path`` to the file_storage object. In case ``path`` + // refers to a directory, files will be added recursively from the directory. + // + // If specified, the predicate ``p`` is called once for every file and directory that + // is encountered. Files for which ``p`` returns true are added, and directories for + // which ``p`` returns true are traversed. ``p`` must have the following signature: + // + // .. code:: c++ + // + // bool Pred(std::string const& p); + // + // The path that is passed in to the predicate is the full path of the file or + // directory. If no predicate is specified, all files are added, and all directories + // are traversed. + // + // The ".." directory is never traversed. + // + // The ``flags`` argument should be the same as the flags passed to the `create_torrent`_ + // constructor. + TORRENT_EXPORT void add_files(file_storage& fs, std::string const& file + , std::function p, create_flags_t flags = {}); + TORRENT_EXPORT void add_files(file_storage& fs, std::string const& file + , create_flags_t flags = {}); + + // This function will assume that the files added to the torrent file exists at path + // ``p``, read those files and hash the content and set the hashes in the ``create_torrent`` + // object. The optional function ``f`` is called in between every hash that is set. ``f`` + // must have the following signature: + // + // .. code:: c++ + // + // void Fun(piece_index_t); + // + // The overloads taking a settings_pack may be used to configure the + // underlying disk access. Such as ``settings_pack::aio_threads``. + // + // The overloads that don't take an ``error_code&`` may throw an exception in case of a + // file error, the other overloads sets the error code to reflect the error, if any. + TORRENT_EXPORT void set_piece_hashes(create_torrent& t, std::string const& p + , std::function const& f, error_code& ec); + TORRENT_EXPORT void set_piece_hashes(create_torrent& t, std::string const& p + , settings_interface const& settings + , std::function const& f, error_code& ec); + TORRENT_EXPORT void set_piece_hashes(create_torrent& t, std::string const& p + , settings_interface const& settings, disk_io_constructor_type disk_io + , std::function const& f, error_code& ec); + inline void set_piece_hashes(create_torrent& t, std::string const& p, error_code& ec) + { + set_piece_hashes(t, p, aux::nop, ec); + } +#ifndef BOOST_NO_EXCEPTIONS + inline void set_piece_hashes(create_torrent& t, std::string const& p) + { + error_code ec; + set_piece_hashes(t, p, aux::nop, ec); + if (ec) aux::throw_ex(ec); + } + inline void set_piece_hashes(create_torrent& t, std::string const& p + , std::function const& f) + { + error_code ec; + set_piece_hashes(t, p, f, ec); + if (ec) aux::throw_ex(ec); + } + inline void set_piece_hashes(create_torrent& t, std::string const& p + , settings_interface const& settings + , std::function const& f) + { + error_code ec; + set_piece_hashes(t, p, settings, f, ec); + if (ec) aux::throw_ex(ec); + } +#endif + +namespace aux { + TORRENT_EXTRA_EXPORT file_flags_t get_file_attributes(std::string const& p); + TORRENT_EXTRA_EXPORT std::string get_symlink_path(std::string const& p); +} + +} + +#endif diff --git a/docs/include/libtorrent/deadline_timer.hpp b/docs/include/libtorrent/deadline_timer.hpp new file mode 100644 index 0000000..723ed9c --- /dev/null +++ b/docs/include/libtorrent/deadline_timer.hpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2009, 2015, 2017-2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DEADLINE_TIMER_HPP_INCLUDED +#define TORRENT_DEADLINE_TIMER_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#else +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // SIMULATOR + +namespace libtorrent { + +#if defined TORRENT_BUILD_SIMULATOR + using deadline_timer = sim::asio::high_resolution_timer; +#else + using deadline_timer = boost::asio::high_resolution_timer; +#endif +} + +#endif // TORRENT_DEADLINE_TIMER_HPP_INCLUDED diff --git a/docs/include/libtorrent/debug.hpp b/docs/include/libtorrent/debug.hpp new file mode 100644 index 0000000..0211d98 --- /dev/null +++ b/docs/include/libtorrent/debug.hpp @@ -0,0 +1,288 @@ +/* + +Copyright (c) 2003, 2010, 2012-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DEBUG_HPP_INCLUDED +#define TORRENT_DEBUG_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" + +#if TORRENT_USE_ASSERTS +#include +#endif + +#if defined TORRENT_ASIO_DEBUGGING + +#include "libtorrent/time.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include +#include + +#ifdef __MACH__ +#include +#include +#include + +const mach_msg_type_number_t task_events_info_count = TASK_EVENTS_INFO_COUNT; +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +std::string demangle(char const* name); + +namespace libtorrent { + + struct async_t + { + async_t() : refs(0) {} + std::string stack; + int refs; + }; + + // defined in session_impl.cpp + TORRENT_EXTRA_EXPORT extern std::map _async_ops; + TORRENT_EXTRA_EXPORT extern int _async_ops_nthreads; + TORRENT_EXTRA_EXPORT extern std::mutex _async_ops_mutex; + + // timestamp -> operation + struct wakeup_t + { + time_point timestamp; + std::uint64_t context_switches; + char const* operation; + }; + TORRENT_EXTRA_EXPORT extern std::deque _wakeups; + + inline bool has_outstanding_async(char const* name) + { + std::lock_guard l(_async_ops_mutex); + std::map::iterator i = _async_ops.find(name); + return i != _async_ops.end(); + } + + inline void add_outstanding_async(char const* name) + { + std::lock_guard l(_async_ops_mutex); + async_t& a = _async_ops[name]; + if (a.stack.empty()) + { + char stack_text[10000]; + print_backtrace(stack_text, sizeof(stack_text), 9); + + // skip the stack frame of 'add_outstanding_async' + char* ptr = strchr(stack_text, '\n'); + if (ptr != nullptr) ++ptr; + else ptr = stack_text; + a.stack = ptr; + } + ++a.refs; + } + + inline void complete_async(char const* name) + { + std::lock_guard l(_async_ops_mutex); + async_t& a = _async_ops[name]; + TORRENT_ASSERT(a.refs > 0); + --a.refs; + + // don't let this grow indefinitely + if (_wakeups.size() < 100000) + { + _wakeups.push_back(wakeup_t()); + wakeup_t& w = _wakeups.back(); + w.timestamp = clock_type::now(); +#ifdef __MACH__ + task_events_info teinfo; + mach_msg_type_number_t t_info_count = task_events_info_count; + task_info(mach_task_self(), TASK_EVENTS_INFO, + reinterpret_cast(&teinfo), &t_info_count); + w.context_switches = static_cast(teinfo.csw); +#else + w.context_switches = 0; +#endif + w.operation = name; + } + } + + inline void async_inc_threads() + { + std::lock_guard l(_async_ops_mutex); + ++_async_ops_nthreads; + } + + inline void async_dec_threads() + { + std::lock_guard l(_async_ops_mutex); + --_async_ops_nthreads; + } + + inline int log_async() + { + std::lock_guard l(_async_ops_mutex); + int ret = 0; + for (auto const& op : _async_ops) + { + if (op.second.refs <= _async_ops_nthreads - 1) continue; + ret += op.second.refs; + std::printf("%s: (%d)\n%s\n", op.first.c_str(), op.second.refs, op.second.stack.c_str()); + } + return ret; + } + + struct handler_alloc_t + { + std::size_t capacity; + std::set> allocations; + }; + // defined in session_impl.cpp + extern std::map _handler_storage; + extern std::mutex _handler_storage_mutex; + extern bool _handler_logger_registered; + + inline void log_handler_allocators() noexcept + { + static char const* const handler_names[] = { + "write_handler", + "read_handler", + "udp_handler", + "tick_handler", + "abort_handler", + "defer_handler", + "utp_handler", + "submit_handler", + }; + std::lock_guard l(_handler_storage_mutex); + std::printf("handler allocator storage:\n\n"); + for (auto const& e : _handler_storage) + { + std::size_t allocated = 0; + std::string handler_name; + // pick the largest allocation, in case the storage was used for + // different handlers + for (auto const& a : e.second.allocations) + { + if (allocated >= a.second) continue; + allocated = a.second; + handler_name = demangle(e.second.allocations.begin()->first->name()); + } + + std::printf("%15s: capacity: %-3d allocated: %-3d handler: %s\n" + , handler_names[e.first], int(e.second.capacity), int(allocated), handler_name.c_str()); + } + } + + template + void record_handler_allocation(int const type, std::size_t const capacity) + { + std::lock_guard l(_handler_storage_mutex); + auto& e = _handler_storage[type]; + e.capacity = capacity; + e.allocations.emplace(&typeid(Handler), sizeof(Handler)); + if (!_handler_logger_registered) + { + std::atexit(&log_handler_allocators); + _handler_logger_registered = true; + } + } +} + +#define ADD_OUTSTANDING_ASYNC(x) add_outstanding_async(x) +#define COMPLETE_ASYNC(x) complete_async(x) + +#else + +#define ADD_OUTSTANDING_ASYNC(x) do {} TORRENT_WHILE_0 +#define COMPLETE_ASYNC(x) do {} TORRENT_WHILE_0 + +#endif // TORRENT_ASIO_DEBUGGING + +namespace libtorrent { + +#if TORRENT_USE_ASSERTS + struct TORRENT_EXTRA_EXPORT single_threaded + { + single_threaded(): m_id() {} + ~single_threaded() { m_id = std::thread::id(); } + bool is_single_thread() const + { + if (m_id == std::thread::id()) + { + m_id = std::this_thread::get_id(); + return true; + } + return m_id == std::this_thread::get_id(); + } + bool is_not_thread() const + { + if (m_id == std::thread::id()) return true; + return m_id != std::this_thread::get_id(); + } + + void thread_started() + { m_id = std::this_thread::get_id(); } + + private: + mutable std::thread::id m_id; + }; +#else + struct single_threaded { + bool is_single_thread() const { return true; } + void thread_started() {} + bool is_not_thread() const {return true; } + }; +#endif + +#if TORRENT_USE_ASSERTS + struct increment_guard + { + int& m_cnt; + explicit increment_guard(int& c) : m_cnt(c) { TORRENT_ASSERT(m_cnt >= 0); ++m_cnt; } + ~increment_guard() { --m_cnt; TORRENT_ASSERT(m_cnt >= 0); } + private: + increment_guard(increment_guard const&); + increment_guard operator=(increment_guard const&); + }; +#define TORRENT_INCREMENT(x) increment_guard inc_(x) +#else +#define TORRENT_INCREMENT(x) do {} TORRENT_WHILE_0 +#endif +} + +#endif // TORRENT_DEBUG_HPP_INCLUDED diff --git a/docs/include/libtorrent/disabled_disk_io.hpp b/docs/include/libtorrent/disabled_disk_io.hpp new file mode 100644 index 0000000..4487b92 --- /dev/null +++ b/docs/include/libtorrent/disabled_disk_io.hpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2016, 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISABLED_DISK_IO +#define TORRENT_DISABLED_DISK_IO + +#include "libtorrent/config.hpp" +#include "libtorrent/io_context.hpp" + +#include + +namespace libtorrent { + + struct counters; + struct disk_interface; + struct settings_interface; + + // creates a disk io object that discards all data written to it, and only + // returns zero-buffers when read from. May be useful for testing and + // benchmarking. + TORRENT_EXPORT std::unique_ptr disabled_disk_io_constructor( + io_context& ios, settings_interface const&, counters& cnt); +} + +#endif + diff --git a/docs/include/libtorrent/disk_buffer_holder.hpp b/docs/include/libtorrent/disk_buffer_holder.hpp new file mode 100644 index 0000000..b88de7a --- /dev/null +++ b/docs/include/libtorrent/disk_buffer_holder.hpp @@ -0,0 +1,112 @@ +/* + +Copyright (c) 2008-2009, 2013-2019, Arvid Norberg +Copyright (c) 2018, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_BUFFER_HOLDER_HPP_INCLUDED +#define TORRENT_DISK_BUFFER_HOLDER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include + +namespace libtorrent { + + // the interface for freeing disk buffers, used by the disk_buffer_holder. + // when implementing disk_interface, this must also be implemented in order + // to return disk buffers back to libtorrent + struct TORRENT_EXPORT buffer_allocator_interface + { + virtual void free_disk_buffer(char* b) = 0; + protected: + ~buffer_allocator_interface() = default; + }; + + // The disk buffer holder acts like a ``unique_ptr`` that frees a disk buffer + // when it's destructed + // + // If this buffer holder is moved-from, default constructed or reset, + // ``data()`` will return nullptr. + struct TORRENT_EXPORT disk_buffer_holder + { + disk_buffer_holder& operator=(disk_buffer_holder&&) & noexcept; + disk_buffer_holder(disk_buffer_holder&&) noexcept; + + disk_buffer_holder& operator=(disk_buffer_holder const&) = delete; + disk_buffer_holder(disk_buffer_holder const&) = delete; + + // construct a buffer holder that will free the held buffer + // using a disk buffer pool directly (there's only one + // disk_buffer_pool per session) + disk_buffer_holder(buffer_allocator_interface& alloc + , char* buf, int sz) noexcept; + + // default construct a holder that does not own any buffer + disk_buffer_holder() noexcept = default; + + // frees disk buffer held by this object + ~disk_buffer_holder(); + + // return a pointer to the held buffer, if any. Otherwise returns nullptr. + char* data() const noexcept { return m_buf; } + + // free the held disk buffer, if any, and clear the holder. This sets the + // holder object to a default-constructed state + void reset(); + + // swap pointers of two disk buffer holders. + void swap(disk_buffer_holder& h) noexcept + { + using std::swap; + swap(h.m_allocator, m_allocator); + swap(h.m_buf, m_buf); + swap(h.m_size, m_size); + } + + // if this returns true, the buffer may not be modified in place + bool is_mutable() const noexcept { return false; } + + // implicitly convertible to true if the object is currently holding a + // buffer + explicit operator bool() const noexcept { return m_buf != nullptr; } + + std::ptrdiff_t size() const { return m_size; } + + private: + + buffer_allocator_interface* m_allocator = nullptr; + char* m_buf = nullptr; + int m_size = 0; + }; + +} + +#endif diff --git a/docs/include/libtorrent/disk_interface.hpp b/docs/include/libtorrent/disk_interface.hpp new file mode 100644 index 0000000..d8fdb76 --- /dev/null +++ b/docs/include/libtorrent/disk_interface.hpp @@ -0,0 +1,428 @@ +/* + +Copyright (c) 2014-2022, Arvid Norberg +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2018, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_INTERFACE_HPP +#define TORRENT_DISK_INTERFACE_HPP + +#include "libtorrent/bdecode.hpp" + +#include +#include + +#include "libtorrent/fwd.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/session_types.hpp" + +// OVERVIEW +// +// The disk I/O can be customized in libtorrent. In previous versions, the +// customization was at the level of each torrent. Now, the customization point +// is at the session level. All torrents added to a session will use the same +// disk I/O subsystem, as determined by the disk_io_constructor (in +// session_params). +// +// This allows the disk subsystem to also customize threading and disk job +// management. +// +// To customize the disk subsystem, implement disk_interface and provide a +// factory function to the session constructor (via session_params). +// +// Example use: +// +// .. include:: ../examples/custom_storage.cpp +// :code: c++ +// :tab-width: 2 +// :start-after: -- example begin +// :end-before: // -- example end +namespace libtorrent { + + struct disk_observer; + struct counters; + + struct storage_holder; + + using file_open_mode_t = flags::bitfield_flag; + + // internal + // this is a bittorrent constant + constexpr int default_block_size = 0x4000; + +namespace file_open_mode { + // open the file for reading only + constexpr file_open_mode_t read_only{}; + + // open the file for writing only + constexpr file_open_mode_t write_only = 0_bit; + + // open the file for reading and writing + constexpr file_open_mode_t read_write = 1_bit; + + // the mask for the bits determining read or write mode + constexpr file_open_mode_t rw_mask = read_only | write_only | read_write; + + // open the file in sparse mode (if supported by the + // filesystem). + constexpr file_open_mode_t sparse = 2_bit; + + // don't update the access timestamps on the file (if + // supported by the operating system and filesystem). + // this generally improves disk performance. + constexpr file_open_mode_t no_atime = 3_bit; + + // When this is not set, the kernel is hinted that access to this file will + // be made sequentially. + constexpr file_open_mode_t random_access = 5_bit; + +#if TORRENT_ABI_VERSION == 1 + // prevent the file from being opened by another process + // while it's still being held open by this handle + constexpr file_open_mode_t locked TORRENT_DEPRECATED = 6_bit; +#endif + + // the file is memory mapped + constexpr file_open_mode_t mmapped = 7_bit; +} + + // this contains information about a file that's currently open by the + // libtorrent disk I/O subsystem. It's associated with a single torrent. + struct TORRENT_EXPORT open_file_state + { + // the index of the file this entry refers to into the ``file_storage`` + // file list of this torrent. This starts indexing at 0. + file_index_t file_index; + + // ``open_mode`` is a bitmask of the file flags this file is currently + // opened with. For possible flags, see file_open_mode_t. + // + // Note that the read/write mode is not a bitmask. The two least significant bits are used + // to represent the read/write mode. Those bits can be masked out using the ``rw_mask`` constant. + file_open_mode_t open_mode; + + // a (high precision) timestamp of when the file was last used. + time_point last_use; + }; + + using disk_job_flags_t = flags::bitfield_flag; + + // The disk_interface is the customization point for disk I/O in libtorrent. + // implement this interface and provide a factory function to the session constructor + // use custom disk I/O. All functions on the disk subsystem (implementing + // disk_interface) are called from within libtorrent's network thread. For + // disk I/O to be performed in a separate thread, the disk subsystem has to + // manage that itself. + // + // Although the functions are called ``async_*``, they do not technically + // *have* to be asynchronous, but they support being asynchronous, by + // expecting the result passed back into a callback. The callbacks must be + // posted back onto the network thread via the io_context object passed into + // the constructor. The callbacks will be run in the network thread. + struct TORRENT_EXPORT disk_interface + { + // force making a copy of the cached block, rather than getting a + // reference to a block already in the cache. This is used the block is + // expected to be overwritten very soon, by async_write()`, and we need + // access to the previous content. + static constexpr disk_job_flags_t force_copy = 0_bit; + + // hint that there may be more disk operations with sequential access to + // the file + static constexpr disk_job_flags_t sequential_access = 3_bit; + + // don't keep the read block in cache. This is a hint that this block is + // unlikely to be read again anytime soon, and caching it would be + // wasteful. + static constexpr disk_job_flags_t volatile_read = 4_bit; + + // compute a v1 piece hash. This is only used by the async_hash() call. + // If this flag is not set in the async_hash() call, the SHA-1 piece + // hash does not need to be computed. + static constexpr disk_job_flags_t v1_hash = 5_bit; + + // this flag instructs a hash job that we just completed this piece, and + // it should be flushed to disk + static constexpr disk_job_flags_t flush_piece = 7_bit; + + // this is called when a new torrent is added. The shared_ptr can be + // used to hold the internal torrent object alive as long as there are + // outstanding disk operations on the storage. + // The returned storage_holder is an owning reference to the underlying + // storage that was just created. It is fundamentally a storage_index_t + virtual storage_holder new_torrent(storage_params const& p + , std::shared_ptr const& torrent) = 0; + + // remove the storage with the specified index. This is not expected to + // delete any files from disk, just to clean up any resources associated + // with the specified storage. + virtual void remove_torrent(storage_index_t) = 0; + + // perform a read or write operation from/to the specified storage + // index and the specified request. When the operation completes, call + // handler possibly with a disk_buffer_holder, holding the buffer with + // the result. Flags may be set to affect the read operation. See + // disk_job_flags_t. + // + // The disk_observer is a callback to indicate that + // the store buffer/disk write queue is below the watermark to let peers + // start writing buffers to disk again. When ``async_write()`` returns + // ``true``, indicating the write queue is full, the peer will stop + // further writes and wait for the passed-in ``disk_observer`` to be + // notified before resuming. + // + // Note that for ``async_read``, the peer_request (``r``) is not + // necessarily aligned to blocks (but it is most of the time). However, + // all writes (passed to ``async_write``) are guaranteed to be block + // aligned. + virtual void async_read(storage_index_t storage, peer_request const& r + , std::function handler + , disk_job_flags_t flags = {}) = 0; + virtual bool async_write(storage_index_t storage, peer_request const& r + , char const* buf, std::shared_ptr o + , std::function handler + , disk_job_flags_t flags = {}) = 0; + + // Compute hash(es) for the specified piece. Unless the v1_hash flag is + // set (in ``flags``), the SHA-1 hash of the whole piece does not need + // to be computed. + // + // The `v2` span is optional and can be empty, which means v2 hashes + // should not be computed. If v2 is non-empty it must be at least large + // enough to hold all v2 blocks in the piece, and this function will + // fill in the span with the SHA-256 block hashes of the piece. + virtual void async_hash(storage_index_t storage, piece_index_t piece, span v2 + , disk_job_flags_t flags + , std::function handler) = 0; + + // computes the v2 hash (SHA-256) of a single block. The block at + // ``offset`` in piece ``piece``. + virtual void async_hash2(storage_index_t storage, piece_index_t piece, int offset, disk_job_flags_t flags + , std::function handler) = 0; + + // called to request the files for the specified storage/torrent be + // moved to a new location. It is the disk I/O object's responsibility + // to synchronize this with any currently outstanding disk operations to + // the storage. Whether files are replaced at the destination path or + // not is controlled by ``flags`` (see move_flags_t). + virtual void async_move_storage(storage_index_t storage, std::string p, move_flags_t flags + , std::function handler) = 0; + + // This is called on disk I/O objects to request they close all open + // files for the specified storage/torrent. If file handles are not + // pooled/cached, it can be a no-op. For truly asynchronous disk I/O, + // this should provide at least one point in time when all files are + // closed. It is possible that later asynchronous operations will + // re-open some of the files, by the time this completion handler is + // called, that's fine. + virtual void async_release_files(storage_index_t storage + , std::function handler = std::function()) = 0; + + // this is called when torrents are added to validate their resume data + // against the files on disk. This function is expected to do a few things: + // + // if ``links`` is non-empty, it contains a string for each file in the + // torrent. The string being a path to an existing identical file. The + // default behavior is to create hard links of those files into the + // storage of the new torrent (specified by ``storage``). An empty + // string indicates that there is no known identical file. This is part + // of the "mutable torrent" feature, where files can be reused from + // other torrents. + // + // The ``resume_data`` points the resume data passed in by the client. + // + // If the ``resume_data->flags`` field has the seed_mode flag set, all + // files/pieces are expected to be on disk already. This should be + // verified. Not just the existence of the file, but also that it has + // the correct size. + // + // Any file with a piece set in the ``resume_data->have_pieces`` bitmask + // should exist on disk, this should be verified. Pad files and files + // with zero priority may be skipped. + virtual void async_check_files(storage_index_t storage + , add_torrent_params const* resume_data + , aux::vector links + , std::function handler) = 0; + + // This is called when a torrent is stopped. It gives the disk I/O + // object an opportunity to flush any data to disk that's currently kept + // cached. This function should at least do the same thing as + // async_release_files(). + virtual void async_stop_torrent(storage_index_t storage + , std::function handler = std::function()) = 0; + + // This function is called when the name of a file in the specified + // storage has been requested to be renamed. The disk I/O object is + // responsible for renaming the file without racing with other + // potentially outstanding operations against the file (such as read, + // write, move, etc.). + virtual void async_rename_file(storage_index_t storage + , file_index_t index, std::string name + , std::function handler) = 0; + + // This function is called when some file(s) on disk have been requested + // to be removed by the client. ``storage`` indicates which torrent is + // referred to. See session_handle for ``remove_flags_t`` flags + // indicating which files are to be removed. + // e.g. session_handle::delete_files - delete all files + // session_handle::delete_partfile - only delete part file. + virtual void async_delete_files(storage_index_t storage, remove_flags_t options + , std::function handler) = 0; + + // This is called to set the priority of some or all files. Changing the + // priority from or to 0 may involve moving data to and from the + // partfile. The disk I/O object is responsible for correctly + // synchronizing this work to not race with any potentially outstanding + // asynchronous operations affecting these files. + // + // ``prio`` is a vector of the file priority for all files. If it's + // shorter than the total number of files in the torrent, they are + // assumed to be set to the default priority. + virtual void async_set_file_priority(storage_index_t storage + , aux::vector prio + , std::function)> handler) = 0; + + // This is called when a piece fails the hash check, to ensure there are + // no outstanding disk operations to the piece before blocks are + // re-requested from peers to overwrite the existing blocks. The disk I/O + // object does not need to perform any action other than synchronize + // with all outstanding disk operations to the specified piece before + // posting the result back. + virtual void async_clear_piece(storage_index_t storage, piece_index_t index + , std::function handler) = 0; + + // update_stats_counters() is called to give the disk storage an + // opportunity to update gauges in the ``c`` stats counters, that aren't + // updated continuously as operations are performed. This is called + // before a snapshot of the counters are passed to the client. + virtual void update_stats_counters(counters& c) const = 0; + + // Return a list of all the files that are currently open for the + // specified storage/torrent. This is is just used for the client to + // query the currently open files, and which modes those files are open + // in. + virtual std::vector get_status(storage_index_t) const = 0; + + // this is called when the session is starting to shut down. The disk + // I/O object is expected to flush any outstanding write jobs, cancel + // hash jobs and initiate tearing down of any internal threads. If + // ``wait`` is true, this should be asynchronous. i.e. this call should + // not return until all threads have stopped and all jobs have either + // been aborted or completed and the disk I/O object is ready to be + // destructed. + virtual void abort(bool wait) = 0; + + // This will be called after a batch of disk jobs has been issues (via + // the ``async_*`` ). It gives the disk I/O object an opportunity to + // notify any potential condition variables to wake up the disk + // thread(s). The ``async_*`` calls can of course also notify condition + // variables, but doing it in this call allows for batching jobs, by + // issuing the notification once for a collection of jobs. + virtual void submit_jobs() = 0; + + // This is called to notify the disk I/O object that the settings have + // been updated. In the disk io constructor, a settings_interface + // reference is passed in. Whenever these settings are updated, this + // function is called to allow the disk I/O object to react to any + // changed settings relevant to its operations. + virtual void settings_updated() = 0; + + // hidden + virtual ~disk_interface() {} + }; + + // a unique, owning, reference to the storage of a torrent in a disk io + // subsystem (class that implements disk_interface). This is held by the + // internal libtorrent torrent object to tie the storage object allocated + // for a torrent to the lifetime of the internal torrent object. When a + // torrent is removed from the session, this holder is destructed and will + // inform the disk object. + struct TORRENT_EXPORT storage_holder + { + storage_holder() = default; + storage_holder(storage_index_t idx, disk_interface& disk_io) + : m_disk_io(&disk_io) + , m_idx(idx) + {} + ~storage_holder() + { + if (m_disk_io) m_disk_io->remove_torrent(m_idx); + } + + explicit operator bool() const { return m_disk_io != nullptr; } + + operator storage_index_t() const + { + TORRENT_ASSERT(m_disk_io); + return m_idx; + } + + void reset() + { + if (m_disk_io) m_disk_io->remove_torrent(m_idx); + m_disk_io = nullptr; + } + + storage_holder(storage_holder const&) = delete; + storage_holder& operator=(storage_holder const&) = delete; + + storage_holder(storage_holder&& rhs) noexcept + : m_disk_io(rhs.m_disk_io) + , m_idx(rhs.m_idx) + { + rhs.m_disk_io = nullptr; + } + + storage_holder& operator=(storage_holder&& rhs) noexcept + { + if (&rhs == this) return *this; + if (m_disk_io) m_disk_io->remove_torrent(m_idx); + m_disk_io = rhs.m_disk_io; + m_idx = rhs.m_idx; + rhs.m_disk_io = nullptr; + return *this; + } + private: + disk_interface* m_disk_io = nullptr; + storage_index_t m_idx{0}; + }; + +} // namespace libtorrent + +#endif diff --git a/docs/include/libtorrent/disk_observer.hpp b/docs/include/libtorrent/disk_observer.hpp new file mode 100644 index 0000000..4b5d5e9 --- /dev/null +++ b/docs/include/libtorrent/disk_observer.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2010, 2013-2015, 2017, 2020, Arvid Norberg +Copyright (c) 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_OBSERVER_HPP +#define TORRENT_DISK_OBSERVER_HPP + +#include "libtorrent/config.hpp" + +namespace libtorrent { + + struct TORRENT_EXPORT disk_observer + { + // called when the disk cache size has dropped + // below the low watermark again and we can + // resume downloading from peers + virtual void on_disk() = 0; + protected: + ~disk_observer() {} + }; +} + +#endif diff --git a/docs/include/libtorrent/download_priority.hpp b/docs/include/libtorrent/download_priority.hpp new file mode 100644 index 0000000..144dd3e --- /dev/null +++ b/docs/include/libtorrent/download_priority.hpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2017-2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DOWNLOAD_PRIORITY_HPP_INCLUDED +#define TORRENT_DOWNLOAD_PRIORITY_HPP_INCLUDED + +#include "libtorrent/units.hpp" + +namespace libtorrent { + +using download_priority_t = aux::strong_typedef; + +// Don't download the file or piece. Partial pieces may still be downloaded when +// setting file priorities. +constexpr download_priority_t dont_download{0}; + +// The default priority for files and pieces. +constexpr download_priority_t default_priority{4}; + +// The lowest priority for files and pieces. +constexpr download_priority_t low_priority{1}; + +// The highest priority for files and pieces. +constexpr download_priority_t top_priority{7}; + +} + +#endif diff --git a/docs/include/libtorrent/entry.hpp b/docs/include/libtorrent/entry.hpp new file mode 100644 index 0000000..87e0ab6 --- /dev/null +++ b/docs/include/libtorrent/entry.hpp @@ -0,0 +1,334 @@ +/* + +Copyright (c) 2003-2009, 2013-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ENTRY_HPP_INCLUDED +#define TORRENT_ENTRY_HPP_INCLUDED + +/* + * + * This file declares the entry class. It is a + * variant-type that can be an integer, list, + * dictionary (map) or a string. This type is + * used to hold bdecoded data (which is the + * encoding BitTorrent messages uses). + * + * it has 4 accessors to access the actual + * type of the object. They are: + * integer() + * string() + * list() + * dict() + * The actual type has to match the type you + * are asking for, otherwise you will get an + * assertion failure. + * When you default construct an entry, it is + * uninitialized. You can initialize it through the + * assignment operator, copy-constructor or + * the constructor that takes a data_type enum. + * + * + */ + +#include "libtorrent/config.hpp" + +#include +#include +#include +#include +#include +#include +#if TORRENT_USE_IOSTREAM +#include +#endif + +#include "libtorrent/assert.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/aux_/aligned_union.hpp" +#include "libtorrent/aux_/strview_less.hpp" + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 + // backwards compatibility + using type_error = system_error; +#endif + struct bdecode_node; + + // The ``entry`` class represents one node in a bencoded hierarchy. It works as a + // variant type, it can be either a list, a dictionary (``std::map``), an integer + // or a string. + class TORRENT_EXPORT entry + { + public: + + // the key is always a string. If a generic entry would be allowed + // as a key, sorting would become a problem (e.g. to compare a string + // to a list). The definition doesn't mention such a limit though. + using dictionary_type = std::map; + using string_type = std::string; + using list_type = std::vector; + using integer_type = std::int64_t; + using preformatted_type = std::vector; + + // the types an entry can have + enum data_type + { + int_t, + string_t, + list_t, + dictionary_t, + undefined_t, + preformatted_t + }; + + // returns the concrete type of the entry + data_type type() const; + + // constructors directly from a specific type. + // The content of the argument is copied into the + // newly constructed entry + entry(dictionary_type); // NOLINT + entry(span); // NOLINT + entry(list_type); // NOLINT + entry(integer_type); // NOLINT + entry(preformatted_type); // NOLINT + + // hidden + template ::value + || std::is_same::value + || std::is_same::value>::type> + entry(U v) // NOLINT + : m_type(undefined_t) + { +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + new(&data) string_type(std::move(v)); + m_type = string_t; + } + + // construct an empty entry of the specified type. + // see data_type enum. + entry(data_type t); // NOLINT + + // hidden + entry(entry const& e); + entry(entry&& e) noexcept; + + // construct from bdecode_node parsed form (see bdecode()) + entry(bdecode_node const& n); // NOLINT + + // hidden + entry(); + + // hidden + ~entry(); + + // copies the structure of the right hand side into this + // entry. + entry& operator=(bdecode_node const&) &; + entry& operator=(entry const&) &; + entry& operator=(entry&&) & noexcept; + entry& operator=(dictionary_type) &; + entry& operator=(span) &; + entry& operator=(list_type) &; + entry& operator=(integer_type) &; + entry& operator=(preformatted_type) &; + + // hidden + template ::value + || std::is_same::value>::type> + entry& operator=(U v) & + { + destruct(); + new(&data) string_type(std::move(v)); + m_type = string_t; +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + return *this; + } + + // The ``integer()``, ``string()``, ``list()`` and ``dict()`` functions + // are accessors that return the respective type. If the ``entry`` object + // isn't of the type you request, the accessor will throw + // system_error. You can ask an ``entry`` for its type through the + // ``type()`` function. + // + // If you want to create an ``entry`` you give it the type you want it to + // have in its constructor, and then use one of the non-const accessors + // to get a reference which you then can assign the value you want it to + // have. + // + // The typical code to get info from a torrent file will then look like + // this: + // + // .. code:: c++ + // + // entry torrent_file; + // // ... + // + // // throws if this is not a dictionary + // entry::dictionary_type const& dict = torrent_file.dict(); + // entry::dictionary_type::const_iterator i; + // i = dict.find("announce"); + // if (i != dict.end()) + // { + // std::string tracker_url = i->second.string(); + // std::cout << tracker_url << "\n"; + // } + // + // + // The following code is equivalent, but a little bit shorter: + // + // .. code:: c++ + // + // entry torrent_file; + // // ... + // + // // throws if this is not a dictionary + // if (entry* i = torrent_file.find_key("announce")) + // { + // std::string tracker_url = i->string(); + // std::cout << tracker_url << "\n"; + // } + // + // + // To make it easier to extract information from a torrent file, the + // class torrent_info exists. + integer_type& integer(); + integer_type const& integer() const; + string_type& string(); + string_type const& string() const; + list_type& list(); + list_type const& list() const; + dictionary_type& dict(); + dictionary_type const& dict() const; + preformatted_type& preformatted(); + preformatted_type const& preformatted() const; + + // swaps the content of *this* with ``e``. + void swap(entry& e); + + // All of these functions requires the entry to be a dictionary, if it + // isn't they will throw ``system_error``. + // + // The non-const versions of the ``operator[]`` will return a reference + // to either the existing element at the given key or, if there is no + // element with the given key, a reference to a newly inserted element at + // that key. + // + // The const version of ``operator[]`` will only return a reference to an + // existing element at the given key. If the key is not found, it will + // throw ``system_error``. + entry& operator[](string_view key); + entry const& operator[](string_view key) const; + + // These functions requires the entry to be a dictionary, if it isn't + // they will throw ``system_error``. + // + // They will look for an element at the given key in the dictionary, if + // the element cannot be found, they will return nullptr. If an element + // with the given key is found, the return a pointer to it. + entry* find_key(string_view key); + entry const* find_key(string_view key) const; + + // returns a pretty-printed string representation + // of the bencoded structure, with JSON-style syntax + std::string to_string(bool single_line = false) const; + + protected: + + void construct(data_type t); + void copy(const entry& e); + void destruct(); + + private: + + aux::aligned_union<1 +#if TORRENT_COMPLETE_TYPES_REQUIRED + // for implementations that require complete types, use char and hope + // for the best + , std::list + , std::map +#else + , list_type + , dictionary_type +#endif + , preformatted_type + , string_type + , integer_type + >::type data; + + // the bitfield is used so that the m_type_queried field still fits, so + // that the ABI is the same for debug builds and release builds. It + // appears to be very hard to match debug builds with debug versions of + // libtorrent + std::uint8_t m_type:7; + + public: + // hidden + // in debug mode this is set to false by bdecode to indicate that the + // program has not yet queried the type of this entry, and should not + // assume that it has a certain type. This is asserted in the accessor + // functions. This does not apply if exceptions are used. + mutable std::uint8_t m_type_queried:1; + }; + + // hidden + TORRENT_EXPORT bool operator==(entry const& lhs, entry const& rhs); + inline bool operator!=(entry const& lhs, entry const& rhs) { return !(lhs == rhs); } + +namespace aux { + + // internal + TORRENT_EXPORT string_view integer_to_str(std::array& buf + , entry::integer_type val); +} + +#if TORRENT_USE_IOSTREAM + // prints the bencoded structure to the ostream as a JSON-style structure. + inline std::ostream& operator<<(std::ostream& os, const entry& e) + { + os << e.to_string(); + return os; + } +#endif + +} + +#endif // TORRENT_ENTRY_HPP_INCLUDED diff --git a/docs/include/libtorrent/enum_net.hpp b/docs/include/libtorrent/enum_net.hpp new file mode 100644 index 0000000..2009f39 --- /dev/null +++ b/docs/include/libtorrent/enum_net.hpp @@ -0,0 +1,234 @@ +/* + +Copyright (c) 2007-2008, 2010, 2014-2021, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ENUM_NET_HPP_INCLUDED +#define TORRENT_ENUM_NET_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#if TORRENT_USE_IFCONF || TORRENT_USE_NETLINK || TORRENT_USE_SYSCTL +#include // for SO_BINDTODEVICE +#endif + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/io_context.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/bind_to_device.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/flags.hpp" + +#include + +namespace libtorrent { + + // internal +using interface_flags = flags::bitfield_flag; + +namespace if_flags { + + // internal + constexpr interface_flags up = 0_bit; + constexpr interface_flags broadcast = 1_bit; + constexpr interface_flags loopback = 2_bit; + constexpr interface_flags pointopoint = 3_bit; + constexpr interface_flags running = 4_bit; + constexpr interface_flags noarp = 5_bit; + constexpr interface_flags promisc = 6_bit; + constexpr interface_flags allmulti = 7_bit; + constexpr interface_flags master = 8_bit; + constexpr interface_flags slave = 9_bit; + constexpr interface_flags multicast = 10_bit; + constexpr interface_flags dynamic = 11_bit; + constexpr interface_flags lower_up = 12_bit; + constexpr interface_flags dormant = 13_bit; +} + +// internal +enum class if_state : std::uint8_t { + + up, + dormant, + lowerlayerdown, + down, + notpresent, + testing, + unknown +}; + +// internal + struct ip_interface + { + address interface_address; + address netmask; + char name[64]{}; + char friendly_name[128]{}; + char description[128]{}; + // an interface is preferred if its address is + // not tentative/duplicate/deprecated + bool preferred = true; + + interface_flags flags = if_flags::up; + if_state state = if_state::unknown; + }; + +// internal + struct ip_route + { + address destination; + address netmask; + address gateway; + address source_hint; + char name[64]{}; + int mtu = 0; + }; + + // returns a list of the configured IP interfaces + // on the machine + TORRENT_EXTRA_EXPORT std::vector enum_net_interfaces(io_context& ios + , error_code& ec); + + TORRENT_EXTRA_EXPORT std::vector enum_routes(io_context& ios + , error_code& ec); + + // returns AF_INET or AF_INET6, depending on the address' family + TORRENT_EXTRA_EXPORT int family(address const& a); + + // return (a1 & mask) == (a2 & mask) + TORRENT_EXTRA_EXPORT bool match_addr_mask(address const& a1 + , address const& a2, address const& mask); + + // return a netmask with the specified address family and the specified + // number of prefix bit set, of the most significant bits in the resulting + // netmask + TORRENT_EXTRA_EXPORT address build_netmask(int bits, int family); + + // return the gateway for the given ip_interface, if there is one. Otherwise + // return nullopt. + TORRENT_EXTRA_EXPORT boost::optional
    get_gateway( + ip_interface const& iface, span routes); + + // returns whether there is a route to the specified device for for any global + // internet address of the specified address family. + TORRENT_EXTRA_EXPORT bool has_internet_route(string_view device, int family + , span routes); + + // returns whether there are *any* routes to the internet in the routing + // table. This can be used to determine if the routing table is fully + // populated or not. + TORRENT_EXTRA_EXPORT bool has_any_internet_route(span routes); + + // attempt to bind socket to the device with the specified name. For systems + // that don't support SO_BINDTODEVICE the socket will be bound to one of the + // IP addresses of the specified device. In this case it is necessary to + // verify the local endpoint of the socket once the connection is established. + // the returned address is the ip the socket was bound to (or address_v4::any() + // in case SO_BINDTODEVICE succeeded and we don't need to verify it). + // TODO: 3 use string_view for device_name + template + address bind_socket_to_device(io_context& ios, Socket& sock + , tcp const& protocol + , char const* device_name, int port, error_code& ec) + { + tcp::endpoint bind_ep(address_v4::any(), std::uint16_t(port)); + + address ip = make_address(device_name, ec); + if (!ec) + { + // this is to cover the case where "0.0.0.0" is considered any IPv4 or + // IPv6 address. If we're asking to be bound to an IPv6 address and + // providing 0.0.0.0 as the device, turn it into "::" + if (ip == address_v4::any() && protocol == boost::asio::ip::tcp::v6()) + ip = address_v6::any(); + bind_ep.address(ip); + // it appears to be an IP. Just bind to that address + sock.bind(bind_ep, ec); + return bind_ep.address(); + } + + ec.clear(); + +#if TORRENT_HAS_BINDTODEVICE + // try to use SO_BINDTODEVICE here, if that exists. If it fails, + // fall back to the mechanism we have below + aux::bind_device(sock, device_name, ec); + if (ec) +#endif + { + ec.clear(); + // TODO: 2 this could be done more efficiently by just looking up + // the interface with the given name, maybe even with if_nametoindex() + std::vector ifs = enum_net_interfaces(ios, ec); + if (ec) return bind_ep.address(); + + bool found = false; + + for (auto const& iface : ifs) + { + // we're looking for a specific interface, and its address + // (which must be of the same family as the address we're + // connecting to) + if (std::strcmp(iface.name, device_name) != 0) continue; + if (iface.interface_address.is_v4() != (protocol == boost::asio::ip::tcp::v4())) + continue; + + bind_ep.address(iface.interface_address); + found = true; + break; + } + + if (!found) + { + ec = error_code(boost::system::errc::no_such_device, generic_category()); + return bind_ep.address(); + } + } + sock.bind(bind_ep, ec); + return bind_ep.address(); + } + + // returns the device name whose local address is ``addr``. If + // no such device is found, an empty string is returned. + TORRENT_EXTRA_EXPORT std::string device_for_address(address addr + , io_context& ios, error_code& ec); + +} + +#endif diff --git a/docs/include/libtorrent/error.hpp b/docs/include/libtorrent/error.hpp new file mode 100644 index 0000000..939d101 --- /dev/null +++ b/docs/include/libtorrent/error.hpp @@ -0,0 +1,53 @@ +/* + +Copyright (c) 2009, 2017, 2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ERROR_HPP_INCLUDED +#define TORRENT_ERROR_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#if defined TORRENT_WINDOWS || defined TORRENT_CYGWIN +// asio assumes that the windows error codes are defined already +#include +#endif + +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + + namespace error = boost::asio::error; +} + +#endif diff --git a/docs/include/libtorrent/error_code.hpp b/docs/include/libtorrent/error_code.hpp new file mode 100644 index 0000000..d34ab0e --- /dev/null +++ b/docs/include/libtorrent/error_code.hpp @@ -0,0 +1,602 @@ +/* + +Copyright (c) 2008-2011, 2013-2021, Arvid Norberg +Copyright (c) 2016-2017, 2019, Steven Siloti +Copyright (c) 2018, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ERROR_CODE_HPP_INCLUDED +#define TORRENT_ERROR_CODE_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/operations.hpp" + +namespace libtorrent { + +namespace errors { + // libtorrent uses boost.system's ``error_code`` class to represent + // errors. libtorrent has its own error category + // libtorrent_category() with the error codes defined by + // error_code_enum. + enum error_code_enum + { + // Not an error + no_error = 0, + // Two torrents has files which end up overwriting each other + file_collision, + // A piece did not match its piece hash + failed_hash_check, + // The .torrent file does not contain a bencoded dictionary at + // its top level + torrent_is_no_dict, + // The .torrent file does not have an ``info`` dictionary + torrent_missing_info, + // The .torrent file's ``info`` entry is not a dictionary + torrent_info_no_dict, + // The .torrent file does not have a ``piece length`` entry + torrent_missing_piece_length, + // The .torrent file does not have a ``name`` entry + torrent_missing_name, + // The .torrent file's name entry is invalid + torrent_invalid_name, + // The length of a file, or of the whole .torrent file is invalid. + // Either negative or not an integer + torrent_invalid_length, + // Failed to parse a file entry in the .torrent + torrent_file_parse_failed, + // The ``pieces`` field is missing or invalid in the .torrent file + torrent_missing_pieces, + // The ``pieces`` string has incorrect length + torrent_invalid_hashes, + // The .torrent file has more pieces than is supported by libtorrent + too_many_pieces_in_torrent, + // The metadata (.torrent file) that was received from the swarm + // matched the info-hash, but failed to be parsed + invalid_swarm_metadata, + // The file or buffer is not correctly bencoded + invalid_bencoding, + // The .torrent file does not contain any files + no_files_in_torrent, + // The string was not properly url-encoded as expected + invalid_escaped_string, + // Operation is not permitted since the session is shutting down + session_is_closing, + // There's already a torrent with that info-hash added to the + // session + duplicate_torrent, + // The supplied torrent_handle is not referring to a valid torrent + invalid_torrent_handle, + // The type requested from the entry did not match its type + invalid_entry_type, + // The specified URI does not contain a valid info-hash + missing_info_hash_in_uri, + // One of the files in the torrent was unexpectedly small. This + // might be caused by files being changed by an external process + file_too_short, + // The URL used an unknown protocol. Currently ``http`` and + // ``https`` (if built with openssl support) are recognized. For + // trackers ``udp`` is recognized as well. + unsupported_url_protocol, + // The URL did not conform to URL syntax and failed to be parsed + url_parse_error, + // The peer sent a piece message of length 0 + peer_sent_empty_piece, + // A bencoded structure was corrupt and failed to be parsed + parse_failed, + // The fast resume file was missing or had an invalid file version + // tag + invalid_file_tag, + // The fast resume file was missing or had an invalid info-hash + missing_info_hash, + // The info-hash did not match the torrent + mismatching_info_hash, + // The URL contained an invalid hostname + invalid_hostname, + // The URL had an invalid port + invalid_port, + // The port is blocked by the port-filter, and prevented the + // connection + port_blocked, + // The IPv6 address was expected to end with "]" + expected_close_bracket_in_address, + // The torrent is being destructed, preventing the operation to + // succeed + destructing_torrent, + // The connection timed out + timed_out, + // The peer is upload only, and we are upload only. There's no point + // in keeping the connection + upload_upload_connection, + // The peer is upload only, and we're not interested in it. There's + // no point in keeping the connection + uninteresting_upload_peer, + // The peer sent an unknown info-hash + invalid_info_hash, + // The torrent is paused, preventing the operation from succeeding + torrent_paused, + // The peer sent an invalid have message, either wrong size or + // referring to a piece that doesn't exist in the torrent + invalid_have, + // The bitfield message had the incorrect size + invalid_bitfield_size, + // The peer kept requesting pieces after it was choked, possible + // abuse attempt. + too_many_requests_when_choked, + // The peer sent a piece message that does not correspond to a + // piece request sent by the client + invalid_piece, + // memory allocation failed + no_memory, + // The torrent is aborted, preventing the operation to succeed + torrent_aborted, + // The peer is a connection to ourself, no point in keeping it + self_connection, + // The peer sent a piece message with invalid size, either negative + // or greater than one block + invalid_piece_size, + // The peer has not been interesting or interested in us for too + // long, no point in keeping it around + timed_out_no_interest, + // The peer has not said anything in a long time, possibly dead + timed_out_inactivity, + // The peer did not send a handshake within a reasonable amount of + // time, it might not be a bittorrent peer + timed_out_no_handshake, + // The peer has been unchoked for too long without requesting any + // data. It might be lying about its interest in us + timed_out_no_request, + // The peer sent an invalid choke message + invalid_choke, + // The peer send an invalid unchoke message + invalid_unchoke, + // The peer sent an invalid interested message + invalid_interested, + // The peer sent an invalid not-interested message + invalid_not_interested, + // The peer sent an invalid piece request message + invalid_request, + // The peer sent an invalid hash-list message (this is part of the + // merkle-torrent extension) + invalid_hash_list, + // The peer sent an invalid hash-piece message (this is part of the + // merkle-torrent extension) + invalid_hash_piece, + // The peer sent an invalid cancel message + invalid_cancel, + // The peer sent an invalid DHT port-message + invalid_dht_port, + // The peer sent an invalid suggest piece-message + invalid_suggest, + // The peer sent an invalid have all-message + invalid_have_all, + // The peer sent an invalid have none-message + invalid_have_none, + // The peer sent an invalid reject message + invalid_reject, + // The peer sent an invalid allow fast-message + invalid_allow_fast, + // The peer sent an invalid extension message ID + invalid_extended, + // The peer sent an invalid message ID + invalid_message, + // The synchronization hash was not found in the encrypted handshake + sync_hash_not_found, + // The encryption constant in the handshake is invalid + invalid_encryption_constant, + // The peer does not support plain text, which is the selected mode + no_plaintext_mode, + // The peer does not support RC4, which is the selected mode + no_rc4_mode, + // The peer does not support any of the encryption modes that the + // client supports + unsupported_encryption_mode, + // The peer selected an encryption mode that the client did not + // advertise and does not support + unsupported_encryption_mode_selected, + // The pad size used in the encryption handshake is of invalid size + invalid_pad_size, + // The encryption handshake is invalid + invalid_encrypt_handshake, + // The client is set to not support incoming encrypted connections + // and this is an encrypted connection + no_incoming_encrypted, + // The client is set to not support incoming regular bittorrent + // connections, and this is a regular connection + no_incoming_regular, + // The client is already connected to this peer-ID + duplicate_peer_id, + // Torrent was removed + torrent_removed, + // The packet size exceeded the upper sanity check-limit + packet_too_large, + + reserved, + + // The web server responded with an error + http_error, + // The web server response is missing a location header + missing_location, + // The web seed redirected to a path that no longer matches the + // .torrent directory structure + invalid_redirection, + // The connection was closed because it redirected to a different + // URL + redirecting, + // The HTTP range header is invalid + invalid_range, + // The HTTP response did not have a content length + no_content_length, + // The IP is blocked by the IP filter + banned_by_ip_filter, + // At the connection limit + too_many_connections, + // The peer is marked as banned + peer_banned, + // The torrent is stopping, causing the operation to fail + stopping_torrent, + // The peer has sent too many corrupt pieces and is banned + too_many_corrupt_pieces, + // The torrent is not ready to receive peers + torrent_not_ready, + // The peer is not completely constructed yet + peer_not_constructed, + // The session is closing, causing the operation to fail + session_closing, + // The peer was disconnected in order to leave room for a + // potentially better peer + optimistic_disconnect, + // The torrent is finished + torrent_finished, + // No UPnP router found + no_router, + // The metadata message says the metadata exceeds the limit + metadata_too_large, + // The peer sent an invalid metadata request message + invalid_metadata_request, + // The peer advertised an invalid metadata size + invalid_metadata_size, + // The peer sent a message with an invalid metadata offset + invalid_metadata_offset, + // The peer sent an invalid metadata message + invalid_metadata_message, + // The peer sent a peer exchange message that was too large + pex_message_too_large, + // The peer sent an invalid peer exchange message + invalid_pex_message, + // The peer sent an invalid tracker exchange message + invalid_lt_tracker_message, + // The peer sent an pex messages too often. This is a possible + // attempt of and attack + too_frequent_pex, + // The operation failed because it requires the torrent to have + // the metadata (.torrent file) and it doesn't have it yet. + // This happens for magnet links before they have downloaded the + // metadata, and also torrents added by URL. + no_metadata, + // The peer sent an invalid ``dont_have`` message. The don't have + // message is an extension to allow peers to advertise that the + // no longer has a piece they previously had. + invalid_dont_have, + // The peer tried to connect to an SSL torrent without connecting + // over SSL. + requires_ssl_connection, + // The peer tried to connect to a torrent with a certificate + // for a different torrent. + invalid_ssl_cert, + // the torrent is not an SSL torrent, and the operation requires + // an SSL torrent + not_an_ssl_torrent, + // peer was banned because its listen port is within a banned port + // range, as specified by the port_filter. + banned_by_port_filter, + // The session_handle is not referring to a valid session_impl + invalid_session_handle, + // the listen socket associated with this request was closed + invalid_listen_socket, + invalid_hash_request, + invalid_hashes, + invalid_hash_reject, + +#if TORRENT_ABI_VERSION == 1 + // these error codes are deprecated, NAT-PMP/PCP error codes have + // been moved to their own category + + // The NAT-PMP router responded with an unsupported protocol version + unsupported_protocol_version TORRENT_DEPRECATED_ENUM = 120, + // You are not authorized to map ports on this NAT-PMP router + natpmp_not_authorized TORRENT_DEPRECATED_ENUM, + // The NAT-PMP router failed because of a network failure + network_failure TORRENT_DEPRECATED_ENUM, + // The NAT-PMP router failed because of lack of resources + no_resources TORRENT_DEPRECATED_ENUM, + // The NAT-PMP router failed because an unsupported opcode was sent + unsupported_opcode TORRENT_DEPRECATED_ENUM, +#else + deprecated_120 = 120, + deprecated_121, + deprecated_122, + deprecated_123, + deprecated_124, +#endif + + // The resume data file is missing the ``file sizes`` entry + missing_file_sizes = 130, + // The resume data file ``file sizes`` entry is empty + no_files_in_resume_data, + // The resume data file is missing the ``pieces`` and ``slots`` entry + missing_pieces, + // The number of files in the resume data does not match the number + // of files in the torrent + mismatching_number_of_files, + // One of the files on disk has a different size than in the fast + // resume file + mismatching_file_size, + // One of the files on disk has a different timestamp than in the + // fast resume file + mismatching_file_timestamp, + // The resume data file is not a dictionary + not_a_dictionary, + // The ``blocks per piece`` entry is invalid in the resume data file + invalid_blocks_per_piece, + // The resume file is missing the ``slots`` entry, which is required + // for torrents with compact allocation. *DEPRECATED* + missing_slots, + // The resume file contains more slots than the torrent + too_many_slots, + // The ``slot`` entry is invalid in the resume data + invalid_slot_list, + // One index in the ``slot`` list is invalid + invalid_piece_index, + // The pieces on disk needs to be re-ordered for the specified + // allocation mode. This happens if you specify sparse allocation + // and the files on disk are using compact storage. The pieces needs + // to be moved to their right position. *DEPRECATED* + pieces_need_reorder, + // this error is returned when asking to save resume data and + // specifying the flag to only save when there's anything new to save + // (torrent_handle::only_if_modified) and there wasn't anything changed. + resume_data_not_modified, + // the save_path in add_torrent_params is not valid + invalid_save_path, + + + // The HTTP header was not correctly formatted + http_parse_error = 150, + // The HTTP response was in the 300-399 range but lacked a location + // header + http_missing_location, + // The HTTP response was encoded with gzip or deflate but + // decompressing it failed + http_failed_decompress, + + + + // The URL specified an i2p address, but no i2p router is configured + no_i2p_router = 160, + // i2p acceptor is not available yet, can't announce without endpoint + no_i2p_endpoint = 161, + + + // The tracker URL doesn't support transforming it into a scrape + // URL. i.e. it doesn't contain "announce. + scrape_not_available = 170, + // invalid tracker response + invalid_tracker_response, + // invalid peer dictionary entry. Not a dictionary + invalid_peer_dict, + // tracker sent a failure message + tracker_failure, + // missing or invalid ``files`` entry + invalid_files_entry, + // missing or invalid ``hash`` entry + invalid_hash_entry, + // missing or invalid ``peers`` and ``peers6`` entry + invalid_peers_entry, + // UDP tracker response packet has invalid size + invalid_tracker_response_length, + // invalid transaction id in UDP tracker response + invalid_tracker_transaction_id, + // invalid action field in UDP tracker response + invalid_tracker_action, + // skipped announce (because it's assumed to be unreachable over the + // given source network interface) + announce_skipped, + +#if TORRENT_ABI_VERSION == 1 + // expected string in bencoded string + expected_string = 190, + // expected colon in bencoded string + expected_colon, + // unexpected end of file in bencoded string + unexpected_eof, + // expected value (list, dict, int or string) in bencoded string + expected_value, + // bencoded recursion depth limit exceeded + depth_exceeded, + // bencoded item count limit exceeded + limit_exceeded, + // integer overflow + overflow, +#endif + + // random number generation failed + no_entropy = 200, + // blocked by SSRF mitigation + ssrf_mitigation, + // blocked because IDNA host names are banned + blocked_by_idna, + + // the torrent file has an unknown meta version + torrent_unknown_version = 210, + // the v2 torrent file has no file tree + torrent_missing_file_tree, + // the torrent contains v2 keys but does not specify meta version 2 + torrent_missing_meta_version, + // the v1 and v2 file metadata does not match + torrent_inconsistent_files, + // one or more files are missing piece layer hashes + torrent_missing_piece_layer, + // a piece layer has the wrong size or failed hash check + torrent_invalid_piece_layer, + // a v2 file entry has no root hash + torrent_missing_pieces_root, + // the v1 and v2 hashes do not describe the same data + torrent_inconsistent_hashes, + // a file in the v2 metadata has the pad attribute set + torrent_invalid_pad_file, + + // the number of error codes + error_code_max + }; + + // HTTP errors are reported in the libtorrent::http_category, with error code enums in + // the ``libtorrent::errors`` namespace. + enum http_errors + { + cont = 100, + ok = 200, + created = 201, + accepted = 202, + no_content = 204, + multiple_choices = 300, + moved_permanently = 301, + moved_temporarily = 302, + not_modified = 304, + bad_request = 400, + unauthorized = 401, + forbidden = 403, + not_found = 404, + internal_server_error = 500, + not_implemented = 501, + bad_gateway = 502, + service_unavailable = 503 + }; + + // hidden + TORRENT_EXPORT boost::system::error_code make_error_code(error_code_enum e); + +} // namespace errors + + // return the instance of the libtorrent_error_category which + // maps libtorrent error codes to human readable error messages. + TORRENT_EXPORT boost::system::error_category& libtorrent_category(); + + // returns the error_category for HTTP errors + TORRENT_EXPORT boost::system::error_category& http_category(); + + using error_code = boost::system::error_code; + using error_condition = boost::system::error_condition; + + // internal + using boost::system::generic_category; + using boost::system::system_category; + + using system_error = boost::system::system_error; + +#ifndef BOOST_NO_EXCEPTIONS +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + inline boost::system::error_category& get_libtorrent_category() + { return libtorrent_category(); } + + TORRENT_DEPRECATED + inline boost::system::error_category& get_http_category() + { return http_category(); } +#endif +#endif + + // used by storage to return errors + // also includes which underlying file the + // error happened on + struct TORRENT_EXPORT storage_error + { + // hidden + storage_error(): file_idx(-1), operation(operation_t::unknown) {} + explicit storage_error(error_code e): ec(e), file_idx(-1), operation(operation_t::unknown) {} + storage_error(error_code e, operation_t const op) + : ec(e), file_idx(-1), operation(op) {} + storage_error(error_code e, file_index_t f, operation_t const op) + : ec(e), file_idx(f), operation(op) {} + + // explicitly converts to true if this object represents an error, and + // false if it does not. + explicit operator bool() const { return ec.value() != 0; } + + // the error that occurred + error_code ec; + + // set and query the index (in the torrent) of the file this error + // occurred on. This may also have special values defined in + // torrent_status. + file_index_t file() const { return file_index_t(file_idx); } + void file(file_index_t f) { file_idx = static_cast(f); } + + private: + // internal + std::int32_t file_idx:24; + + public: + + // A code from operation_t enum, indicating what + // kind of operation failed. + operation_t operation; + +#if TORRENT_ABI_VERSION == 1 + // Returns a string literal representing the file operation + // that failed. If there were no failure, it returns + // an empty string. + TORRENT_DEPRECATED + char const* operation_str() const + { return operation_name(operation); } +#endif + }; + + // internal + std::string print_error(error_code const&); +} // namespace libtorrent + +namespace boost { +namespace system { + + template<> struct is_error_code_enum + { static const bool value = true; }; + + template<> struct is_error_code_enum + { static const bool value = true; }; +} +} + +#endif diff --git a/docs/include/libtorrent/extensions.hpp b/docs/include/libtorrent/extensions.hpp new file mode 100644 index 0000000..8302b9d --- /dev/null +++ b/docs/include/libtorrent/extensions.hpp @@ -0,0 +1,552 @@ +/* + +Copyright (c) 2006-2007, 2011, 2013-2020, Arvid Norberg +Copyright (c) 2014-2019, Steven Siloti +Copyright (c) 2016, Alden Torres +Copyright (c) 2018, Greg Hazel +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_EXTENSIONS_HPP_INCLUDED +#define TORRENT_EXTENSIONS_HPP_INCLUDED + +#include "libtorrent/units.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/peer_info.hpp" // for peer_source_flags_t +#include "libtorrent/torrent_status.hpp" // for torrent_status::state_t + +// OVERVIEW +// +// libtorrent has a plugin interface for implementing extensions to the protocol. +// These can be general extensions for transferring metadata or peer exchange +// extensions, or it could be used to provide a way to customize the protocol +// to fit a particular (closed) network. +// +// In short, the plugin interface makes it possible to: +// +// * register extension messages (sent in the extension handshake), see +// extensions_. +// * add data and parse data from the extension handshake. +// * send extension messages and standard bittorrent messages. +// * override or block the handling of standard bittorrent messages. +// * save and restore state via the session state +// * see all alerts that are posted +// +// a word of caution +// ----------------- +// +// Writing your own plugin is a very easy way to introduce serious bugs such as +// dead locks and race conditions. Since a plugin has access to internal +// structures it is also quite easy to sabotage libtorrent's operation. +// +// All the callbacks are always called from the libtorrent network thread. In +// case portions of your plugin are called from other threads, typically the main +// thread, you cannot use any of the member functions on the internal structures +// in libtorrent, since those require being called from the libtorrent network +// thread . Furthermore, you also need to synchronize your own shared data +// within the plugin, to make sure it is not accessed at the same time from the +// libtorrent thread (through a callback). If you need to send out a message +// from another thread, it is advised to use an internal queue, and do the +// actual sending in ``tick()``. +// +// Since the plugin interface gives you easy access to internal structures, it +// is not supported as a stable API. Plugins should be considered specific to a +// specific version of libtorrent. Although, in practice the internals mostly +// don't change that dramatically. +// +// +// plugin-interface +// ---------------- +// +// The plugin interface consists of three base classes that the plugin may +// implement. These are called plugin, torrent_plugin and peer_plugin. +// They are found in the ```` header. +// +// These plugins are instantiated for each session, torrent and possibly each peer, +// respectively. +// +// For plugins that only need per torrent state, it is enough to only implement +// ``torrent_plugin`` and pass a constructor function or function object to +// ``session::add_extension()`` or ``torrent_handle::add_extension()`` (if the +// torrent has already been started and you want to hook in the extension at +// run-time). +// +// The signature of the function is: +// +// .. code:: c++ +// +// std::shared_ptr (*)(torrent_handle const&, client_data_t); +// +// The second argument is the userdata passed to ``session::add_torrent()`` or +// ``torrent_handle::add_extension()``. +// +// The function should return a ``std::shared_ptr`` which +// may or may not be 0. If it is a nullptr, the extension is simply ignored +// for this torrent. If it is a valid pointer (to a class inheriting +// ``torrent_plugin``), it will be associated with this torrent and callbacks +// will be made on torrent events. +// +// For more elaborate plugins which require session wide state, you would +// implement ``plugin``, construct an object (in a ``std::shared_ptr``) and pass +// it in to ``session::add_extension()``. +// +// custom alerts +// ------------- +// +// Since plugins are running within internal libtorrent threads, one convenient +// way to communicate with the client is to post custom alerts. +// +// The expected interface of any alert, apart from deriving from the alert +// base class, looks like this: +// +// .. parsed-literal:: +// +// static const int alert_type = **; +// virtual int type() const { return alert_type; } +// +// virtual std::string message() const; +// +// static const alert_category_t static_category = **; +// virtual alert_category_t category() const { return static_category; } +// +// virtual char const* what() const { return **; } +// +// The ``alert_type`` is used for the type-checking in ``alert_cast``. It must +// not collide with any other alert. The built-in alerts in libtorrent will +// not use alert type IDs greater than ``user_alert_id``. When defining your +// own alert, make sure it's greater than this constant. +// +// ``type()`` is the run-time equivalence of the ``alert_type``. +// +// The ``message()`` virtual function is expected to construct a useful +// string representation of the alert and the event or data it represents. +// Something convenient to put in a log file for instance. +// +// ``clone()`` is used internally to copy alerts. The suggested implementation +// of simply allocating a new instance as a copy of ``*this`` is all that's +// expected. +// +// The static category is required for checking whether or not the category +// for a specific alert is enabled or not, without instantiating the alert. +// The ``category`` virtual function is the run-time equivalence. +// +// The ``what()`` virtual function may simply be a string literal of the class +// name of your alert. +// +// For more information, see the `alert section`_. +// +// .. _`alert section`: reference-Alerts.html + +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" + +namespace libtorrent { + +#ifndef TORRENT_DISABLE_EXTENSIONS + + // these are flags that can be returned by implemented_features() + // indicating which callbacks this plugin is interested in + using feature_flags_t = flags::bitfield_flag; + +TORRENT_VERSION_NAMESPACE_3 + + // this is the base class for a session plugin. One primary feature + // is that it is notified of all torrents that are added to the session, + // and can add its own torrent_plugins. + struct TORRENT_EXPORT plugin + { + // hidden + virtual ~plugin() {} + +#if TORRENT_ABI_VERSION == 1 + using feature_flags_t = libtorrent::feature_flags_t; +#endif + + // include this bit if your plugin needs to alter the order of the + // optimistic unchoke of peers. i.e. have the on_optimistic_unchoke() + // callback be called. + static constexpr feature_flags_t optimistic_unchoke_feature = 1_bit; + + // include this bit if your plugin needs to have on_tick() called + static constexpr feature_flags_t tick_feature = 2_bit; + + // include this bit if your plugin needs to have on_dht_request() + // called + static constexpr feature_flags_t dht_request_feature = 3_bit; + + // include this bit if your plugin needs to have on_alert() + // called + static constexpr feature_flags_t alert_feature = 4_bit; + + // include this bit if your plugin needs to have on_unknown_torrent() + // called even if there is no active torrent in the session + static constexpr feature_flags_t unknown_torrent_feature = 5_bit; + + // This function is expected to return a bitmask indicating which features + // this plugin implements. Some callbacks on this object may not be called + // unless the corresponding feature flag is returned here. Note that + // callbacks may still be called even if the corresponding feature is not + // specified in the return value here. See feature_flags_t for possible + // flags to return. + virtual feature_flags_t implemented_features() { return {}; } + + // this is called by the session every time a new torrent is added. + // The ``torrent*`` points to the internal torrent object created + // for the new torrent. The client_data_t is the userdata pointer as + // passed in via add_torrent_params. + // + // If the plugin returns a torrent_plugin instance, it will be added + // to the new torrent. Otherwise, return an empty shared_ptr to a + // torrent_plugin (the default). + virtual std::shared_ptr new_torrent(torrent_handle const&, client_data_t) + { return std::shared_ptr(); } + + // called when plugin is added to a session + virtual void added(session_handle const&) {} + + // called when the session is aborted + // the plugin should perform any cleanup necessary to allow the session's + // destruction (e.g. cancel outstanding async operations) + virtual void abort() {} + + // called when a dht request is received. + // If your plugin expects this to be called, make sure to include the flag + // ``dht_request_feature`` in the return value from implemented_features(). + virtual bool on_dht_request(string_view /* query */ + , udp::endpoint const& /* source */, bdecode_node const& /* message */ + , entry& /* response */) + { return false; } + + // called when an alert is posted alerts that are filtered are not posted. + // If your plugin expects this to be called, make sure to include the flag + // ``alert_feature`` in the return value from implemented_features(). + virtual void on_alert(alert const*) {} + + // return true if the add_torrent_params should be added + virtual bool on_unknown_torrent(info_hash_t const& /* info_hash */ + , peer_connection_handle const& /* pc */, add_torrent_params& /* p */) + { return false; } + + // called once per second. + // If your plugin expects this to be called, make sure to include the flag + // ``tick_feature`` in the return value from implemented_features(). + virtual void on_tick() {} + + // called when choosing peers to optimistically unchoke. The return value + // indicates the peer's priority for unchoking. Lower return values + // correspond to higher priority. Priorities above 2^63-1 are reserved. + // If your plugin has no priority to assign a peer it should return 2^64-1. + // If your plugin expects this to be called, make sure to include the flag + // ``optimistic_unchoke_feature`` in the return value from implemented_features(). + // If multiple plugins implement this function the lowest return value + // (i.e. the highest priority) is used. + virtual uint64_t get_unchoke_priority(peer_connection_handle const& /* peer */) + { return (std::numeric_limits::max)(); } + +#if TORRENT_ABI_VERSION <= 2 + // called when saving settings state + virtual void save_state(entry&) {} + + // called when loading settings state + virtual void load_state(bdecode_node const&) {} +#endif + + virtual std::map save_state() const { return {}; } + + // called on startup while loading settings state from the session_params + virtual void load_state(std::map const&) {} + }; + +TORRENT_VERSION_NAMESPACE_3_END + + using add_peer_flags_t = flags::bitfield_flag; + + // Torrent plugins are associated with a single torrent and have a number + // of functions called at certain events. Many of its functions have the + // ability to change or override the default libtorrent behavior. + struct TORRENT_EXPORT torrent_plugin + { + // hidden + virtual ~torrent_plugin() {} + +#if TORRENT_ABI_VERSION == 1 + using flags_t = libtorrent::add_peer_flags_t; +#endif + + // This function is called each time a new peer is connected to the torrent. You + // may choose to ignore this by just returning a default constructed + // ``shared_ptr`` (in which case you don't need to override this member + // function). + // + // If you need an extension to the peer connection (which most plugins do) you + // are supposed to return an instance of your peer_plugin class. Which in + // turn will have its hook functions called on event specific to that peer. + // + // The ``peer_connection_handle`` will be valid as long as the ``shared_ptr`` + // is being held by the torrent object. So, it is generally a good idea to not + // keep a ``shared_ptr`` to your own peer_plugin. If you want to keep references + // to it, use ``weak_ptr``. + // + // If this function throws an exception, the connection will be closed. + virtual std::shared_ptr new_connection(peer_connection_handle const&) + { return std::shared_ptr(); } + + // These hooks are called when a piece passes the hash check or fails the hash + // check, respectively. The ``index`` is the piece index that was downloaded. + // It is possible to access the list of peers that participated in sending the + // piece through the ``torrent`` and the ``piece_picker``. + virtual void on_piece_pass(piece_index_t) {} + virtual void on_piece_failed(piece_index_t) {} + + // This hook is called approximately once per second. It is a way of making it + // easy for plugins to do timed events, for sending messages or whatever. + virtual void tick() {} + + // These hooks are called when the torrent is paused and resumed respectively. + // The return value indicates if the event was handled. A return value of + // ``true`` indicates that it was handled, and no other plugin after this one + // will have this hook function called, and the standard handler will also not be + // invoked. So, returning true effectively overrides the standard behavior of + // pause or resume. + // + // Note that if you call ``pause()`` or ``resume()`` on the torrent from your + // handler it will recurse back into your handler, so in order to invoke the + // standard handler, you have to keep your own state on whether you want standard + // behavior or overridden behavior. + virtual bool on_pause() { return false; } + virtual bool on_resume() { return false; } + + // This function is called when the initial files of the torrent have been + // checked. If there are no files to check, this function is called immediately. + // + // i.e. This function is always called when the torrent is in a state where it + // can start downloading. + virtual void on_files_checked() {} + + // called when the torrent changes state + // the state is one of torrent_status::state_t + // enum members + virtual void on_state(torrent_status::state_t) {} + + // this is the first time we see this peer + static constexpr add_peer_flags_t first_time = 1_bit; + + // this peer was not added because it was + // filtered by the IP filter + static constexpr add_peer_flags_t filtered = 2_bit; + + // called every time a new peer is added to the peer list. + // This is before the peer is connected to. For ``flags``, see + // torrent_plugin::flags_t. The ``source`` argument refers to + // the source where we learned about this peer from. It's a + // bitmask, because many sources may have told us about the same + // peer. For peer source flags, see peer_info::peer_source_flags. + virtual void on_add_peer(tcp::endpoint const&, + peer_source_flags_t, add_peer_flags_t) {} + }; + + // peer plugins are associated with a specific peer. A peer could be + // both a regular bittorrent peer (``bt_peer_connection``) or one of the + // web seed connections (``web_peer_connection`` or ``http_seed_connection``). + // In order to only attach to certain peers, make your + // torrent_plugin::new_connection only return a plugin for certain peer + // connection types + struct TORRENT_EXPORT peer_plugin + { + // hidden + virtual ~peer_plugin() {} + + // This function is expected to return the name of + // the plugin. + virtual string_view type() const { return {}; } + + // can add entries to the extension handshake + // this is not called for web seeds + virtual void add_handshake(entry&) {} + + // called when the peer is being disconnected. + virtual void on_disconnect(error_code const&) {} + + // called when the peer is successfully connected. Note that + // incoming connections will have been connected by the time + // the peer plugin is attached to it, and won't have this hook + // called. + virtual void on_connected() {} + + // throwing an exception from any of the handlers (except add_handshake) + // closes the connection + + // this is called when the initial bittorrent handshake is received. + // Returning false means that the other end doesn't support this extension + // and will remove it from the list of plugins. this is not called for web + // seeds + virtual bool on_handshake(span) { return true; } + + // called when the extension handshake from the other end is received + // if this returns false, it means that this extension isn't + // supported by this peer. It will result in this peer_plugin + // being removed from the peer_connection and destructed. + // this is not called for web seeds + virtual bool on_extension_handshake(bdecode_node const&) { return true; } + + // returning true from any of the message handlers + // indicates that the plugin has handled the message. + // it will break the plugin chain traversing and not let + // anyone else handle the message, including the default + // handler. + virtual bool on_choke() { return false; } + virtual bool on_unchoke() { return false; } + virtual bool on_interested() { return false; } + virtual bool on_not_interested() { return false; } + virtual bool on_have(piece_index_t) { return false; } + virtual bool on_dont_have(piece_index_t) { return false; } + virtual bool on_bitfield(bitfield const& /*bitfield*/) { return false; } + virtual bool on_have_all() { return false; } + virtual bool on_have_none() { return false; } + virtual bool on_allowed_fast(piece_index_t) { return false; } + virtual bool on_request(peer_request const&) { return false; } + + // This function is called when the peer connection is receiving + // a piece. ``buf`` points (non-owning pointer) to the data in an + // internal immutable disk buffer. The length of the data is specified + // in the ``length`` member of the ``piece`` parameter. + // returns true to indicate that the piece is handled and the + // rest of the logic should be ignored. + virtual bool on_piece(peer_request const& /*piece*/ + , span /*buf*/) { return false; } + + virtual bool on_cancel(peer_request const&) { return false; } + virtual bool on_reject(peer_request const&) { return false; } + virtual bool on_suggest(piece_index_t) { return false; } + + virtual void sent_have_all() {} + virtual void sent_have_none() {} + virtual void sent_reject_request(peer_request const&) {} + virtual void sent_allow_fast(piece_index_t) {} + virtual void sent_suggest(piece_index_t) {} + virtual void sent_cancel(peer_request const&) {} + virtual void sent_request(peer_request const&) {} + virtual void sent_choke() {} + // called after a choke message has been sent to the peer + virtual void sent_unchoke() {} + virtual void sent_interested() {} + virtual void sent_not_interested() {} + virtual void sent_have(piece_index_t) {} + virtual void sent_piece(peer_request const&) {} + + // called after piece data has been sent to the peer + // this can be used for stats book keeping + virtual void sent_payload(int /* bytes */) {} + + // called when libtorrent think this peer should be disconnected. + // if the plugin returns false, the peer will not be disconnected. + virtual bool can_disconnect(error_code const& /*ec*/) { return true; } + + // called when an extended message is received. If returning true, + // the message is not processed by any other plugin and if false + // is returned the next plugin in the chain will receive it to + // be able to handle it. This is not called for web seeds. + // thus function may be called more than once per incoming message, but + // only the last of the calls will the ``body`` size equal the ``length``. + // i.e. Every time another fragment of the message is received, this + // function will be called, until finally the whole message has been + // received. The purpose of this is to allow early disconnects for invalid + // messages and for reporting progress of receiving large messages. + virtual bool on_extended(int /*length*/, int /*msg*/, + span /*body*/) + { return false; } + + // this is not called for web seeds + virtual bool on_unknown_message(int /*length*/, int /*msg*/, + span /*body*/) + { return false; } + + // called when a piece that this peer participated in either + // fails or passes the hash_check + virtual void on_piece_pass(piece_index_t) {} + virtual void on_piece_failed(piece_index_t) {} + + // called approximately once every second + virtual void tick() {} + + // called each time a request message is to be sent. If true + // is returned, the original request message won't be sent and + // no other plugin will have this function called. + virtual bool write_request(peer_request const&) { return false; } + }; +#endif // TORRENT_DISABLE_EXTENSIONS + +#if !defined TORRENT_DISABLE_ENCRYPTION + + struct TORRENT_EXPORT crypto_plugin + { + // hidden + virtual ~crypto_plugin() {} + + virtual void set_incoming_key(span key) = 0; + virtual void set_outgoing_key(span key) = 0; + + // encrypted the provided buffers and returns the number of bytes which + // are now ready to be sent to the lower layer. This must be at least + // as large as the number of bytes passed in and may be larger if there + // is additional data to be inserted at the head of the send buffer. + // The additional data is returned as the second tuple value. Any + // returned buffer as well as the iovec itself, to be prepended to the + // send buffer, must be owned by the crypto plugin and guaranteed to stay + // alive until the crypto_plugin is destructed or this function is called + // again. + virtual std::tuple>> + encrypt(span> /*send_vec*/) = 0; + + // decrypt the provided buffers. + // returns is a tuple representing the values + // (consume, produce, packet_size) + // + // consume is set to the number of bytes which should be trimmed from the + // head of the buffers, default is 0 + // + // produce is set to the number of bytes of payload which are now ready to + // be sent to the upper layer. default is the number of bytes passed in receive_vec + // + // packet_size is set to the minimum number of bytes which must be read to + // advance the next step of decryption. default is 0 + virtual std::tuple decrypt(span> /*receive_vec*/) = 0; + }; + +#endif // TORRENT_DISABLE_ENCRYPTION +} + +#endif // TORRENT_EXTENSIONS_HPP_INCLUDED diff --git a/docs/include/libtorrent/file.hpp b/docs/include/libtorrent/file.hpp new file mode 100644 index 0000000..02a0014 --- /dev/null +++ b/docs/include/libtorrent/file.hpp @@ -0,0 +1,128 @@ +/* + +Copyright (c) 2004, 2008-2010, 2014-2018, 2020, Arvid Norberg +Copyright (c) 2016, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FILE_HPP_INCLUDED +#define TORRENT_FILE_HPP_INCLUDED + +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/aux_/storage_utils.hpp" +#include "libtorrent/aux_/open_mode.hpp" +#include "libtorrent/flags.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#ifdef TORRENT_WINDOWS +// windows part +#include "libtorrent/aux_/windows.hpp" +#include +#include +#else +// posix part + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 +#endif + +#include +#include +#include +#include +#include // for DIR + +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/error_code.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent { + +#ifdef TORRENT_WINDOWS + using handle_type = HANDLE; + const handle_type invalid_handle = INVALID_HANDLE_VALUE; +#else + using handle_type = int; + const handle_type invalid_handle = -1; +#endif + +namespace aux { + + int pwrite_all(handle_type handle + , span buf + , std::int64_t file_offset + , error_code& ec); + + int pread_all(handle_type handle + , span buf + , std::int64_t file_offset + , error_code& ec); + + struct TORRENT_EXTRA_EXPORT file_handle + { + file_handle(): m_fd(invalid_handle) {} + file_handle(string_view name, std::int64_t size, open_mode_t mode); + file_handle(file_handle const& rhs) = delete; + file_handle& operator=(file_handle const& rhs) = delete; + + file_handle(file_handle&& rhs) : m_fd(rhs.m_fd) { rhs.m_fd = invalid_handle; } + file_handle& operator=(file_handle&& rhs) &; + + ~file_handle(); + + std::int64_t get_size() const; + + handle_type fd() const { return m_fd; } + private: + void close(); + handle_type m_fd; +#ifdef TORRENT_WINDOWS + aux::open_mode_t m_open_mode; +#endif + }; + +} // namespace aux +} + +#endif // TORRENT_FILE_HPP_INCLUDED diff --git a/docs/include/libtorrent/file_layout.hpp b/docs/include/libtorrent/file_layout.hpp new file mode 100644 index 0000000..4d6f0e0 --- /dev/null +++ b/docs/include/libtorrent/file_layout.hpp @@ -0,0 +1,44 @@ +/* + +Copyright (c) 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FILE_LAYOUT_HPP_INCLUDED +#define TORRENT_FILE_LAYOUT_HPP_INCLUDED + +#include "libtorrent/file_storage.hpp" + +namespace libtorrent { + + using file_layout = file_storage; +} + +#endif + diff --git a/docs/include/libtorrent/file_storage.hpp b/docs/include/libtorrent/file_storage.hpp new file mode 100644 index 0000000..8050397 --- /dev/null +++ b/docs/include/libtorrent/file_storage.hpp @@ -0,0 +1,744 @@ +/* + +Copyright (c) 2008-2010, 2012-2021, Arvid Norberg +Copyright (c) 2016, 2020, Alden Torres +Copyright (c) 2017, 2019, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FILE_STORAGE_HPP_INCLUDED +#define TORRENT_FILE_STORAGE_HPP_INCLUDED + + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/assert.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/index_range.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/disk_interface.hpp" // for default_block_size +#include "libtorrent/fwd.hpp" + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 + // information about a file in a file_storage + struct TORRENT_DEPRECATED_EXPORT file_entry + { +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + // hidden + file_entry(); + // hidden + ~file_entry(); + file_entry(file_entry const&) = default; + file_entry& operator=(file_entry const&) & = default; + file_entry(file_entry&&) noexcept = default; + file_entry& operator=(file_entry&&) & = default; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // the full path of this file. The paths are unicode strings + // encoded in UTF-8. + std::string path; + + // the path which this is a symlink to, or empty if this is + // not a symlink. This field is only used if the ``symlink_attribute`` is set. + std::string symlink_path; + + // the offset of this file inside the torrent + std::int64_t offset; + + // the size of the file (in bytes) and ``offset`` is the byte offset + // of the file within the torrent. i.e. the sum of all the sizes of the files + // before it in the list. + std::int64_t size; + + // the modification time of this file specified in posix time. + std::time_t mtime; + + // a SHA-1 hash of the content of the file, or zeros, if no + // file hash was present in the torrent file. It can be used to potentially + // find alternative sources for the file. + sha1_hash filehash; + + // set to true for files that are not part of the data of the torrent. + // They are just there to make sure the next file is aligned to a particular byte offset + // or piece boundary. These files should typically be hidden from an end user. They are + // not written to disk. + bool pad_file:1; + + // true if the file was marked as hidden (on windows). + bool hidden_attribute:1; + + // true if the file was marked as executable (posix) + bool executable_attribute:1; + + // true if the file was a symlink. If this is the case + // the ``symlink_index`` refers to a string which specifies the original location + // where the data for this file was found. + bool symlink_attribute:1; + }; + +#endif // TORRENT_ABI_VERSION + +namespace aux { + struct path_index_tag; + using path_index_t = aux::strong_typedef; + + // internal + struct file_entry + { + friend class ::lt::file_storage; + file_entry(); + file_entry(file_entry const& fe); + file_entry& operator=(file_entry const& fe) &; + file_entry(file_entry&& fe) noexcept; + file_entry& operator=(file_entry&& fe) & noexcept; + ~file_entry(); + + void set_name(string_view n, bool borrow_string = false); + string_view filename() const; + + enum { + name_is_owned = (1 << 12) - 1, + not_a_symlink = (1 << 15) - 1, + }; + + static constexpr aux::path_index_t no_path{(1 << 30) - 1}; + static constexpr aux::path_index_t path_is_absolute{(1 << 30) - 2}; + + // the offset of this file inside the torrent + std::uint64_t offset:48; + + // index into file_storage::m_symlinks or not_a_symlink + // if this is not a symlink + std::uint64_t symlink_index:15; + + // if this is true, don't include m_name as part of the + // path to this file + std::uint64_t no_root_dir:1; + + // the size of this file + std::uint64_t size:48; + + // the number of characters in the name. If this is + // name_is_owned, name is 0-terminated and owned by this object + // (i.e. it should be freed in the destructor). If + // the len is not name_is_owned, the name pointer does not belong + // to this object, and it's not 0-terminated + std::uint64_t name_len:12; + std::uint64_t pad_file:1; + std::uint64_t hidden_attribute:1; + std::uint64_t executable_attribute:1; + std::uint64_t symlink_attribute:1; + + // make it available for logging + private: + // This string is not necessarily 0-terminated! + // that's why it's private, to keep people away from it + char const* name = nullptr; + public: + // the SHA-256 root of the merkle tree for this file + // this is a pointer into the .torrent file + char const* root = nullptr; + + // the index into file_storage::m_paths. To get + // the full path to this file, concatenate the path + // from that array with the 'name' field in + // this struct + // values for path_index include: + // no_path means no path (i.e. single file torrent) + // path_is_absolute means the filename + // in this field contains the full, absolute path + // to the file + aux::path_index_t path_index = file_entry::no_path; + }; + +} // aux namespace + + // represents a window of a file in a torrent. + // + // The ``file_index`` refers to the index of the file (in the torrent_info). + // To get the path and filename, use ``file_path()`` and give the ``file_index`` + // as argument. The ``offset`` is the byte offset in the file where the range + // starts, and ``size`` is the number of bytes this range is. The size + offset + // will never be greater than the file size. + struct TORRENT_EXPORT file_slice + { + // the index of the file + file_index_t file_index; + + // the offset from the start of the file, in bytes + std::int64_t offset; + + // the size of the window, in bytes + std::int64_t size; + }; + + // hidden + using file_flags_t = flags::bitfield_flag; + + // The ``file_storage`` class represents a file list and the piece + // size. Everything necessary to interpret a regular bittorrent storage + // file structure. + class TORRENT_EXPORT file_storage + { + public: + // hidden + file_storage(); + // hidden + ~file_storage(); + file_storage(file_storage const&); + file_storage& operator=(file_storage const&) &; + file_storage(file_storage&&) noexcept; + file_storage& operator=(file_storage&&) &; + + // internal limitations restrict file sizes to not be larger than this + // We use int to index into file merkle trees, so a file may not contain more + // than INT_MAX entries. That means INT_MAX / 2 blocks (leafs) in each + // tree. + static constexpr std::int64_t max_file_size = (std::min)( + (std::int64_t(1) << 48) - 1 + , std::int64_t((std::numeric_limits::max)() / 2) * default_block_size); + static constexpr std::int64_t max_file_offset = (std::int64_t(1) << 48) - 1; + + // we use a signed 32 bit integer for piece indices internally, but + // frequently need headroom for intermediate calculations, so we limit + // the number of pieces 1 bit below the maximum + static constexpr std::int32_t max_num_pieces = (std::int32_t(1) << 30) - 1; + + // limit the piece length at (2 ^ 30) to get a bit of headroom. We + // commonly compute the number of blocks per pieces by adding + // block_size - 1 before dividing by block_size. That would overflow with + // a piece size of 2 ^ 31. This limit is still an unreasonably large + // piece size anyway. + // The piece picker (currently) has a limit of no more than (2^15)-1 + // blocks per piece, which is more restrictive, at a block size of 16 + // kiB (0x4000). + static constexpr std::int32_t max_piece_size = ((1 << 15) - 1) * 0x4000; + + // returns true if the piece length has been initialized + // on the file_storage. This is typically taken as a proxy + // of whether the file_storage as a whole is initialized or + // not. + bool is_valid() const { return m_piece_length > 0; } + +#if TORRENT_ABI_VERSION == 1 + using flags_t = file_flags_t; + TORRENT_DEPRECATED static constexpr file_flags_t pad_file = 0_bit; + TORRENT_DEPRECATED static constexpr file_flags_t attribute_hidden = 1_bit; + TORRENT_DEPRECATED static constexpr file_flags_t attribute_executable = 2_bit; + TORRENT_DEPRECATED static constexpr file_flags_t attribute_symlink = 3_bit; +#endif + + // allocates space for ``num_files`` in the internal file list. This can + // be used to avoid reallocating the internal file list when the number + // of files to be added is known up-front. + void reserve(int num_files); + + // Adds a file to the file storage. The ``add_file_borrow`` version + // expects that ``filename`` is the file name (without a path) of + // the file that's being added. + // This memory is *borrowed*, i.e. it is the caller's + // responsibility to make sure it stays valid throughout the lifetime + // of this file_storage object or any copy of it. The same thing applies + // to ``filehash``, which is an optional pointer to a 20 byte binary + // SHA-1 hash of the file. + // + // if ``filename`` is empty, the filename from ``path`` is used and not + // borrowed. + // + // The ``path`` argument is the full path (in the torrent file) to + // the file to add. Note that this is not supposed to be an absolute + // path, but it is expected to include the name of the torrent as the + // first path element. + // + // ``file_size`` is the size of the file in bytes. + // + // The ``file_flags`` argument sets attributes on the file. The file + // attributes is an extension and may not work in all bittorrent clients. + // + // For possible file attributes, see file_storage::flags_t. + // + // The ``mtime`` argument is optional and can be set to 0. If non-zero, + // it is the posix time of the last modification time of this file. + // + // ``symlink_path`` is the path the file is a symlink to. To make this a + // symlink you also need to set the file_storage::flag_symlink file flag. + // + // ``root_hash`` is an optional pointer to a 32 byte SHA-256 hash, being + // the merkle tree root hash for this file. This is only used for v2 + // torrents. If the ``root hash`` is specified for one file, it has to + // be specified for all, otherwise this function will fail. + // Note that the buffer ``root_hash`` points to must out-live the + // file_storage object, it will not be copied. This parameter is only + // used when *loading* torrents, that already have their file hashes + // computed. When creating torrents, the file hashes will be computed by + // the piece hashes. + // + // If more files than one are added, certain restrictions to their paths + // apply. In a multi-file file storage (torrent), all files must share + // the same root directory. + // + // That is, the first path element of all files must be the same. + // This shared path element is also set to the name of the torrent. It + // can be changed by calling ``set_name``. + // + // The overloads that take an `error_code` reference will report failures + // via that variable, otherwise `system_error` is thrown. +#ifndef BOOST_NO_EXCEPTIONS + void add_file_borrow(string_view filename + , std::string const& path, std::int64_t file_size + , file_flags_t file_flags = {}, char const* filehash = nullptr + , std::int64_t mtime = 0, string_view symlink_path = string_view() + , char const* root_hash = nullptr); + void add_file(std::string const& path, std::int64_t file_size + , file_flags_t file_flags = {} + , std::time_t mtime = 0, string_view symlink_path = string_view() + , char const* root_hash = nullptr); +#endif // BOOST_NO_EXCEPTIONS + void add_file_borrow(error_code& ec, string_view filename + , std::string const& path, std::int64_t file_size + , file_flags_t file_flags = {}, char const* filehash = nullptr + , std::int64_t mtime = 0, string_view symlink_path = string_view() + , char const* root_hash = nullptr); + void add_file(error_code& ec, std::string const& path, std::int64_t file_size + , file_flags_t file_flags = {} + , std::time_t mtime = 0, string_view symlink_path = string_view() + , char const* root_hash = nullptr); + + // renames the file at ``index`` to ``new_filename``. Keep in mind + // that filenames are expected to be UTF-8 encoded. + void rename_file(file_index_t index, std::string const& new_filename); + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + TORRENT_DEPRECATED + void add_file_borrow(char const* filename, int filename_len + , std::string const& path, std::int64_t file_size + , file_flags_t file_flags = {}, char const* filehash = nullptr + , std::int64_t mtime = 0, string_view symlink_path = string_view()); + TORRENT_DEPRECATED + void add_file(file_entry const& fe, char const* filehash = nullptr); + + // all functions depending on aux::file_entry + // were deprecated in 1.0. Use the variants that take an + // index instead + using iterator = std::vector::const_iterator; + using reverse_iterator = std::vector::const_reverse_iterator; + + TORRENT_DEPRECATED + iterator file_at_offset(std::int64_t offset) const; + TORRENT_DEPRECATED + iterator begin() const { return m_files.begin(); } + TORRENT_DEPRECATED + iterator end() const { return m_files.end(); } + TORRENT_DEPRECATED + reverse_iterator rbegin() const { return m_files.rbegin(); } + TORRENT_DEPRECATED + reverse_iterator rend() const { return m_files.rend(); } + TORRENT_DEPRECATED + aux::file_entry const& internal_at(int const index) const; + TORRENT_DEPRECATED + file_entry at(iterator i) const; + + // returns a file_entry with information about the file + // at ``index``. Index must be in the range [0, ``num_files()`` ). + TORRENT_DEPRECATED + file_entry at(int index) const; + + iterator begin_deprecated() const { return m_files.begin(); } + iterator end_deprecated() const { return m_files.end(); } + reverse_iterator rbegin_deprecated() const { return m_files.rbegin(); } + reverse_iterator rend_deprecated() const { return m_files.rend(); } + iterator file_at_offset_deprecated(std::int64_t offset) const; + file_entry at_deprecated(int index) const; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // TORRENT_ABI_VERSION + + // returns a list of file_slice objects representing the portions of + // files the specified piece index, byte offset and size range overlaps. + // this is the inverse mapping of map_file(). + // + // Preconditions of this function is that the input range is within the + // torrents address space. ``piece`` may not be negative and + // + // ``piece`` * piece_size + ``offset`` + ``size`` + // + // may not exceed the total size of the torrent. + std::vector map_block(piece_index_t piece, std::int64_t offset + , std::int64_t size) const; + + // returns a peer_request representing the piece index, byte offset + // and size the specified file range overlaps. This is the inverse + // mapping over map_block(). Note that the ``peer_request`` return type + // is meant to hold bittorrent block requests, which may not be larger + // than 16 kiB. Mapping a range larger than that may return an overflown + // integer. + peer_request map_file(file_index_t file, std::int64_t offset, int size) const; + + // returns the number of files in the file_storage + int num_files() const noexcept; + + // returns the index of the one-past-end file in the file storage + file_index_t end_file() const noexcept; + + // returns an implementation-defined type that can be used as the + // container in a range-for loop. Where the values are the indices of all + // files in the file_storage. + index_range file_range() const noexcept; + + // returns the total number of bytes all the files in this torrent spans + std::int64_t total_size() const { return m_total_size; } + + // set and get the number of pieces in the torrent + void set_num_pieces(int n) { m_num_pieces = n; } + int num_pieces() const { TORRENT_ASSERT(m_piece_length > 0); return m_num_pieces; } + + // returns the index of the one-past-end piece in the file storage + piece_index_t end_piece() const + { return piece_index_t(m_num_pieces); } + + // returns the index of the last piece in the torrent. The last piece is + // special in that it may be smaller than the other pieces (and the other + // pieces are all the same size). + piece_index_t last_piece() const + { return piece_index_t(m_num_pieces - 1); } + + // returns an implementation-defined type that can be used as the + // container in a range-for loop. Where the values are the indices of all + // pieces in the file_storage. + index_range piece_range() const noexcept; + + // set and get the size of each piece in this torrent. It must be a power of two + // and at least 16 kiB. + void set_piece_length(int l) { m_piece_length = l; } + int piece_length() const { TORRENT_ASSERT(m_piece_length > 0); return m_piece_length; } + + // returns the piece size of ``index``. This will be the same as piece_length(), except + // for the last piece, which may be shorter. + int piece_size(piece_index_t index) const; + + // Returns the size of the given piece. If the piece spans multiple files, + // only the first file is considered part of the piece. This is used for + // v2 torrents, where all files are piece aligned and padded. i.e. The pad + // files are not considered part of the piece for this purpose. + int piece_size2(piece_index_t index) const; + + // returns the number of blocks in the specified piece, for v2 torrents. + int blocks_in_piece2(piece_index_t index) const; + + // returns the number of blocks there are in the typical piece. There + // may be fewer in the last piece) + int blocks_per_piece() const; + + // set and get the name of this torrent. For multi-file torrents, this is also + // the name of the root directory all the files are stored in. + void set_name(std::string const& n) { m_name = n; } + std::string const& name() const { return m_name; } + + // swap all content of *this* with *ti*. + void swap(file_storage& ti) noexcept; + + // arrange files and padding to match the canonical form required + // by BEP 52 + void canonicalize(); + + // These functions are used to query attributes of files at + // a given index. + // + // The ``hash()`` is a SHA-1 hash of the file, or 0 if none was + // provided in the torrent file. This can potentially be used to + // join a bittorrent network with other file sharing networks. + // + // ``root()`` returns the SHA-256 merkle tree root of the specified file, + // in case this is a v2 torrent. Otherwise returns zeros. + // ``root_ptr()`` returns a pointer to the SHA-256 merkle tree root hash + // for the specified file. The pointer points into storage referred to + // when the file was added, it is not owned by this object. Torrents + // that are not v2 torrents return nullptr. + // + // The ``mtime()`` is the modification time is the posix + // time when a file was last modified when the torrent + // was created, or 0 if it was not included in the torrent file. + // + // ``file_path()`` returns the full path to a file. + // + // ``file_size()`` returns the size of a file. + // + // ``pad_file_at()`` returns true if the file at the given + // index is a pad-file. + // + // ``file_name()`` returns *just* the name of the file, whereas + // ``file_path()`` returns the path (inside the torrent file) with + // the filename appended. + // + // ``file_offset()`` returns the byte offset within the torrent file + // where this file starts. It can be used to map the file to a piece + // index (given the piece size). + sha1_hash hash(file_index_t index) const; + sha256_hash root(file_index_t index) const; + char const* root_ptr(file_index_t const index) const; + std::string symlink(file_index_t index) const; + std::time_t mtime(file_index_t index) const; + std::string file_path(file_index_t index, std::string const& save_path = "") const; + string_view file_name(file_index_t index) const; + std::int64_t file_size(file_index_t index) const; + bool pad_file_at(file_index_t index) const; + std::int64_t file_offset(file_index_t index) const; + + // Returns the number of pieces or blocks the file at `index` spans, + // under the assumption that the file is aligned to the start of a piece. + // This is only meaningful for v2 torrents, where files are guaranteed + // such alignment. + // These numbers are used to size and navigate the merkle hash tree for + // each file. + int file_num_pieces(file_index_t index) const; + int file_num_blocks(file_index_t index) const; + index_range file_piece_range(file_index_t) const; + + // index of first piece node in the merkle tree + int file_first_piece_node(file_index_t index) const; + int file_first_block_node(file_index_t index) const; + + // returns the crc32 hash of file_path(index) + std::uint32_t file_path_hash(file_index_t index, std::string const& save_path) const; + + // this will add the CRC32 hash of all directory entries to the table. No + // filename will be included, just directories. Every depth of directories + // are added separately to allow test for collisions with files at all + // levels. i.e. if one path in the torrent is ``foo/bar/baz``, the CRC32 + // hashes for ``foo``, ``foo/bar`` and ``foo/bar/baz`` will be added to + // the set. + void all_path_hashes(std::unordered_set& table) const; + + // the file is a pad file. It's required to contain zeros + // at it will not be saved to disk. Its purpose is to make + // the following file start on a piece boundary. + static constexpr file_flags_t flag_pad_file = 0_bit; + + // this file has the hidden attribute set. This is primarily + // a windows attribute + static constexpr file_flags_t flag_hidden = 1_bit; + + // this file has the executable attribute set. + static constexpr file_flags_t flag_executable = 2_bit; + + // this file is a symbolic link. It should have a link + // target string associated with it. + static constexpr file_flags_t flag_symlink = 3_bit; + + // internal + // returns all directories used in the torrent. Files in the torrent are + // located in one of these directories. This is not a tree, it's a flat + // list of all *leaf* directories. i.e. the union of the parent paths of + // all files. + aux::vector const& paths() const { return m_paths; } + + // returns a bitmask of flags from file_flags_t that apply + // to file at ``index``. + file_flags_t file_flags(file_index_t index) const; + + // returns true if the file at the specified index has been renamed to + // have an absolute path, i.e. is not anchored in the save path of the + // torrent. + bool file_absolute_path(file_index_t index) const; + + // returns the index of the file at the given offset in the torrent + file_index_t file_index_at_offset(std::int64_t offset) const; + file_index_t file_index_at_piece(piece_index_t piece) const; + + // finds the file with the given root hash and returns its index + // if there is no file with the root hash, file_index_t{-1} is returned + file_index_t file_index_for_root(sha256_hash const& root_hash) const; + + // returns the piece index the given file starts at + piece_index_t piece_index_at_file(file_index_t f) const; + +#if TORRENT_USE_INVARIANT_CHECKS + // internal + bool owns_name(file_index_t const f) const + { return m_files[f].name_len == aux::file_entry::name_is_owned; } +#endif + +#if TORRENT_ABI_VERSION <= 2 + // low-level function. returns a pointer to the internal storage for + // the filename. This string may not be 0-terminated! + // the ``file_name_len()`` function returns the length of the filename. + // prefer to use ``file_name()`` instead, which returns a ``string_view``. + TORRENT_DEPRECATED + char const* file_name_ptr(file_index_t index) const; + TORRENT_DEPRECATED + int file_name_len(file_index_t index) const; +#endif + +#if TORRENT_ABI_VERSION == 1 + // these were deprecated in 1.0. Use the versions that take an index instead + TORRENT_DEPRECATED + sha1_hash hash(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + std::string symlink(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + std::time_t mtime(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + int file_index(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + std::string file_path(aux::file_entry const& fe, std::string const& save_path = "") const; + TORRENT_DEPRECATED + std::string file_name(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + std::int64_t file_size(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + bool pad_file_at(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + std::int64_t file_offset(aux::file_entry const& fe) const; +#endif + + // validate any symlinks, to ensure they all point to + // other files or directories inside this storage. Any invalid symlinks + // are updated to point to themselves. + void sanitize_symlinks(); + + // returns true if this torrent contains v2 metadata. + bool v2() const { return m_v2; } + + // internal + // this is an optimization for create_torrent + std::string const& internal_symlink(file_index_t index) const; + + // internal + void remove_tail_padding(); + + // internal + void canonicalize_impl(bool backwards_compatible); + + private: + + std::string internal_file_path(file_index_t index) const; + file_index_t last_file() const noexcept; + + aux::path_index_t get_or_add_path(string_view path); + + // the number of bytes in a regular piece + // (i.e. not the potentially truncated last piece) + int m_piece_length = 0; + + // the number of pieces in the torrent + int m_num_pieces = 0; + + // whether this is a v2 torrent or not. Additional requirements apply to + // v2 torrents + bool m_v2 = false; + + void update_path_index(aux::file_entry& e, std::string const& path + , bool set_name = true); + + // the list of files that this torrent consists of + aux::vector m_files; + + // if there are sha1 hashes for each individual file there are as many + // entries in this array as the m_files array. Each entry in m_files has + // a corresponding hash pointer in this array. The reason to split it up + // in separate arrays is to save memory in case the torrent doesn't have + // file hashes + // the pointers in this vector are pointing into the .torrent file in + // memory which is _not_ owned by this file_storage object. It's simply + // a non-owning pointer. It is the user's responsibility that the hash + // stays valid throughout the lifetime of this file_storage object. + aux::vector m_file_hashes; + + // for files that are symlinks, the symlink + // path_index in the aux::file_entry indexes + // this vector of strings + std::vector m_symlinks; + + // the modification times of each file. This vector + // is empty if no file have a modification time. + // each element corresponds to the file with the same + // index in m_files + aux::vector m_mtime; + + // all unique paths files have. The aux::file_entry::path_index + // points into this array. The paths don't include the root directory + // name for multi-file torrents. The m_name field need to be + // prepended to these paths, and the filename of a specific file + // entry appended, to form full file paths + aux::vector m_paths; + + // name of torrent. For multi-file torrents + // this is always the root directory + std::string m_name; + + // the sum of all file sizes + std::int64_t m_total_size = 0; + }; + +namespace aux { + + TORRENT_EXTRA_EXPORT + int calc_num_pieces(file_storage const& fs); + + // this is used when loading v2 torrents that are backwards compatible with + // v1 torrents. Both v1 and v2 structures must describe the same file layout, + // this compares the two. + TORRENT_EXTRA_EXPORT + bool files_compatible(file_storage const& lhs, file_storage const& rhs); + + // returns the piece range that entirely falls within the specified file. the + // end piece is one-past the last piece that entirely falls within the file. + // i.e. They can conveniently be used as loop boundaries. No edge partial + // pieces will be included. + TORRENT_EXTRA_EXPORT std::tuple + file_piece_range_exclusive(file_storage const& fs, file_index_t file); + + // returns the piece range of pieces that overlaps with the specified file. + // the end piece is one-past the last piece. i.e. They can conveniently be + // used as loop boundaries. + TORRENT_EXTRA_EXPORT std::tuple + file_piece_range_inclusive(file_storage const& fs, file_index_t file); + + TORRENT_EXTRA_EXPORT + std::int64_t size_on_disk(file_storage const& fs); + +} // namespace aux +} // namespace libtorrent + +#endif // TORRENT_FILE_STORAGE_HPP_INCLUDED diff --git a/docs/include/libtorrent/fingerprint.hpp b/docs/include/libtorrent/fingerprint.hpp new file mode 100644 index 0000000..cadde82 --- /dev/null +++ b/docs/include/libtorrent/fingerprint.hpp @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2003, 2006, 2009, 2013, 2016-2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FINGERPRINT_HPP_INCLUDED +#define TORRENT_FINGERPRINT_HPP_INCLUDED + +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/export.hpp" + +namespace libtorrent { + + // This is a utility function to produce a client ID fingerprint formatted to + // the most common convention. The fingerprint can be set via the + // ``peer_fingerprint`` setting, in settings_pack. + // + // The name string should contain exactly two characters. These are the + // characters unique to your client, used to identify it. Make sure not to + // clash with anybody else. Here are some taken id's: + // + // +----------+-----------------------+ + // | id chars | client | + // +==========+=======================+ + // | LT | libtorrent (default) | + // +----------+-----------------------+ + // | UT | uTorrent | + // +----------+-----------------------+ + // | UM | uTorrent Mac | + // +----------+-----------------------+ + // | qB | qBittorrent | + // +----------+-----------------------+ + // | BP | BitTorrent Pro | + // +----------+-----------------------+ + // | BT | BitTorrent | + // +----------+-----------------------+ + // | DE | Deluge | + // +----------+-----------------------+ + // | AZ | Azureus | + // +----------+-----------------------+ + // | TL | Tribler | + // +----------+-----------------------+ + // + // There's an informal directory of client id's here_. + // + // .. _here: http://wiki.theory.org/BitTorrentSpecification#peer_id + // + // The ``major``, ``minor``, ``revision`` and ``tag`` parameters are used to + // identify the version of your client. + TORRENT_EXPORT std::string generate_fingerprint(std::string name + , int major, int minor = 0, int revision = 0, int tag = 0); + + // The fingerprint class represents information about a client and its version. It is used + // to encode this information into the client's peer id. + struct TORRENT_DEPRECATED_EXPORT fingerprint + { + fingerprint(const char* id_string, int major, int minor, int revision, int tag); + +#if TORRENT_ABI_VERSION == 1 + // generates the actual string put in the peer-id, and return it. + std::string to_string() const; +#endif + + char name[2]; + int major_version; + int minor_version; + int revision_version; + int tag_version; + }; + +} + +#endif // TORRENT_FINGERPRINT_HPP_INCLUDED diff --git a/docs/include/libtorrent/flags.hpp b/docs/include/libtorrent/flags.hpp new file mode 100644 index 0000000..9175d8f --- /dev/null +++ b/docs/include/libtorrent/flags.hpp @@ -0,0 +1,141 @@ +/* + +Copyright (c) 2017-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FLAGS_HPP_INCLUDED +#define TORRENT_FLAGS_HPP_INCLUDED + +#include // for enable_if +#include + +namespace libtorrent { + +struct bit_t +{ + explicit constexpr bit_t(int b) : m_bit_idx(b) {} + explicit constexpr operator int() const { return m_bit_idx; } +private: + int m_bit_idx; +}; + +constexpr bit_t operator ""_bit(unsigned long long int b) { return bit_t{static_cast(b)}; } + +namespace flags { + +template::value>::type> +struct bitfield_flag +{ + static_assert(std::is_unsigned::value + , "flags must use unsigned integers as underlying types"); + + using underlying_type = UnderlyingType; + + constexpr bitfield_flag(bitfield_flag const& rhs) noexcept = default; + constexpr bitfield_flag(bitfield_flag&& rhs) noexcept = default; + constexpr bitfield_flag() noexcept : m_val(0) {} + explicit constexpr bitfield_flag(UnderlyingType const val) noexcept : m_val(val) {} + constexpr bitfield_flag(bit_t const bit) noexcept : m_val(static_cast(UnderlyingType{1} << static_cast(bit))) {} +#if TORRENT_ABI_VERSION >= 2 + explicit constexpr operator UnderlyingType() const noexcept { return m_val; } +#else + constexpr operator UnderlyingType() const noexcept { return m_val; } +#endif + explicit constexpr operator bool() const noexcept { return m_val != 0; } + + static constexpr bitfield_flag all() + { + return bitfield_flag(static_cast(~UnderlyingType{0})); + } + + bool constexpr operator==(bitfield_flag const f) const noexcept + { return m_val == f.m_val; } + + bool constexpr operator!=(bitfield_flag const f) const noexcept + { return m_val != f.m_val; } + + bitfield_flag& operator|=(bitfield_flag const f) & noexcept + { + m_val |= f.m_val; + return *this; + } + + bitfield_flag& operator&=(bitfield_flag const f) & noexcept + { + m_val &= f.m_val; + return *this; + } + + bitfield_flag& operator^=(bitfield_flag const f) & noexcept + { + m_val ^= f.m_val; + return *this; + } + + constexpr friend bitfield_flag operator|(bitfield_flag const lhs, bitfield_flag const rhs) noexcept + { + return bitfield_flag(lhs.m_val | rhs.m_val); + } + + constexpr friend bitfield_flag operator&(bitfield_flag const lhs, bitfield_flag const rhs) noexcept + { + return bitfield_flag(lhs.m_val & rhs.m_val); + } + + constexpr friend bitfield_flag operator^(bitfield_flag const lhs, bitfield_flag const rhs) noexcept + { + return bitfield_flag(lhs.m_val ^ rhs.m_val); + } + + constexpr bitfield_flag operator~() const noexcept + { + // technically, m_val is promoted to int before applying operator~, which + // means the result may not fit into the underlying type again. So, + // explicitly cast it + return bitfield_flag(static_cast(~m_val)); + } + + bitfield_flag& operator=(bitfield_flag const& rhs) & noexcept = default; + bitfield_flag& operator=(bitfield_flag&& rhs) & noexcept = default; + +#if TORRENT_USE_IOSTREAM + friend std::ostream& operator<<(std::ostream& os, bitfield_flag val) + { return os << static_cast(val); } +#endif + +private: + UnderlyingType m_val; +}; + +} // flags +} // libtorrent + +#endif diff --git a/docs/include/libtorrent/fwd.hpp b/docs/include/libtorrent/fwd.hpp new file mode 100644 index 0000000..44a7223 --- /dev/null +++ b/docs/include/libtorrent/fwd.hpp @@ -0,0 +1,323 @@ +/* + +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2017-2021, Arvid Norberg +Copyright (c) 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FWD_HPP +#define TORRENT_FWD_HPP + +#include "libtorrent/config.hpp" + +namespace libtorrent { + +// include/libtorrent/add_torrent_params.hpp +TORRENT_VERSION_NAMESPACE_3 +struct add_torrent_params; +TORRENT_VERSION_NAMESPACE_3_END + +// include/libtorrent/alert.hpp +struct alert; + +// include/libtorrent/alert_types.hpp +struct dht_routing_bucket; +TORRENT_VERSION_NAMESPACE_3 +struct torrent_alert; +struct peer_alert; +struct tracker_alert; +struct torrent_removed_alert; +struct read_piece_alert; +struct file_completed_alert; +struct file_renamed_alert; +struct file_rename_failed_alert; +struct performance_alert; +struct state_changed_alert; +struct tracker_error_alert; +struct tracker_warning_alert; +struct scrape_reply_alert; +struct scrape_failed_alert; +struct tracker_reply_alert; +struct dht_reply_alert; +struct tracker_announce_alert; +struct hash_failed_alert; +struct peer_ban_alert; +struct peer_unsnubbed_alert; +struct peer_snubbed_alert; +struct peer_error_alert; +struct peer_connect_alert; +struct peer_disconnected_alert; +struct invalid_request_alert; +struct torrent_finished_alert; +struct piece_finished_alert; +struct request_dropped_alert; +struct block_timeout_alert; +struct block_finished_alert; +struct block_downloading_alert; +struct unwanted_block_alert; +struct storage_moved_alert; +struct storage_moved_failed_alert; +struct torrent_deleted_alert; +struct torrent_delete_failed_alert; +struct save_resume_data_alert; +struct save_resume_data_failed_alert; +struct torrent_paused_alert; +struct torrent_resumed_alert; +struct torrent_checked_alert; +struct url_seed_alert; +struct file_error_alert; +struct metadata_failed_alert; +struct metadata_received_alert; +struct udp_error_alert; +struct external_ip_alert; +struct listen_failed_alert; +struct listen_succeeded_alert; +struct portmap_error_alert; +struct portmap_alert; +struct portmap_log_alert; +struct fastresume_rejected_alert; +struct peer_blocked_alert; +struct dht_announce_alert; +struct dht_get_peers_alert; +struct cache_flushed_alert; +struct lsd_peer_alert; +struct trackerid_alert; +struct dht_bootstrap_alert; +struct torrent_error_alert; +struct torrent_need_cert_alert; +struct incoming_connection_alert; +struct add_torrent_alert; +struct state_update_alert; +struct session_stats_alert; +struct dht_error_alert; +struct dht_immutable_item_alert; +struct dht_mutable_item_alert; +struct dht_put_alert; +struct i2p_alert; +struct dht_outgoing_get_peers_alert; +struct log_alert; +struct torrent_log_alert; +struct peer_log_alert; +struct lsd_error_alert; +struct dht_lookup; +struct dht_stats_alert; +struct incoming_request_alert; +struct dht_log_alert; +struct dht_pkt_alert; +struct dht_get_peers_reply_alert; +struct dht_direct_response_alert; +struct picker_log_alert; +struct session_error_alert; +struct dht_live_nodes_alert; +struct session_stats_header_alert; +struct dht_sample_infohashes_alert; +struct block_uploaded_alert; +struct alerts_dropped_alert; +struct socks5_alert; +struct file_prio_alert; +TORRENT_VERSION_NAMESPACE_3_END +struct oversized_file_alert; +struct torrent_conflict_alert; + +// include/libtorrent/announce_entry.hpp +TORRENT_VERSION_NAMESPACE_2 +struct announce_infohash; +struct announce_endpoint; +struct announce_entry; +TORRENT_VERSION_NAMESPACE_2_END + +// include/libtorrent/bdecode.hpp +struct bdecode_node; + +// include/libtorrent/bitfield.hpp +struct bitfield; + +// include/libtorrent/client_data.hpp +struct client_data_t; + +// include/libtorrent/create_torrent.hpp +struct create_torrent; + +// include/libtorrent/disk_buffer_holder.hpp +struct buffer_allocator_interface; +struct disk_buffer_holder; + +// include/libtorrent/disk_interface.hpp +struct open_file_state; +struct disk_interface; +struct storage_holder; + +// include/libtorrent/disk_observer.hpp +struct disk_observer; + +// include/libtorrent/entry.hpp +class entry; + +// include/libtorrent/error_code.hpp +struct storage_error; + +// include/libtorrent/extensions.hpp +TORRENT_VERSION_NAMESPACE_3 +struct plugin; +TORRENT_VERSION_NAMESPACE_3_END +struct torrent_plugin; +struct peer_plugin; +struct crypto_plugin; + +// include/libtorrent/file_storage.hpp +struct file_slice; +class file_storage; + +// include/libtorrent/hasher.hpp +TORRENT_CRYPTO_NAMESPACE +class hasher; +class hasher256; +TORRENT_CRYPTO_NAMESPACE_END + +// include/libtorrent/info_hash.hpp +struct info_hash_t; + +// include/libtorrent/ip_filter.hpp +struct ip_filter; +class port_filter; + +// include/libtorrent/kademlia/dht_state.hpp +namespace dht { +struct dht_state; +} + +// include/libtorrent/kademlia/dht_storage.hpp +namespace dht { +struct dht_storage_counters; +} +namespace dht { +struct dht_storage_interface; +} + +// include/libtorrent/peer_class.hpp +struct peer_class_info; + +// include/libtorrent/peer_class_type_filter.hpp +struct peer_class_type_filter; + +// include/libtorrent/peer_connection_handle.hpp +struct peer_connection_handle; +struct bt_peer_connection_handle; + +// include/libtorrent/peer_info.hpp +TORRENT_VERSION_NAMESPACE_2 +struct peer_info; +TORRENT_VERSION_NAMESPACE_2_END + +// include/libtorrent/peer_request.hpp +struct peer_request; + +// include/libtorrent/performance_counters.hpp +struct counters; + +// include/libtorrent/piece_block.hpp +struct piece_block; + +// include/libtorrent/session.hpp +struct session_proxy; +struct session; + +// include/libtorrent/session_handle.hpp +struct session_handle; + +// include/libtorrent/session_params.hpp +TORRENT_VERSION_NAMESPACE_3 +struct session_params; +TORRENT_VERSION_NAMESPACE_3_END + +// include/libtorrent/session_stats.hpp +struct stats_metric; + +// include/libtorrent/settings_pack.hpp +struct settings_interface; +struct settings_pack; + +// include/libtorrent/storage_defs.hpp +struct storage_params; + +// include/libtorrent/torrent_handle.hpp +struct block_info; +struct partial_piece_info; +struct torrent_handle; + +// include/libtorrent/torrent_info.hpp +struct web_seed_entry; +struct load_torrent_limits; +TORRENT_VERSION_NAMESPACE_3 +class torrent_info; +TORRENT_VERSION_NAMESPACE_3_END + +// include/libtorrent/torrent_status.hpp +TORRENT_VERSION_NAMESPACE_3 +struct torrent_status; +TORRENT_VERSION_NAMESPACE_3_END + +#if TORRENT_ABI_VERSION <= 2 + +// include/libtorrent/alert_types.hpp +TORRENT_VERSION_NAMESPACE_3 +struct torrent_added_alert; +struct stats_alert; +struct anonymous_mode_alert; +struct mmap_cache_alert; +TORRENT_VERSION_NAMESPACE_3_END + +// include/libtorrent/file_storage.hpp +struct file_entry; + +// include/libtorrent/fingerprint.hpp +struct fingerprint; + +// include/libtorrent/kademlia/dht_settings.hpp +namespace dht { +struct dht_settings; +} + +// include/libtorrent/session_settings.hpp +struct pe_settings; + +// include/libtorrent/session_status.hpp +struct utp_status; +struct session_status; + +#endif // TORRENT_ABI_VERSION + + using file_layout = file_storage; + +} + +namespace lt = libtorrent; + +#endif // TORRENT_FWD_HPP diff --git a/docs/include/libtorrent/gzip.hpp b/docs/include/libtorrent/gzip.hpp new file mode 100644 index 0000000..f8f96c1 --- /dev/null +++ b/docs/include/libtorrent/gzip.hpp @@ -0,0 +1,137 @@ +/* + +Copyright (c) 2008-2009, 2014-2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_GZIP_HPP_INCLUDED +#define TORRENT_GZIP_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/span.hpp" + +#include + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT void inflate_gzip( + span in + , std::vector& buffer + , int maximum_size + , error_code& ec); + + // get the ``error_category`` for zip errors + TORRENT_EXPORT boost::system::error_category& gzip_category(); + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + inline boost::system::error_category& get_gzip_category() + { return gzip_category(); } +#endif + +namespace gzip_errors { + // libtorrent uses boost.system's ``error_code`` class to represent errors. libtorrent has + // its own error category get_gzip_category() with the error codes defined by error_code_enum. + enum error_code_enum + { + // Not an error + no_error = 0, + + // the supplied gzip buffer has invalid header + invalid_gzip_header, + + // the gzip buffer would inflate to more bytes than the specified + // maximum size, and was rejected. + inflated_data_too_large, + + // available inflate data did not terminate + data_did_not_terminate, + + // output space exhausted before completing inflate + space_exhausted, + + // invalid block type (type == 3) + invalid_block_type, + + // stored block length did not match one's complement + invalid_stored_block_length, + + // dynamic block code description: too many length or distance codes + too_many_length_or_distance_codes, + + // dynamic block code description: code lengths codes incomplete + code_lengths_codes_incomplete, + + // dynamic block code description: repeat lengths with no first length + repeat_lengths_with_no_first_length, + + // dynamic block code description: repeat more than specified lengths + repeat_more_than_specified_lengths, + + // dynamic block code description: invalid literal/length code lengths + invalid_literal_length_code_lengths, + + // dynamic block code description: invalid distance code lengths + invalid_distance_code_lengths, + + // invalid literal/length or distance code in fixed or dynamic block + invalid_literal_code_in_block, + + // distance is too far back in fixed or dynamic block + distance_too_far_back_in_block, + + // an unknown error occurred during gzip inflation + unknown_gzip_error, + + // the number of error codes + error_code_max + }; + + // hidden + TORRENT_EXPORT boost::system::error_code make_error_code(error_code_enum e); +} // namespace gzip_errors + +} // namespace libtorrent + +namespace boost { +namespace system { + +template<> +struct is_error_code_enum +{ static const bool value = true; }; + +template<> +struct is_error_condition_enum +{ static const bool value = true; }; + +} +} + +#endif diff --git a/docs/include/libtorrent/hash_picker.hpp b/docs/include/libtorrent/hash_picker.hpp new file mode 100644 index 0000000..ed3e95f --- /dev/null +++ b/docs/include/libtorrent/hash_picker.hpp @@ -0,0 +1,246 @@ +/* + +Copyright (c) 2017, BitTorrent Inc. +Copyright (c) 2019-2021, Arvid Norberg +Copyright (c) 2019, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. +* Neither the name of the author nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HASH_PICKER_HPP_INCLUDED +#define TORRENT_HASH_PICKER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/merkle_tree.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/index_range.hpp" +#include +#include + +namespace libtorrent +{ + struct torrent_peer; + + struct set_block_hash_result + { + enum class result + { + // hash is verified + success, + // hash cannot be verified yet + unknown, + // hash conflict in leaf node + block_hash_failed, + // hash conflict in a parent node + piece_hash_failed + }; + + explicit set_block_hash_result(result s) : status(s), first_verified_block(0), num_verified(0) {} + set_block_hash_result(result st, int first_block, int num) : status(st), first_verified_block(first_block), num_verified(num) {} + + index_range piece_range( + piece_index_t const piece + , int const blocks_per_piece) const + { + using delta = piece_index_t::diff_type; + piece_index_t const start = piece + delta(first_verified_block / blocks_per_piece); + piece_index_t const end = start + delta(num_verified / blocks_per_piece); + return {start, end}; + } + + static set_block_hash_result success(int first_block, int num) { return set_block_hash_result(result::success, first_block, num); } + static set_block_hash_result unknown() { return set_block_hash_result(result::unknown); } + static set_block_hash_result block_hash_failed() { return set_block_hash_result(result::block_hash_failed); } + static set_block_hash_result piece_hash_failed(int first_block, int num) { return set_block_hash_result(result::piece_hash_failed, first_block, num); } + + result status; + // if status is success, this will hold the index of the first verified + // block hash as an offset from the index of the first block in the piece + int first_verified_block; + int num_verified; + }; + + struct add_hashes_result + { + explicit add_hashes_result(bool const v) : valid(v) {} + + bool valid; + // the vector contains the block indices (within the piece) that failed + // the hash check + std::vector>> hash_failed; + std::vector hash_passed; + }; + + struct node_index + { + node_index(file_index_t f, std::int32_t n) : file(f), node(n) {} + bool operator==(node_index const& o) const { return file == o.file && node == o.node; } + file_index_t file; + std::int32_t node; + }; + + // the hash request represents a range of hashes in the merkle hash tree for + // a specific file ('file'). + struct TORRENT_EXTRA_EXPORT hash_request + { + hash_request() = default; + hash_request(file_index_t const f, int const b, int const i, int const c, int const p) + : file(f), base(b), index(i), count(c), proof_layers(p) + {} + + hash_request(hash_request const&) = default; + hash_request& operator=(hash_request const& o) = default; + + bool operator==(hash_request const& o) const + { + return file == o.file && base == o.base && index == o.index && count == o.count + && proof_layers == o.proof_layers; + } + + file_index_t file{0}; + // indicates which *level* of the tree we're referring to. 0 means the + // leaf level. + int base = 0; + // the index of the first hash at the specified level. + int index = 0; + // the number of hashes in the range + int count = 0; + int proof_layers = 0; + }; + + // validates the hash_request, to ensure its invariant as well as matching + // the torrent's file_storage and the number of hashes accompanying the + // request + TORRENT_EXTRA_EXPORT + bool validate_hash_request(hash_request const& hr, file_storage const& fs); + + class TORRENT_EXTRA_EXPORT hash_picker + { + public: + hash_picker(file_storage const& files + , aux::vector& trees); + + hash_request pick_hashes(typed_bitfield const& pieces); + + add_hashes_result add_hashes(hash_request const& req, span hashes); + // TODO: support batched adding of block hashes for reduced overhead? + set_block_hash_result set_block_hash(piece_index_t piece, int offset, sha256_hash const& h); + void hashes_rejected(hash_request const& req); + void verify_block_hashes(piece_index_t index); + + // do we know the piece layer hash for a piece + bool have_hash(piece_index_t index) const; + // do we know all the block hashes for a file? + bool have_all(file_index_t file) const; + bool have_all() const; + bool piece_verified(piece_index_t piece) const; + + int piece_layer() const { return m_piece_layer; } + + private: + // returns the number of proof layers needed to verify the node's hash + int layers_to_verify(node_index idx) const; + int file_num_layers(file_index_t idx) const; + + struct piece_hash_request + { + time_point last_request = min_time(); + int num_requests = 0; + bool have = false; + }; + + struct priority_block_request + { + priority_block_request(file_index_t const f, int const b) + : file(f), block(b) {} + file_index_t file; + int block; + int num_requests = 0; + bool operator==(priority_block_request const& o) const + { return file == o.file && block == o.block; } + bool operator!=(priority_block_request const& o) const + { return !(*this == o); } + bool operator<(priority_block_request const& o) const + { return num_requests < o.num_requests; } + }; + + struct piece_block_request + { + piece_block_request(file_index_t const f, piece_index_t::diff_type const p) : file(f), piece(p) {} + file_index_t file; + // the piece from the start of the file + piece_index_t::diff_type piece; + time_point last_request = min_time(); + int num_requests = 0; + bool operator==(piece_block_request const& o) const + { return file == o.file && piece == o.piece; } + bool operator!=(piece_block_request const& o) const + { return !(*this == o); } + bool operator<(piece_block_request const& o) const + { return num_requests < o.num_requests; } + }; + + file_storage const& m_files; + aux::vector& m_merkle_trees; + + // information about every 512-piece span of each file. We request hashes + // for 512 pieces at a time + aux::vector, file_index_t> m_piece_hash_requested; + + // this is for a future per-block request feature +#if 0 + // blocks are only added to this list if there is a time critical block which + // has been downloaded but we don't have its hash or if the initial request + // for the hash was rejected + // this block hash will be requested from every peer possible until the hash + // is received + // the vector is sorted by the number of requests sent for each block + aux::vector m_priority_block_requests; +#endif + + // when a piece fails hash check a request is queued to download the piece's + // block hashes + aux::vector m_piece_block_requests; + + // this is the number of tree levels in a piece. if the piece size is 16 + // kiB, this is 0, since there is no tree per piece. If the piece size is + // 32 kiB, it's 1, and so on. + int const m_piece_layer; + + // this is the number of tree layers for a 512-piece range, which is + // the granularity with which we send hash requests. The number of layers + // all the way down the the block level. + int const m_piece_tree_root_layer; + }; +} // namespace libtorrent + +#endif // TORRENT_HASH_PICKER_HPP_INCLUDED diff --git a/docs/include/libtorrent/hasher.hpp b/docs/include/libtorrent/hasher.hpp new file mode 100644 index 0000000..28e293f --- /dev/null +++ b/docs/include/libtorrent/hasher.hpp @@ -0,0 +1,185 @@ +/* + +Copyright (c) 2003-2004, 2007, 2009, 2012-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, 2019, Andrei Kurushin +Copyright (c) 2017, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HASHER_HPP_INCLUDED +#define TORRENT_HASHER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/span.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#ifdef TORRENT_USE_LIBGCRYPT +#include + +#elif TORRENT_USE_COMMONCRYPTO +#include + +#elif TORRENT_USE_CNG +#include "libtorrent/aux_/win_cng.hpp" + +#elif TORRENT_USE_CRYPTOAPI +#include "libtorrent/aux_/win_crypto_provider.hpp" + +#if !TORRENT_USE_CRYPTOAPI_SHA_512 +#include "libtorrent/sha256.hpp" +#endif + +#elif defined TORRENT_USE_LIBCRYPTO + +extern "C" { +#include +} + +#else +#include "libtorrent/sha1.hpp" +#include "libtorrent/sha256.hpp" +#endif +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { +TORRENT_CRYPTO_NAMESPACE + + // this is a SHA-1 hash class. + // + // You use it by first instantiating it, then call ``update()`` to feed it + // with data. i.e. you don't have to keep the entire buffer of which you want to + // create the hash in memory. You can feed the hasher parts of it at a time. When + // You have fed the hasher with all the data, you call ``final()`` and it + // will return the sha1-hash of the data. + // + // The constructor that takes a ``char const*`` and an integer will construct the + // sha1 context and feed it the data passed in. + // + // If you want to reuse the hasher object once you have created a hash, you have to + // call ``reset()`` to reinitialize it. + // + // The built-in software version of sha1-algorithm was implemented + // by Steve Reid and released as public domain. + // For more info, see ``src/sha1.cpp``. + class TORRENT_EXPORT hasher + { + public: + + hasher(); + + // this is the same as default constructing followed by a call to + // ``update(data, len)``. + hasher(char const* data, int len); + explicit hasher(span data); + hasher(hasher const&); + hasher& operator=(hasher const&) &; + + // append the following bytes to what is being hashed + hasher& update(span data); + hasher& update(char const* data, int len); + + // returns the SHA-1 digest of the buffers previously passed to + // update() and the hasher constructor. + sha1_hash final(); + + // restore the hasher state to be as if the hasher has just been + // default constructed. + void reset(); + + // hidden + ~hasher(); + + private: + +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_hd_t m_context; +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA1_CTX m_context; +#elif TORRENT_USE_CNG + aux::cng_hash m_context; +#elif TORRENT_USE_CRYPTOAPI + aux::crypt_hash m_context; +#elif defined TORRENT_USE_LIBCRYPTO + SHA_CTX m_context; +#else + sha1_ctx m_context; +#endif + }; + + class TORRENT_EXPORT hasher256 + { + public: + hasher256(); + + // this is the same as default constructing followed by a call to + // ``update(data, len)``. + hasher256(char const* data, int len); + explicit hasher256(span data); + hasher256(hasher256 const&); + hasher256& operator=(hasher256 const&) &; + + // append the following bytes to what is being hashed + hasher256& update(span data); + hasher256& update(char const* data, int len); + + // returns the SHA-1 digest of the buffers previously passed to + // update() and the hasher constructor. + sha256_hash final(); + + // restore the hasher state to be as if the hasher has just been + // default constructed. + void reset(); + + ~hasher256(); + + private: +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_hd_t m_context; +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA256_CTX m_context; +#elif TORRENT_USE_CNG + aux::cng_hash m_context; +#elif TORRENT_USE_CRYPTOAPI_SHA_512 + aux::crypt_hash m_context; +#elif defined TORRENT_USE_LIBCRYPTO + SHA256_CTX m_context; +#else + sha256_ctx m_context; +#endif + }; + +TORRENT_CRYPTO_NAMESPACE_END +} + +#endif // TORRENT_HASHER_HPP_INCLUDED diff --git a/docs/include/libtorrent/hex.hpp b/docs/include/libtorrent/hex.hpp new file mode 100644 index 0000000..140f944 --- /dev/null +++ b/docs/include/libtorrent/hex.hpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2004, 2009, 2013, 2015-2020, Arvid Norberg +Copyright (c) 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HEX_HPP_INCLUDED +#define TORRENT_HEX_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/span.hpp" + +#include + +namespace libtorrent { + +namespace aux { + + TORRENT_EXTRA_EXPORT int hex_to_int(char in); + TORRENT_EXTRA_EXPORT bool is_hex(span in); + +#if TORRENT_ABI_VERSION == 1 +#define TORRENT_CONDITIONAL_EXPORT TORRENT_EXPORT +#else +#define TORRENT_CONDITIONAL_EXPORT TORRENT_EXTRA_EXPORT +#endif + + // The overload taking a ``std::string`` converts (binary) the string ``s`` + // to hexadecimal representation and returns it. + // The overload taking a ``char const*`` and a length converts the binary + // buffer [``in``, ``in`` + len) to hexadecimal and prints it to the buffer + // ``out``. The caller is responsible for making sure the buffer pointed to + // by ``out`` is large enough, i.e. has at least len * 2 bytes of space. + TORRENT_CONDITIONAL_EXPORT std::string to_hex(span s); + TORRENT_CONDITIONAL_EXPORT void to_hex(span in, char* out); + TORRENT_CONDITIONAL_EXPORT void to_hex(char const* in, int len, char* out); + + // converts the buffer [``in``, ``in`` + len) from hexadecimal to + // binary. The binary output is written to the buffer pointed to + // by ``out``. The caller is responsible for making sure the buffer + // at ``out`` has enough space for the result to be written to, i.e. + // (len + 1) / 2 bytes. + TORRENT_CONDITIONAL_EXPORT bool from_hex(span in, char* out); + +#undef TORRENT_CONDITIONAL_EXPORT + +} // namespace aux + +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // deprecated in 1.2 + TORRENT_DEPRECATED + inline void to_hex(char const* in, int len, char* out) + { aux::to_hex({in, len}, out); } + TORRENT_DEPRECATED + inline std::string to_hex(std::string const& s) + { return aux::to_hex(s); } + TORRENT_DEPRECATED + inline bool from_hex(char const *in, int len, char* out) + { return aux::from_hex({in, len}, out); } + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif +} // namespace libtorrent + +#endif // TORRENT_HEX_HPP_INCLUDED diff --git a/docs/include/libtorrent/http_connection.hpp b/docs/include/libtorrent/http_connection.hpp new file mode 100644 index 0000000..f59fb60 --- /dev/null +++ b/docs/include/libtorrent/http_connection.hpp @@ -0,0 +1,262 @@ +/* + +Copyright (c) 2017, Steven Siloti +Copyright (c) 2007-2020, 2022, Arvid Norberg +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2016-2017, 2020, Alden Torres +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_CONNECTION +#define TORRENT_HTTP_CONNECTION + +#include +#include +#include + +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/http_parser.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/i2p_stream.hpp" +#include "libtorrent/aux_/socket_type.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/resolver_interface.hpp" +#include "libtorrent/optional.hpp" +#include "libtorrent/ssl.hpp" + +namespace libtorrent { + +struct http_connection; +namespace aux { struct resolver_interface; } + +struct close_visitor; + +// internal +constexpr int default_max_bottled_buffer_size = 2 * 1024 * 1024; + +using http_handler = std::function data, http_connection&)>; + +using http_connect_handler = std::function; + +using http_filter_handler = std::function&)>; +using hostname_filter_handler = std::function; + +struct bind_info_t +{ + std::string device; + address ip; + bool operator==(bind_info_t const& rhs) const + { + return device == rhs.device && ip == rhs.ip; + } +}; + +// when bottled, the last two arguments to the handler +// will always be 0 +struct TORRENT_EXTRA_EXPORT http_connection + : std::enable_shared_from_this +{ + friend struct close_visitor; + + http_connection(io_context& ios + , aux::resolver_interface& resolver + , http_handler handler + , bool bottled + , int max_bottled_buffer_size + , http_connect_handler ch + , http_filter_handler fh + , hostname_filter_handler hfh +#if TORRENT_USE_SSL + , ssl::context* ssl_ctx +#endif + ); + + // non-copyable + http_connection(http_connection const&) = delete; + http_connection& operator=(http_connection const&) = delete; + + virtual ~http_connection(); + + void rate_limit(int limit); + + int rate_limit() const + { return m_rate_limit; } + + std::string m_sendbuffer; + + void get(std::string const& url, time_duration timeout = seconds(30) + , aux::proxy_settings const* ps = nullptr, int handle_redirects = 5 + , std::string const& user_agent = std::string() + , boost::optional const& bind_addr = boost::none + , aux::resolver_flags resolve_flags = aux::resolver_flags{}, std::string const& auth_ = std::string() +#if TORRENT_USE_I2P + , i2p_connection* i2p_conn = nullptr +#endif + ); + + void start(std::string const& hostname, int port + , time_duration timeout, aux::proxy_settings const* ps = nullptr + , bool ssl = false, int handle_redirect = 5 + , boost::optional const& bind_addr = boost::none + , aux::resolver_flags resolve_flags = aux::resolver_flags{} +#if TORRENT_USE_I2P + , i2p_connection* i2p_conn = nullptr +#endif + ); + + void close(bool force = false); + + aux::socket_type const& socket() const { return *m_sock; } + + std::vector const& endpoints() const { return m_endpoints; } + + std::string const& url() const { return m_url; } + +private: + +#if TORRENT_USE_I2P + void connect_i2p_tracker(char const* destination); + void on_i2p_resolve(error_code const& e + , char const* destination); +#endif + void on_resolve(error_code const& e, std::vector
    const& addresses); + void connect(); + void on_connect(error_code const& e); + void on_write(error_code const& e); + void on_read(error_code const& e, std::size_t bytes_transferred); + static void on_timeout(std::weak_ptr p + , error_code const& e); + void on_assign_bandwidth(error_code const& e); + + void callback(error_code e, span data = {}); + + aux::vector m_recvbuffer; + io_context& m_ios; + + std::string m_hostname; + std::string m_url; + std::string m_user_agent; + + aux::vector m_endpoints; + + // if the current connection attempt fails, we'll connect to the + // endpoint with this index (in m_endpoints) next + int m_next_ep; + + boost::optional m_sock; + +#if TORRENT_USE_SSL + ssl::context* m_ssl_ctx; +#endif + +#if TORRENT_USE_I2P + i2p_connection* m_i2p_conn; +#endif + aux::resolver_interface& m_resolver; + + http_parser m_parser; + http_handler m_handler; + http_connect_handler m_connect_handler; + http_filter_handler m_filter_handler; + hostname_filter_handler m_hostname_filter_handler; + deadline_timer m_timer; + + time_duration m_completion_timeout; + + // the timer fires every 250 millisecond as long + // as all the quota was used. + deadline_timer m_limiter_timer; + + time_point m_last_receive; + time_point m_start_time; + + // specifies whether or not the connection is + // configured to use a proxy + aux::proxy_settings m_proxy; + + // the address and/or device to bind to. unset means do not bind + boost::optional m_bind_addr; + + // if username password was passed in, remember it in case we need to + // re-issue the request for a redirect + std::string m_auth; + + int m_read_pos; + + // the number of redirects to follow (in sequence) + int m_redirects; + + // maximum size of bottled buffer + int m_max_bottled_buffer_size; + + // the current download limit, in bytes per second + // 0 is unlimited. + int m_rate_limit; + + // the number of bytes we are allowed to receive + int m_download_quota; + + // used for DNS lookups + aux::resolver_flags m_resolve_flags; + + std::uint16_t m_port; + + // bottled means that the handler is called once, when + // everything is received (and buffered in memory). + // non bottled means that once the headers have been + // received, data is streamed to the handler + bool m_bottled; + + // set to true the first time the handler is called + bool m_called = false; + + // only hand out new quota 4 times a second if the + // quota is 0. If it isn't 0 wait for it to reach + // 0 and continue to hand out quota at that time. + bool m_limiter_timer_active = false; + + // true if the connection is using ssl + bool m_ssl = false; + + bool m_abort = false; + + // true while waiting for an async_connect + bool m_connecting = false; + + // true while resolving hostname + bool m_resolving_host = false; +}; + +} + +#endif diff --git a/docs/include/libtorrent/http_parser.hpp b/docs/include/libtorrent/http_parser.hpp new file mode 100644 index 0000000..f26faa5 --- /dev/null +++ b/docs/include/libtorrent/http_parser.hpp @@ -0,0 +1,168 @@ +/* + +Copyright (c) 2008-2017, 2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_PARSER_HPP_INCLUDED +#define TORRENT_HTTP_PARSER_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/time.hpp" // for seconds32 +#include "libtorrent/optional.hpp" +#include "libtorrent/aux_/strview_less.hpp" + +namespace libtorrent { + + // return true if the status code is 200, 206, or in the 300-400 range + TORRENT_EXTRA_EXPORT bool is_ok_status(int http_status); + + // return true if the status code is a redirect + TORRENT_EXTRA_EXPORT bool is_redirect(int http_status); + + TORRENT_EXTRA_EXPORT std::string resolve_redirect_location(std::string referrer + , std::string location); + + class TORRENT_EXTRA_EXPORT http_parser + { + public: + enum flags_t { dont_parse_chunks = 1 }; + explicit http_parser(int flags = 0); + ~http_parser(); + std::string const& header(string_view key) const; + boost::optional header_duration(string_view key) const; + std::string const& protocol() const { return m_protocol; } + int status_code() const { return m_status_code; } + std::string const& method() const { return m_method; } + std::string const& path() const { return m_path; } + std::string const& message() const { return m_server_message; } + span get_body() const; + bool header_finished() const { return m_state == read_body; } + bool finished() const { return m_finished; } + std::tuple incoming(span recv_buffer + , bool& error); + int body_start() const { return m_body_start_pos; } + std::int64_t content_length() const { return m_content_length; } + std::pair content_range() const + { return std::make_pair(m_range_start, m_range_end); } + + // returns true if this response is using chunked encoding. + // in this case the body is split up into chunks. You need + // to call parse_chunk_header() for each chunk, starting with + // the start of the body. + bool chunked_encoding() const { return m_chunked_encoding; } + + // removes the chunk headers from the supplied buffer. The buffer + // must be the stream received from the http server this parser + // instanced parsed. It will use the internal chunk list to determine + // where the chunks are in the buffer. It returns the new length of + // the buffer + span collapse_chunk_headers(span buffer) const; + + // returns false if the buffer doesn't contain a complete + // chunk header. In this case, call the function again with + // a bigger buffer once more bytes have been received. + // chunk_size is filled in with the number of bytes in the + // chunk that follows. 0 means the response terminated. In + // this case there might be additional headers in the parser + // object. + // header_size is filled in with the number of bytes the header + // itself was. Skip this number of bytes to get to the actual + // chunk data. + // if the function returns false, the chunk size and header + // size may still have been modified, but their values are + // undefined + bool parse_chunk_header(span buf + , std::int64_t* chunk_size, int* header_size); + + // reset the whole state and start over + void reset(); + + bool connection_close() const { return m_connection_close; } + + std::multimap const& headers() const { return m_header; } + std::vector> const& chunks() const { return m_chunked_ranges; } + + private: + std::int64_t m_recv_pos = 0; + std::string m_method; + std::string m_path; + std::string m_protocol; + std::string m_server_message; + + std::int64_t m_content_length = -1; + std::int64_t m_range_start = -1; + std::int64_t m_range_end = -1; + + std::multimap m_header; + span m_recv_buffer; + // contains offsets of the first and one-past-end of + // each chunked range in the response + std::vector> m_chunked_ranges; + + // while reading a chunk, this is the offset where the + // current chunk will end (it refers to the first character + // in the chunk tail header or the next chunk header) + std::int64_t m_cur_chunk_end = -1; + + int m_status_code = -1; + + // the sum of all chunk headers read so far + int m_chunk_header_size = 0; + + int m_partial_chunk_header = 0; + + // controls some behaviors of the parser + int m_flags; + + int m_body_start_pos = 0; + + enum { read_status, read_header, read_body, error_state } m_state = read_status; + + // this is true if the server is HTTP/1.0 or + // if it sent "connection: close" + bool m_connection_close = false; + bool m_chunked_encoding = false; + bool m_finished = false; + }; + +} + +#endif // TORRENT_HTTP_PARSER_HPP_INCLUDED diff --git a/docs/include/libtorrent/http_seed_connection.hpp b/docs/include/libtorrent/http_seed_connection.hpp new file mode 100644 index 0000000..3d269ac --- /dev/null +++ b/docs/include/libtorrent/http_seed_connection.hpp @@ -0,0 +1,116 @@ +/* + +Copyright (c) 2008-2010, 2012, 2014-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_SEED_CONNECTION_HPP_INCLUDED +#define TORRENT_HTTP_SEED_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/web_connection_base.hpp" +#include "libtorrent/piece_block_progress.hpp" + +namespace libtorrent { + + struct torrent; + struct peer_request; + + class TORRENT_EXTRA_EXPORT http_seed_connection + : public web_connection_base + { + friend struct invariant_access; + public: + + // this is the constructor where the we are the active part. + // The peer_connection should handshake and verify that the + // other end has the correct id + http_seed_connection(peer_connection_args& pack + , web_seed_t& web); + + connection_type type() const override + { return connection_type::http_seed; } + + // called from the main loop when this connection has any + // work to do. + void on_receive(error_code const& error + , std::size_t bytes_transferred) override; + + void on_connected() override; + + std::string const& url() const override { return m_url; } + + void get_specific_peer_info(peer_info& p) const override; + void disconnect(error_code const& ec, operation_t op + , disconnect_severity_t error = peer_connection_interface::normal) override; + + void write_request(peer_request const& r) override; + + private: + + // returns the block currently being + // downloaded. And the progress of that + // block. If the peer isn't downloading + // a piece for the moment, the boost::optional + // will be invalid. + piece_block_progress downloading_piece_progress() const override; + + void disable(error_code const& ec); + + // this is const since it's used as a key in the web seed list in the torrent + // if it's changed referencing back into that list will fail + const std::string m_url; + + web_seed_t* m_web; + + // the number of bytes left to receive of the response we're + // currently parsing + std::int64_t m_response_left; + + // this is the offset inside the current receive + // buffer where the next chunk header will be. + // this is updated for each chunk header that's + // parsed. It does not necessarily point to a valid + // offset in the receive buffer, if we haven't received + // it yet. This offset never includes the HTTP header + std::int64_t m_chunk_pos; + + // this is the number of bytes we've already received + // from the next chunk header we're waiting for + int m_partial_chunk_header; + }; +} + +#endif // TORRENT_WEB_PEER_CONNECTION_HPP_INCLUDED diff --git a/docs/include/libtorrent/http_stream.hpp b/docs/include/libtorrent/http_stream.hpp new file mode 100644 index 0000000..9151dce --- /dev/null +++ b/docs/include/libtorrent/http_stream.hpp @@ -0,0 +1,230 @@ +/* + +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2007, 2010, 2015, 2019, 2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_STREAM_HPP_INCLUDED +#define TORRENT_HTTP_STREAM_HPP_INCLUDED + +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/aux_/escape_string.hpp" // for base64encode +#include "libtorrent/socket_io.hpp" // for print_endpoint + +namespace libtorrent { + +class http_stream : public proxy_base +{ +public: + + explicit http_stream(io_context& io_context) + : proxy_base(io_context) + , m_no_connect(false) + {} + + void set_no_connect(bool c) { m_no_connect = c; } + + void set_username(std::string const& user + , std::string const& password) + { + m_user = user; + m_password = password; + } + + void set_dst_name(std::string const& host) + { + m_dst_name = host; + } + + void close(error_code& ec) + { + m_dst_name.clear(); + proxy_base::close(ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void close() + { + m_dst_name.clear(); + proxy_base::close(); + } +#endif + + template + void async_connect(endpoint_type const& endpoint, Handler const& handler) + { + m_remote_endpoint = endpoint; + + // the connect is split up in the following steps: + // 1. resolve name of proxy server + // 2. connect to proxy server + // 3. send HTTP CONNECT method and possibly username+password + // 4. read CONNECT response + + m_resolver.async_resolve(m_hostname, to_string(m_port).data(), wrap_allocator( + [this](error_code const& ec, tcp::resolver::results_type ips, Handler hn) { + name_lookup(ec, std::move(ips), std::move(hn)); + }, std::move(handler))); + } + +private: + + template + void name_lookup(error_code const& e, tcp::resolver::results_type ips + , Handler h) + { + if (handle_error(e, h)) return; + + auto i = ips.begin(); + m_sock.async_connect(i->endpoint(), wrap_allocator( + [this](error_code const& ec, Handler hn) { + connected(ec, std::move(hn)); + }, std::move(h))); + } + + template + void connected(error_code const& e, Handler h) + { + if (handle_error(e, h)) return; + + using namespace libtorrent::aux; + + if (m_no_connect) + { + std::vector().swap(m_buffer); + std::move(h)(e); + return; + } + + // send CONNECT + std::back_insert_iterator> p(m_buffer); + std::string const endpoint = print_endpoint(m_remote_endpoint); + write_string("CONNECT " + endpoint + " HTTP/1.0\r\n", p); + if (!m_user.empty()) + { + write_string("Proxy-Authorization: Basic " + base64encode( + m_user + ":" + m_password) + "\r\n", p); + } + write_string("\r\n", p); + async_write(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake1(ec, std::move(hn)); + }, std::move(h))); + } + + template + void handshake1(error_code const& e, Handler h) + { + if (handle_error(e, h)) return; + + // read one byte from the socket + m_buffer.resize(1); + async_read(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake2(ec, std::move(hn)); + }, std::move(h))); + } + + template + void handshake2(error_code const& e, Handler h) + { + if (handle_error(e, h)) return; + + std::size_t const read_pos = m_buffer.size(); + // look for \n\n and \r\n\r\n + // both of which means end of http response header + bool found_end = false; + if (read_pos > 2 && m_buffer[read_pos - 1] == '\n') + { + if (m_buffer[read_pos - 2] == '\n') + { + found_end = true; + } + else if (read_pos > 4 + && m_buffer[read_pos - 2] == '\r' + && m_buffer[read_pos - 3] == '\n' + && m_buffer[read_pos - 4] == '\r') + { + found_end = true; + } + } + + if (found_end) + { + m_buffer.push_back(0); + char const* status = std::strchr(m_buffer.data(), ' '); + if (status == nullptr) + { + h(boost::asio::error::operation_not_supported); + error_code ec; + close(ec); + return; + } + + status++; + int const code = std::atoi(status); + if (code != 200) + { + h(boost::asio::error::operation_not_supported); + error_code ec; + close(ec); + return; + } + + h(e); + std::vector().swap(m_buffer); + return; + } + + // read another byte from the socket + m_buffer.resize(read_pos + 1); + async_read(m_sock, boost::asio::buffer(m_buffer.data() + read_pos, 1), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake2(ec, std::move(hn)); + }, std::move(h))); + } + + // send and receive buffer + std::vector m_buffer; + // proxy authentication + std::string m_user; + std::string m_password; + std::string m_dst_name; + + // this is true if the connection is HTTP based and + // want to talk directly to the proxy + bool m_no_connect; +}; + +} + +#endif diff --git a/docs/include/libtorrent/http_tracker_connection.hpp b/docs/include/libtorrent/http_tracker_connection.hpp new file mode 100644 index 0000000..0c775af --- /dev/null +++ b/docs/include/libtorrent/http_tracker_connection.hpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2004, 2006-2009, 2012, 2014-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, 2018, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED +#define TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED + +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/tracker_manager.hpp" // for tracker_connection + +namespace libtorrent { + + class tracker_manager; + struct http_connection; + class http_parser; + struct bdecode_node; + struct peer_entry; + + class TORRENT_EXTRA_EXPORT http_tracker_connection + : public tracker_connection + { + friend class tracker_manager; + public: + + http_tracker_connection( + io_context& ios + , tracker_manager& man + , tracker_request req + , std::weak_ptr c); + + void start() override; + void close() override; + + private: + + std::shared_ptr shared_from_this() + { + return std::static_pointer_cast( + tracker_connection::shared_from_this()); + } + + void on_filter(http_connection& c, std::vector& endpoints); + bool on_filter_hostname(http_connection& c, string_view hostname); + void on_connect(http_connection& c); + void on_response(error_code const& ec, http_parser const& parser + , span data); + + void on_timeout(error_code const&) override {} + + std::shared_ptr m_tracker_connection; + address m_tracker_ip; + io_context& m_ioc; + }; + + TORRENT_EXTRA_EXPORT tracker_response parse_tracker_response( + span data, error_code& ec + , tracker_request_flags_t flags, sha1_hash const& scrape_ih); + + TORRENT_EXTRA_EXPORT bool extract_peer_info(bdecode_node const& info + , peer_entry& ret, error_code& ec); +} + +#endif // TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED diff --git a/docs/include/libtorrent/i2p_stream.hpp b/docs/include/libtorrent/i2p_stream.hpp new file mode 100644 index 0000000..55d6914 --- /dev/null +++ b/docs/include/libtorrent/i2p_stream.hpp @@ -0,0 +1,642 @@ +/* + +Copyright (c) 2009, 2013-2019, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_I2P_STREAM_HPP_INCLUDED +#define TORRENT_I2P_STREAM_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_I2P + +#include +#include +#include +#include + +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/proxy_settings.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/hex.hpp" // for to_hex +#include "libtorrent/debug.hpp" + +namespace libtorrent { + +namespace i2p_error { + + // error values for the i2p_category error_category. + enum i2p_error_code + { + no_error = 0, + parse_failed, + cant_reach_peer, + i2p_error, + invalid_key, + invalid_id, + timeout, + key_not_found, + duplicated_id, + num_errors + }; + + // hidden + TORRENT_EXPORT boost::system::error_code make_error_code(i2p_error_code e); +} +} + +namespace boost { +namespace system { + +template<> +struct is_error_code_enum +{ static const bool value = true; }; + +} +} + + +namespace libtorrent { + + // returns the error category for I2P errors + TORRENT_EXPORT boost::system::error_category& i2p_category(); + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + inline boost::system::error_category& get_i2p_category() + { return i2p_category(); } +#endif + +struct i2p_session_options +{ + int m_inbound_quantity = 3; + int m_outbound_quantity = 3; + int m_inbound_length = 3; + int m_outbound_length = 3; +}; + +struct i2p_stream : proxy_base +{ + explicit i2p_stream(io_context& io_context); + i2p_stream(i2p_stream&&) noexcept = default; + // explicitly disallow assignment, to silence msvc warning + i2p_stream& operator=(i2p_stream const&) = delete; + + enum command_t : std::uint8_t + { + cmd_none, + cmd_create_session, + cmd_connect, + cmd_accept, + cmd_name_lookup, + cmd_incoming + }; + + void set_command(command_t c) { m_command = c; } + + void set_session_options(i2p_session_options const& session_options) + { + m_session_options = session_options; + } + + void set_session_id(char const* id) { m_id = id; } + + void set_local_i2p_endpoint(string_view d) { m_local = d.to_string(); } + std::string const& local_i2p_endpoint() const { return m_local; } + void set_destination(string_view d) { m_dest = d.to_string(); } + std::string const& destination() const { return m_dest; } + + template + void async_connect(endpoint_type const&, Handler h) + { + // since we don't support regular endpoints, just ignore the one + // provided and use m_dest. + + // the connect is split up in the following steps: + // 1. resolve name of proxy server + // 2. connect to SAM bridge + // 4 send command message (CONNECT/ACCEPT) + + m_resolver.async_resolve(m_hostname, to_string(m_port).data(), wrap_allocator( + [this](error_code const& ec, tcp::resolver::results_type ips, Handler hn) { + do_connect(ec, std::move(ips), std::move(hn)); + }, std::move(h))); + } + + std::string name_lookup() const { return m_name_lookup; } + void set_name_lookup(char const* name) { m_name_lookup = name; } + + template + void send_name_lookup(Handler h) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_state = read_name_lookup_response; + char cmd[1024]; + int size = std::snprintf(cmd, sizeof(cmd), "NAMING LOOKUP NAME=%s\n", m_name_lookup.c_str()); + ADD_OUTSTANDING_ASYNC("i2p_stream::start_read_line"); + async_write(m_sock, boost::asio::buffer(cmd, std::size_t(size)), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + start_read_line(ec, std::move(hn)); + }, std::move(h))); + } + +private: + + template + void do_connect(error_code const& e, tcp::resolver::results_type ips, Handler h) + { + TORRENT_ASSERT(m_magic == 0x1337); + if (e || ips.empty()) + { + h(e); + error_code ec; + close(ec); + return; + } + + auto i = ips.begin(); + ADD_OUTSTANDING_ASYNC("i2p_stream::connected"); + m_sock.async_connect(i->endpoint(), wrap_allocator( + [this](error_code const& ec, Handler hn) { + connected(ec, std::move(hn)); + }, std::move(h))); + } + + template + void connected(error_code const& e, Handler h) + { + TORRENT_ASSERT(m_magic == 0x1337); + COMPLETE_ASYNC("i2p_stream::connected"); + if (handle_error(e, h)) return; + + // send hello command + m_state = read_hello_response; + static const char cmd[] = "HELLO VERSION MIN=3.1 MAX=3.1\n"; + + ADD_OUTSTANDING_ASYNC("i2p_stream::start_read_line"); + async_write(m_sock, boost::asio::buffer(cmd, sizeof(cmd) - 1), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + start_read_line(ec, std::move(hn)); + }, std::move(h))); + } + + template + void start_read_line(error_code const& e, Handler h) + { + TORRENT_ASSERT(m_magic == 0x1337); + COMPLETE_ASYNC("i2p_stream::start_read_line"); + if (handle_error(e, h)) return; + + ADD_OUTSTANDING_ASYNC("i2p_stream::read_line"); + m_buffer.resize(1); + async_read(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + read_line(ec, std::move(hn)); + }, std::move(h))); + } + + template + void read_line(error_code const& e, Handler h) + { + TORRENT_ASSERT(m_magic == 0x1337); + COMPLETE_ASYNC("i2p_stream::read_line"); + if (handle_error(e, h)) return; + + auto const read_pos = int(m_buffer.size()); + + // look for \n which means end of the response + if (m_buffer[read_pos - 1] != '\n') + { + ADD_OUTSTANDING_ASYNC("i2p_stream::read_line"); + // read another byte from the socket + m_buffer.resize(read_pos + 1); + async_read(m_sock, boost::asio::buffer(&m_buffer[read_pos], 1), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + read_line(ec, std::move(hn)); + }, std::move(h))); + return; + } + m_buffer[read_pos - 1] = 0; + + if (m_command == cmd_incoming) + { + // this is the line containing the destination + // of the incoming connection in an accept call + m_dest = &m_buffer[0]; + h(e); + std::vector().swap(m_buffer); + return; + } + + error_code invalid_response(i2p_error::parse_failed + , i2p_category()); + + string_view expect1; + string_view expect2; + + switch (m_state) + { + case read_hello_response: + expect1 = "HELLO"_sv; + expect2 = "REPLY"_sv; + break; + case read_connect_response: + case read_accept_response: + expect1 = "STREAM"_sv; + expect2 = "STATUS"_sv; + break; + case read_session_create_response: + expect1 = "SESSION"_sv; + expect2 = "STATUS"_sv; + break; + case read_name_lookup_response: + expect1 = "NAMING"_sv; + expect2 = "REPLY"_sv; + break; + } + + TORRENT_ASSERT(m_buffer[int(m_buffer.size()) - 1] == '\0'); + string_view remaining(m_buffer.data(), m_buffer.size() - 1); + string_view token; + + std::tie(token, remaining) = split_string(remaining, ' '); + if (expect1.empty() || expect1 != token) + { handle_error(invalid_response, h); return; } + + std::tie(token, remaining) = split_string(remaining, ' '); + if (expect2.empty() || expect2 != token) + { handle_error(invalid_response, h); return; } + + int result = 0; + + for(;;) + { + string_view name; + std::tie(name, remaining) = split_string(remaining, '='); + if (name.empty()) break; + string_view value; + if (remaining[0] == '"') + { + std::tie(value, remaining) = split_string(remaining.substr(1), '"'); + if (value.empty()) { handle_error(invalid_response, h); return; } + value.remove_suffix(1); + } + else + { + std::tie(value, remaining) = split_string(remaining, ' '); + } + if (value.empty()) { handle_error(invalid_response, h); return; } + + if ("RESULT"_sv == name) + { + if ("OK"_sv == value) + result = i2p_error::no_error; + else if ("CANT_REACH_PEER"_sv == value) + result = i2p_error::cant_reach_peer; + else if ("I2P_ERROR"_sv == value) + result = i2p_error::i2p_error; + else if ("INVALID_KEY"_sv == value) + result = i2p_error::invalid_key; + else if ("INVALID_ID"_sv == value) + result = i2p_error::invalid_id; + else if ("TIMEOUT"_sv == value) + result = i2p_error::timeout; + else if ("KEY_NOT_FOUND"_sv == value) + result = i2p_error::key_not_found; + else if ("DUPLICATED_ID"_sv == value) + result = i2p_error::duplicated_id; + else + result = i2p_error::num_errors; // unknown error + } + /*else if ("MESSAGE" == name) + { + } + else if ("VERSION"_sv == name) + { + }*/ + else if ("VALUE"_sv == name) + { + m_name_lookup = value.to_string(); + } + else if ("DESTINATION"_sv == name) + { + m_dest = value.to_string(); + } + } + + error_code ec(result, i2p_category()); + if (ec) + { + std::forward(h)(ec); + return; + } + + switch (m_state) + { + case read_hello_response: + switch (m_command) + { + case cmd_create_session: + send_session_create(std::move(h)); + break; + case cmd_accept: + send_accept(std::move(h)); + break; + case cmd_connect: + send_connect(std::move(h)); + break; + case cmd_none: + case cmd_name_lookup: + case cmd_incoming: + std::forward(h)(ec); + std::vector().swap(m_buffer); + } + break; + case read_connect_response: + case read_session_create_response: + case read_name_lookup_response: + std::forward(h)(ec); + std::vector().swap(m_buffer); + break; + case read_accept_response: + // the SAM bridge is waiting for an incoming + // connection. + // wait for one more line containing + // the destination of the remote peer + m_command = cmd_incoming; + m_buffer.resize(1); + ADD_OUTSTANDING_ASYNC("i2p_stream::read_line"); + async_read(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& err, std::size_t, Handler hn) { + read_line(err, std::move(hn)); + }, std::move(h))); + break; + } + } + + template + void send_connect(Handler h) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_state = read_connect_response; + char cmd[1024]; + int size = std::snprintf(cmd, sizeof(cmd), "STREAM CONNECT ID=%s DESTINATION=%s\n" + , m_id, m_dest.c_str()); + ADD_OUTSTANDING_ASYNC("i2p_stream::start_read_line"); + async_write(m_sock, boost::asio::buffer(cmd, std::size_t(size)), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + start_read_line(ec, std::move(hn)); + }, std::move(h))); + } + + template + void send_accept(Handler h) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_state = read_accept_response; + char cmd[400]; + int size = std::snprintf(cmd, sizeof(cmd), "STREAM ACCEPT ID=%s\n", m_id); + ADD_OUTSTANDING_ASYNC("i2p_stream::start_read_line"); + async_write(m_sock, boost::asio::buffer(cmd, std::size_t(size)), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + start_read_line(ec, std::move(hn)); + }, std::move(h))); + } + + template + void send_session_create(Handler h) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_state = read_session_create_response; + char cmd[400]; + int size = std::snprintf(cmd, sizeof(cmd), + "SESSION CREATE STYLE=STREAM ID=%s " + "DESTINATION=TRANSIENT SIGNATURE_TYPE=7 i2cp.leaseSetEncType=4,0 " + "inbound.quantity=%d outbound.quantity=%d inbound.length=%d outbound.length=%d\n", + m_id, m_session_options.m_inbound_quantity, m_session_options.m_outbound_quantity, + m_session_options.m_inbound_length, m_session_options.m_outbound_length); + ADD_OUTSTANDING_ASYNC("i2p_stream::start_read_line"); + async_write(m_sock, boost::asio::buffer(cmd, std::size_t(size)), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + start_read_line(ec, std::move(hn)); + }, std::move(h))); + } + + // send and receive buffer + aux::noexcept_movable> m_buffer; + char const* m_id = nullptr; + std::string m_dest; + std::string m_local; + std::string m_name_lookup; + + i2p_session_options m_session_options; + + enum state_t : std::uint8_t + { + read_hello_response, + read_connect_response, + read_accept_response, + read_session_create_response, + read_name_lookup_response + }; + + command_t m_command; + state_t m_state; +}; + +class i2p_connection +{ +public: + explicit i2p_connection(io_context& ios); + ~i2p_connection(); + // explicitly disallow assignment, to silence msvc warning + i2p_connection& operator=(i2p_connection const&) = delete; + + aux::proxy_settings proxy() const; + + bool is_open() const + { + return m_sam_socket + && m_sam_socket->is_open() + && m_state != sam_connecting; + } + template + void open(std::string const& hostname, int port, + i2p_session_options const& session_options, Handler handler) + { + // we already seem to have a session to this SAM router + if (m_hostname == hostname + && m_port == port + && m_sam_socket + && (is_open() || m_state == sam_connecting)) return; + + m_hostname = hostname; + m_port = port; + + if (m_hostname.empty()) return; + + m_state = sam_connecting; + + char tmp[20]; + aux::random_bytes(tmp); + m_session_id.resize(sizeof(tmp)*2); + aux::to_hex(tmp, &m_session_id[0]); + + m_sam_socket = std::make_shared(m_io_service); + m_sam_socket->set_proxy(m_hostname, m_port); + m_sam_socket->set_command(i2p_stream::cmd_create_session); + m_sam_socket->set_session_id(m_session_id.c_str()); + m_sam_socket->set_session_options(session_options); + + ADD_OUTSTANDING_ASYNC("i2p_stream::on_sam_connect"); + m_sam_socket->async_connect(tcp::endpoint(), wrap_allocator( + [this,s=m_sam_socket](error_code const& ec, Handler hn) { + on_sam_connect(ec, s, std::move(hn)); + }, std::move(handler))); + } + void close(error_code&); + + // TODO: make this a string_view + char const* session_id() const { return m_session_id.c_str(); } + std::string const& local_endpoint() const { return m_i2p_local_endpoint; } + + template + void async_name_lookup(char const* name, Handler handler) + { + if (m_state == sam_idle && m_name_lookup.empty() && is_open()) + do_name_lookup(name, std::move(handler)); + else + m_name_lookup.emplace_back(std::string(name) + , std::move(handler)); + } + +private: + + template + void on_sam_connect(error_code const& ec, std::shared_ptr, Handler h) + { + COMPLETE_ASYNC("i2p_stream::on_sam_connect"); + m_state = sam_idle; + + if (ec) + { + h(ec); + return; + } + + do_name_lookup("ME", wrap_allocator( + [this](error_code const& e, char const* dst, Handler hn) { + set_local_endpoint(e, dst, std::move(hn)); + }, std::move(h))); + } + + using name_lookup_handler = std::function; + + template + void do_name_lookup(std::string const& name, Handler handler) + { + TORRENT_ASSERT(m_state == sam_idle); + m_state = sam_name_lookup; + m_sam_socket->set_name_lookup(name.c_str()); + m_sam_socket->send_name_lookup(wrap_allocator( + [this,s=m_sam_socket](error_code const& ec, Handler hn) { + on_name_lookup(ec, s, std::move(hn)); + }, std::move(handler))); + } + + template + void on_name_lookup(error_code const& ec, std::shared_ptr, Handler handler) + { + m_state = sam_idle; + + std::string name = m_sam_socket->name_lookup(); + if (!m_name_lookup.empty()) + { + std::pair& nl = m_name_lookup.front(); + do_name_lookup(nl.first, std::move(nl.second)); + m_name_lookup.pop_front(); + } + + if (ec) + { + handler(ec, nullptr); + return; + } + + handler(ec, name.c_str()); + } + + + template + void set_local_endpoint(error_code const& ec, char const* dest, Handler h) + { + if (!ec && dest != nullptr) + m_i2p_local_endpoint = dest; + else + m_i2p_local_endpoint.clear(); + + h(ec); + } + + // to talk to i2p SAM bridge + std::shared_ptr m_sam_socket; + std::string m_hostname; + int m_port; + + // our i2p endpoint key + std::string m_i2p_local_endpoint; + std::string m_session_id; + + std::list> m_name_lookup; + + enum state_t + { + sam_connecting, + sam_name_lookup, + sam_idle + }; + + state_t m_state; + + io_context& m_io_service; +}; + +} + +#endif // TORRENT_USE_I2P + +#endif diff --git a/docs/include/libtorrent/identify_client.hpp b/docs/include/libtorrent/identify_client.hpp new file mode 100644 index 0000000..64bf9b2 --- /dev/null +++ b/docs/include/libtorrent/identify_client.hpp @@ -0,0 +1,86 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2003, 2006, 2013-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IDENTIFY_CLIENT_HPP_INCLUDED +#define TORRENT_IDENTIFY_CLIENT_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/fingerprint.hpp" + +// TODO: hide this declaration when deprecated functions are disabled, and +// remove its internal use +namespace libtorrent { + +namespace aux { + + TORRENT_EXTRA_EXPORT + std::string identify_client_impl(const peer_id& p); + +} + + // these functions don't really need to be public. This mechanism of + // advertising client software and version is also out-dated. + + // This function can can be used to extract a string describing a client + // version from its peer-id. It will recognize most clients that have this + // kind of identification in the peer-id. + TORRENT_DEPRECATED_EXPORT + std::string identify_client(const peer_id& p); + +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // Returns an optional fingerprint if any can be identified from the peer + // id. This can be used to automate the identification of clients. It will + // not be able to identify peers with non- standard encodings. Only Azureus + // style, Shadow's style and Mainline style. + TORRENT_DEPRECATED_EXPORT + boost::optional + client_fingerprint(peer_id const& p); + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif // TORRENT_ABI_VERSION + +} + +#endif // TORRENT_IDENTIFY_CLIENT_HPP_INCLUDED diff --git a/docs/include/libtorrent/index_range.hpp b/docs/include/libtorrent/index_range.hpp new file mode 100644 index 0000000..ad5e292 --- /dev/null +++ b/docs/include/libtorrent/index_range.hpp @@ -0,0 +1,72 @@ +/* + +Copyright (c) 2018-2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_INDEX_RANGE_HPP +#define TORRENT_INDEX_RANGE_HPP + +namespace libtorrent { + +template +struct index_iter +{ + explicit index_iter(Index i) : m_idx(i) {} + index_iter operator++() + { + ++m_idx; + return *this; + } + index_iter operator--() + { + --m_idx; + return *this; + } + Index operator*() const { return m_idx; } + friend inline bool operator==(index_iter lhs, index_iter rhs) + { return lhs.m_idx == rhs.m_idx; } + friend inline bool operator!=(index_iter lhs, index_iter rhs) + { return lhs.m_idx != rhs.m_idx; } +private: + Index m_idx; +}; + +template +struct index_range +{ + Index _begin; + Index _end; + index_iter begin() const { return index_iter{_begin}; } + index_iter end() const { return index_iter{_end}; } +}; + +} + +#endif diff --git a/docs/include/libtorrent/info_hash.hpp b/docs/include/libtorrent/info_hash.hpp new file mode 100644 index 0000000..a8c580f --- /dev/null +++ b/docs/include/libtorrent/info_hash.hpp @@ -0,0 +1,167 @@ +/* + +Copyright (c) 2018, BitTorrent Inc. +Copyright (c) 2018, Steven Siloti +Copyright (c) 2019-2022, Arvid Norberg +Copyright (c) 2020, Mike Tzou +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. +* Neither the name of the author nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_INFO_HASH_HPP_INCLUDED +#define TORRENT_INFO_HASH_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/sha1_hash.hpp" + +#if TORRENT_USE_IOSTREAM +#include +#endif // TORRENT_USE_IOSTREAM + +namespace libtorrent +{ + // BitTorrent version enumerator + enum class protocol_version : std::uint8_t + { + // The original BitTorrent version, using SHA-1 hashes + V1, + // Version 2 of the BitTorrent protocol, using SHA-256 hashes + V2, + NUM + }; + + // internal + constexpr std::size_t num_protocols = int(protocol_version::NUM); + +namespace { + std::initializer_list const all_versions{ + protocol_version::V1, + protocol_version::V2 + }; +} + + // class holding the info-hash of a torrent. It can hold a v1 info-hash + // (SHA-1) or a v2 info-hash (SHA-256) or both. + // + // .. note:: + // + // If ``has_v2()`` is false then the v1 hash might actually be a truncated + // v2 hash + struct TORRENT_EXPORT info_hash_t + { + // The default constructor creates an object that has neither a v1 or v2 + // hash. + // + // For backwards compatibility, make it possible to construct directly + // from a v1 hash. This constructor allows *implicit* conversion from a + // v1 hash, but the implicitness is deprecated. + info_hash_t() noexcept = default; + explicit info_hash_t(sha1_hash h1) noexcept : v1(h1) {} // NOLINT + explicit info_hash_t(sha256_hash h2) noexcept : v2(h2) {} + info_hash_t(sha1_hash h1, sha256_hash h2) noexcept + : v1(h1), v2(h2) {} + + // hidden + info_hash_t(info_hash_t const&) noexcept = default; + info_hash_t& operator=(info_hash_t const&) & noexcept = default; + + // returns true if the corresponding info hash is present in this + // object. + bool has_v1() const { return !v1.is_all_zeros(); } + bool has_v2() const { return !v2.is_all_zeros(); } + bool has(protocol_version v) const + { + TORRENT_ASSERT(v != protocol_version::NUM); + return v == protocol_version::V1 ? has_v1() : has_v2(); + } + + // returns the has for the specified protocol version + sha1_hash get(protocol_version v) const + { + TORRENT_ASSERT(v != protocol_version::NUM); + return v == protocol_version::V1 ? v1 : sha1_hash(v2.data()); + } + + // returns the v2 (truncated) info-hash, if there is one, otherwise + // returns the v1 info-hash + sha1_hash get_best() const + { + return has_v2() ? get(protocol_version::V2) : v1; + } + + friend bool operator!=(info_hash_t const& lhs, info_hash_t const& rhs) + { + return std::tie(lhs.v1, lhs.v2) != std::tie(rhs.v1, rhs.v2); + } + + friend bool operator==(info_hash_t const& lhs, info_hash_t const& rhs) noexcept + { + return std::tie(lhs.v1, lhs.v2) == std::tie(rhs.v1, rhs.v2); + } + + // calls the function object ``f`` for each hash that is available. + // starting with v1. The signature of ``F`` is:: + // + // void(sha1_hash const&, protocol_version); + template void for_each(F f) const + { + if (has_v1()) f(v1, protocol_version::V1); + if (has_v2()) f(sha1_hash(v2.data()), protocol_version::V2); + } + + bool operator<(info_hash_t const& o) const + { + return std::tie(v1, v2) < std::tie(o.v1, o.v2); + } + +#if TORRENT_USE_IOSTREAM + friend std::ostream& operator<<(std::ostream& os, info_hash_t const& ih) + { + return os << '[' << ih.v1 << ',' << ih.v2 << ']'; + } +#endif // TORRENT_USE_IOSTREAM + + sha1_hash v1; + sha256_hash v2; + }; + +} + +namespace std { + template <> + struct hash + { + std::size_t operator()(libtorrent::info_hash_t const& k) const + { + return std::hash{}(k.v1) + ^ std::hash{}(k.v2) ; + } + }; +} + +#endif diff --git a/docs/include/libtorrent/io.hpp b/docs/include/libtorrent/io.hpp new file mode 100644 index 0000000..7802e16 --- /dev/null +++ b/docs/include/libtorrent/io.hpp @@ -0,0 +1,189 @@ +/* + +Copyright (c) 2004, 2007, 2009, 2011, 2016-2017, 2019, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IO_HPP_INCLUDED +#define TORRENT_IO_HPP_INCLUDED + +#include +#include +#include // for copy +#include // for memcpy +#include +#include + +#include "assert.hpp" +#include "libtorrent/aux_/io.hpp" + +namespace libtorrent { +namespace aux { + + // reads an integer from a byte stream + // in big endian byte order and converts + // it to native endianness + template + inline T read_impl(InIt& start, type) + { + T ret = 0; + for (int i = 0; i < int(sizeof(T)); ++i) + { + ret <<= 8; + ret |= static_cast(*start); + ++start; + } + return ret; + } + + template + std::uint8_t read_impl(InIt& start, type) + { + return static_cast(*start++); + } + + template + std::int8_t read_impl(InIt& start, type) + { + return static_cast(*start++); + } + + template + typename std::enable_if<(std::is_integral::value + && !std::is_same::value) + || std::is_enum::value, void>::type + write_impl(In data, OutIt& start) + { + // Note: the test for [OutItT==void] below is necessary because + // in C++11 std::back_insert_iterator::value_type is void. + // This could change in C++17 or above + using OutItT = typename std::iterator_traits::value_type; + using Byte = typename std::conditional< + std::is_same::value, char, OutItT>::type; + static_assert(sizeof(Byte) == 1, "wrong iterator or pointer type"); + + T val = static_cast(data); + TORRENT_ASSERT(data == static_cast(val)); + for (int i = int(sizeof(T)) - 1; i >= 0; --i) + { + *start = static_cast((val >> (i * 8)) & 0xff); + ++start; + } + } + + template + typename std::enable_if::value, void>::type + write_impl(Val val, OutIt& start) + { write_impl(val ? 1 : 0, start); } + + // -- adaptors + + template + std::int64_t read_int64(InIt& start) + { return read_impl(start, type()); } + + template + std::uint64_t read_uint64(InIt& start) + { return read_impl(start, type()); } + + template + std::uint32_t read_uint32(InIt& start) + { return read_impl(start, type()); } + + template + std::int32_t read_int32(InIt& start) + { return read_impl(start, type()); } + + template + std::int16_t read_int16(InIt& start) + { return read_impl(start, type()); } + + template + std::uint16_t read_uint16(InIt& start) + { return read_impl(start, type()); } + + template + std::int8_t read_int8(InIt& start) + { return read_impl(start, type()); } + + template + std::uint8_t read_uint8(InIt& start) + { return read_impl(start, type()); } + + + template + void write_uint64(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_int64(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_uint32(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_int32(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_uint16(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_int16(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_uint8(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_int8(T val, OutIt& start) + { write_impl(val, start); } + + inline int write_string(std::string const& str, char*& start) + { + std::memcpy(reinterpret_cast(start), str.c_str(), str.size()); + start += str.size(); + return int(str.size()); + } + + template + int write_string(std::string const& val, OutIt& out) + { + for (auto const c : val) *out++ = c; + return int(val.length()); + } +} // namespace aux +} + +#endif // TORRENT_IO_HPP_INCLUDED diff --git a/docs/include/libtorrent/io_context.hpp b/docs/include/libtorrent/io_context.hpp new file mode 100644 index 0000000..efb2710 --- /dev/null +++ b/docs/include/libtorrent/io_context.hpp @@ -0,0 +1,63 @@ +/* + +Copyright (c) 2006-2007, 2009, 2015, 2017, 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IO_CONTEXT_HPP_INCLUDED +#define TORRENT_IO_CONTEXT_HPP_INCLUDED + +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#else +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#include +#include +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // SIMULATOR + +namespace libtorrent { + +#if defined TORRENT_BUILD_SIMULATOR + using sim::asio::io_context; +#else + using boost::asio::io_context; +#endif + using boost::asio::executor_work_guard; + using boost::asio::make_work_guard; + + using boost::asio::post; + using boost::asio::dispatch; + using boost::asio::defer; +} + +#endif diff --git a/docs/include/libtorrent/io_service.hpp b/docs/include/libtorrent/io_service.hpp new file mode 100644 index 0000000..86b0b99 --- /dev/null +++ b/docs/include/libtorrent/io_service.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2006-2007, 2009, 2015, 2017, 2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IO_SERVICE_HPP_INCLUDED +#define TORRENT_IO_SERVICE_HPP_INCLUDED + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#error warning "this header is deprecated, use io_context.hpp instead" +namespace libtorrent { + + using io_service = boost::asio::io_context; +} + +#endif diff --git a/docs/include/libtorrent/ip_filter.hpp b/docs/include/libtorrent/ip_filter.hpp new file mode 100644 index 0000000..fdaccc0 --- /dev/null +++ b/docs/include/libtorrent/ip_filter.hpp @@ -0,0 +1,241 @@ +/* + +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2005-2007, 2009-2010, 2013, 2015-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016-2018, 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IP_FILTER_HPP +#define TORRENT_IP_FILTER_HPP + +#include "libtorrent/config.hpp" + +#include +#include +#include +#include +#include + +#include "libtorrent/address.hpp" + +namespace libtorrent { + +template +struct ip_range +{ + Addr first; + Addr last; + std::uint32_t flags; + friend bool operator==(ip_range const& lhs, ip_range const& rhs) + { + return lhs.first == rhs.first + && lhs.last == rhs.last + && lhs.flags == rhs.flags; + } +}; + +namespace aux { + + template + TORRENT_EXTRA_EXPORT Addr zero(); + template + TORRENT_EXTRA_EXPORT Addr plus_one(Addr const& a); + template + TORRENT_EXTRA_EXPORT Addr minus_one(Addr const& a); + template + TORRENT_EXTRA_EXPORT Addr max_addr(); + + extern template address_v4::bytes_type minus_one(address_v4::bytes_type const&); + extern template address_v6::bytes_type minus_one(address_v6::bytes_type const&); + extern template address_v4::bytes_type plus_one(address_v4::bytes_type const&); + extern template address_v6::bytes_type plus_one(address_v6::bytes_type const&); + extern template address_v4::bytes_type zero(); + extern template address_v6::bytes_type zero(); + extern template address_v4::bytes_type max_addr(); + extern template address_v6::bytes_type max_addr(); + + inline std::uint16_t plus_one(std::uint16_t val) { return val + 1; } + inline std::uint16_t minus_one(std::uint16_t val) { return val - 1; } + template<> + inline std::uint16_t zero() { return 0; } + template<> + inline std::uint16_t max_addr() + { return (std::numeric_limits::max)(); } + + // this is the generic implementation of + // a filter for a specific address type. + // it works with IPv4 and IPv6 + template + class filter_impl + { + public: + + filter_impl(); + bool empty() const; + void add_rule(Addr first, Addr last, std::uint32_t flags); + std::uint32_t access(Addr const& addr) const; + template + std::vector> export_filter() const; + + private: + + struct range + { + range(Addr addr, std::uint32_t a = 0) : start(addr), access(a) {} // NOLINT + bool operator<(range const& r) const { return start < r.start; } + bool operator<(Addr const& a) const { return start < a; } + Addr start; + // the end of the range is implicit + // and given by the next entry in the set + std::uint32_t access; + friend bool operator==(range const& lhs, range const& rhs) + { return lhs.start == rhs.start && lhs.access == rhs.access; } + }; + + std::set m_access_list; + }; + + extern template class filter_impl; + extern template class filter_impl; + extern template class filter_impl; + + extern template std::vector> filter_impl::export_filter() const; + extern template std::vector> filter_impl::export_filter() const; + extern template std::vector> filter_impl::export_filter() const; +} + +// The ``ip_filter`` class is a set of rules that uniquely categorizes all +// ip addresses as allowed or disallowed. The default constructor creates +// a single rule that allows all addresses (0.0.0.0 - 255.255.255.255 for +// the IPv4 range, and the equivalent range covering all addresses for the +// IPv6 range). +// +// A default constructed ip_filter does not filter any address. +struct TORRENT_EXPORT ip_filter +{ + ip_filter(); + ip_filter(ip_filter const&); + ip_filter(ip_filter&&); + ip_filter& operator=(ip_filter const&); + ip_filter& operator=(ip_filter&&); + ~ip_filter(); + + // the flags defined for an IP range + enum access_flags + { + // indicates that IPs in this range should not be connected + // to nor accepted as incoming connections + blocked = 1 + }; + + // returns true if the filter does not contain any rules + bool empty() const; + + // Adds a rule to the filter. ``first`` and ``last`` defines a range of + // ip addresses that will be marked with the given flags. The ``flags`` + // can currently be 0, which means allowed, or ``ip_filter::blocked``, which + // means disallowed. + // + // precondition: + // ``first.is_v4() == last.is_v4() && first.is_v6() == last.is_v6()`` + // + // postcondition: + // ``access(x) == flags`` for every ``x`` in the range [``first``, ``last``] + // + // This means that in a case of overlapping ranges, the last one applied takes + // precedence. + void add_rule(address const& first, address const& last, std::uint32_t flags); + + // Returns the access permissions for the given address (``addr``). The permission + // can currently be 0 or ``ip_filter::blocked``. The complexity of this operation + // is O(``log`` n), where n is the minimum number of non-overlapping ranges to describe + // the current filter. + std::uint32_t access(address const& addr) const; + + using filter_tuple_t = std::tuple> + , std::vector>>; + + // This function will return the current state of the filter in the minimum number of + // ranges possible. They are sorted from ranges in low addresses to high addresses. Each + // entry in the returned vector is a range with the access control specified in its + // ``flags`` field. + // + // The return value is a tuple containing two range-lists. One for IPv4 addresses + // and one for IPv6 addresses. + filter_tuple_t export_filter() const; + +private: + + aux::filter_impl m_filter4; + aux::filter_impl m_filter6; +}; + +// the port filter maps non-overlapping port ranges to flags. This +// is primarily used to indicate whether a range of ports should +// be connected to or not. The default is to have the full port +// range (0-65535) set to flag 0. +class TORRENT_EXPORT port_filter +{ +public: + + port_filter(); + port_filter(port_filter const&); + port_filter(port_filter&&); + port_filter& operator=(port_filter const&); + port_filter& operator=(port_filter&&); + ~port_filter(); + + // the defined flags for a port range + enum access_flags + { + // this flag indicates that destination ports in the + // range should not be connected to + blocked = 1 + }; + + // set the flags for the specified port range (``first``, ``last``) to + // ``flags`` overwriting any existing rule for those ports. The range + // is inclusive, i.e. the port ``last`` also has the flag set on it. + void add_rule(std::uint16_t first, std::uint16_t last, std::uint32_t flags); + + // test the specified port (``port``) for whether it is blocked + // or not. The returned value is the flags set for this port. + // see access_flags. + std::uint32_t access(std::uint16_t port) const; + +private: + + aux::filter_impl m_filter; + +}; + +} + +#endif diff --git a/docs/include/libtorrent/ip_voter.hpp b/docs/include/libtorrent/ip_voter.hpp new file mode 100644 index 0000000..a10d93f --- /dev/null +++ b/docs/include/libtorrent/ip_voter.hpp @@ -0,0 +1,136 @@ +/* + +Copyright (c) 2013-2015, 2017, 2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2016, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IP_VOTER_HPP_INCLUDED +#define TORRENT_IP_VOTER_HPP_INCLUDED + +#include +#include "libtorrent/address.hpp" +#include "libtorrent/bloom_filter.hpp" +#include "libtorrent/time.hpp" // for time_point +#include "libtorrent/aux_/session_interface.hpp" // for ip_source_t + +namespace libtorrent { + + // this is an object that keeps the state for a single external IP + // based on peoples votes + struct TORRENT_EXTRA_EXPORT ip_voter + { + ip_voter(); + + // returns true if a different IP is the top vote now + // i.e. we changed our idea of what our external IP is + bool cast_vote(address const& ip, aux::ip_source_t source_type, address const& source); + + address external_address() const { return m_external_address; } + + private: + + bool maybe_rotate(); + + struct external_ip_t + { + bool add_vote(sha1_hash const& k, aux::ip_source_t type); + + // we want to sort descending + bool operator<(external_ip_t const& rhs) const + { + if (num_votes > rhs.num_votes) return true; + if (num_votes < rhs.num_votes) return false; + return static_cast(sources) > static_cast(rhs.sources); + } + + // this is a bloom filter of the IPs that have + // reported this address + bloom_filter<16> voters; + // this is the actual external address + address addr; + // a bitmask of sources the reporters have come from + aux::ip_source_t sources{}; + // the total number of votes for this IP + std::uint16_t num_votes = 0; + }; + + // this is a bloom filter of all the IPs that have + // been the first to report an external address. Each + // IP only gets to add a new item once. + bloom_filter<32> m_external_address_voters; + + std::vector m_external_addresses; + address m_external_address; + + // the total number of unique IPs that have voted + int m_total_votes; + + // this is true from the first time we rotate. Before + // we rotate for the first time, we keep updating the + // external address as we go, since we don't have any + // stable setting to fall back on. Once this is true, + // we stop updating it on the fly, and just use the + // address from when we rotated. + bool m_valid_external; + + // the last time we rotated this ip_voter. i.e. threw + // away all the votes and started from scratch, in case + // our IP has changed + time_point m_last_rotate; + }; + + // stores one address for each combination of local/global and ipv4/ipv6 + // use of this class should be avoided, get the IP from the appropriate + // listen interface wherever possible + struct TORRENT_EXTRA_EXPORT external_ip + { + external_ip() + : m_addresses{{address_v4(), address_v6()}, {address_v4(), address_v6()}} + {} + + external_ip(address const& local4, address const& global4 + , address const& local6, address const& global6); + + // the external IP as it would be observed from `ip` + address external_address(address const& ip) const; + + private: + + // support one local and one global address per address family + // [0][n] = global [1][n] = local + // [n][0] = IPv4 [n][1] = IPv6 + // TODO: 1 have one instance per possible subnet, 192.168.x.x, 10.x.x.x, etc. + address m_addresses[2][2]; + }; + +} + +#endif diff --git a/docs/include/libtorrent/libtorrent.hpp b/docs/include/libtorrent/libtorrent.hpp new file mode 100644 index 0000000..756ffb2 --- /dev/null +++ b/docs/include/libtorrent/libtorrent.hpp @@ -0,0 +1,168 @@ + +// This header is generated by tools/gen_convenience_header.py + +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/bloom_filter.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/choker.hpp" +#include "libtorrent/client_data.hpp" +#include "libtorrent/close_reason.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/copy_ptr.hpp" +#include "libtorrent/crc32c.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/disabled_disk_io.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/disk_observer.hpp" +#include "libtorrent/download_priority.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/error.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/extensions/smart_ban.hpp" +#include "libtorrent/extensions/ut_metadata.hpp" +#include "libtorrent/extensions/ut_pex.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/fingerprint.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/gzip.hpp" +#include "libtorrent/hash_picker.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/hex.hpp" +#include "libtorrent/http_connection.hpp" +#include "libtorrent/http_parser.hpp" +#include "libtorrent/http_seed_connection.hpp" +#include "libtorrent/http_stream.hpp" +#include "libtorrent/http_tracker_connection.hpp" +#include "libtorrent/i2p_stream.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/index_range.hpp" +#include "libtorrent/info_hash.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/ip_voter.hpp" +#include "libtorrent/kademlia/announce_flags.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" +#include "libtorrent/kademlia/dht_settings.hpp" +#include "libtorrent/kademlia/dht_state.hpp" +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/kademlia/dht_tracker.hpp" +#include "libtorrent/kademlia/direct_request.hpp" +#include "libtorrent/kademlia/dos_blocker.hpp" +#include "libtorrent/kademlia/ed25519.hpp" +#include "libtorrent/kademlia/find_data.hpp" +#include "libtorrent/kademlia/get_item.hpp" +#include "libtorrent/kademlia/get_peers.hpp" +#include "libtorrent/kademlia/io.hpp" +#include "libtorrent/kademlia/item.hpp" +#include "libtorrent/kademlia/msg.hpp" +#include "libtorrent/kademlia/node.hpp" +#include "libtorrent/kademlia/node_entry.hpp" +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/observer.hpp" +#include "libtorrent/kademlia/put_data.hpp" +#include "libtorrent/kademlia/refresh.hpp" +#include "libtorrent/kademlia/routing_table.hpp" +#include "libtorrent/kademlia/rpc_manager.hpp" +#include "libtorrent/kademlia/sample_infohashes.hpp" +#include "libtorrent/kademlia/traversal_algorithm.hpp" +#include "libtorrent/kademlia/types.hpp" +#include "libtorrent/link.hpp" +#include "libtorrent/lsd.hpp" +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/mmap_disk_io.hpp" +#include "libtorrent/mmap_storage.hpp" +#include "libtorrent/natpmp.hpp" +#include "libtorrent/netlink.hpp" +#include "libtorrent/operations.hpp" +#include "libtorrent/optional.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/part_file.hpp" +#include "libtorrent/pe_crypto.hpp" +#include "libtorrent/peer.hpp" +#include "libtorrent/peer_class.hpp" +#include "libtorrent/peer_class_set.hpp" +#include "libtorrent/peer_class_type_filter.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/peer_connection_handle.hpp" +#include "libtorrent/peer_connection_interface.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/peer_list.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/pex_flags.hpp" +#include "libtorrent/piece_block.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/platform_util.hpp" +#include "libtorrent/portmap.hpp" +#include "libtorrent/posix_disk_io.hpp" +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/puff.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/read_resume_data.hpp" +#include "libtorrent/request_blocks.hpp" +#include "libtorrent/resolve_links.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/session_handle.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/session_stats.hpp" +#include "libtorrent/session_status.hpp" +#include "libtorrent/session_types.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/sha1.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/sha256.hpp" +#include "libtorrent/sliding_average.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/socket_type.hpp" +#include "libtorrent/socks5_stream.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/ssl.hpp" +#include "libtorrent/ssl_stream.hpp" +#include "libtorrent/stack_allocator.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/tailqueue.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/torrent_flags.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/torrent_peer_allocator.hpp" +#include "libtorrent/torrent_status.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/udp_tracker_connection.hpp" +#include "libtorrent/union_endpoint.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/upnp.hpp" +#include "libtorrent/utf8.hpp" +#include "libtorrent/vector_utils.hpp" +#include "libtorrent/version.hpp" +#include "libtorrent/web_connection_base.hpp" +#include "libtorrent/web_peer_connection.hpp" +#include "libtorrent/write_resume_data.hpp" +#include "libtorrent/xml_parse.hpp" diff --git a/docs/include/libtorrent/link.hpp b/docs/include/libtorrent/link.hpp new file mode 100644 index 0000000..14d8dce --- /dev/null +++ b/docs/include/libtorrent/link.hpp @@ -0,0 +1,83 @@ +/* + +Copyright (c) 2010, 2013-2019, Arvid Norberg +Copyright (c) 2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_LINK_HPP_INCLUDED +#define TORRENT_LINK_HPP_INCLUDED + +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/units.hpp" + +namespace libtorrent { + + using torrent_list_index_t = aux::strong_typedef; + + struct link + { + link() : index(-1) {} + // this is either -1 (not in the list) + // or the index of where in the list this + // element is found + int index; + + bool in_list() const { return index >= 0; } + + void clear() { index = -1; } + + template + void unlink(aux::vector& list + , torrent_list_index_t const link_index) + { + if (index == -1) return; + TORRENT_ASSERT(index >= 0 && index < int(list.size())); + int const last = int(list.size()) - 1; + if (index < last) + { + list[last]->m_links[link_index].index = index; + list[index] = list[last]; + } + list.resize(last); + index = -1; + } + + template + void insert(aux::vector& list, T* self) + { + if (index >= 0) return; + TORRENT_ASSERT(index == -1); + list.push_back(self); + index = int(list.size()) - 1; + } + }; +} + +#endif diff --git a/docs/include/libtorrent/load_torrent.hpp b/docs/include/libtorrent/load_torrent.hpp new file mode 100644 index 0000000..53586af --- /dev/null +++ b/docs/include/libtorrent/load_torrent.hpp @@ -0,0 +1,69 @@ +/* + +Copyright (c) 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_LOAD_TORRENT_HPP_INCLUDED +#define TORRENT_LOAD_TORRENT_HPP_INCLUDED + +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/torrent_info.hpp" // for load_torrent_limits +#include "libtorrent/aux_/export.hpp" + +namespace libtorrent { + + // These functions load the content of a .torrent file into an + // add_torrent_params object. + // The immutable part of a torrent file (the info-dictionary) is stored in + // the ``ti`` field in the add_torrent_params object (as a torrent_info + // object). + // The returned object is suitable to be: + // + // * added to a session via add_torrent() or async_add_torrent() + // * saved as a .torrent_file via write_torrent_file() + // * turned into a magnet link via make_magnet_uri() + TORRENT_EXPORT add_torrent_params load_torrent_file( + std::string const& filename, load_torrent_limits const& cfg); + TORRENT_EXPORT add_torrent_params load_torrent_file( + std::string const& filename); + TORRENT_EXPORT add_torrent_params load_torrent_buffer( + span buffer, load_torrent_limits const& cfg); + TORRENT_EXPORT add_torrent_params load_torrent_buffer( + span buffer); + TORRENT_EXPORT add_torrent_params load_torrent_parsed( + bdecode_node const& torrent_file, load_torrent_limits const& cfg); + TORRENT_EXPORT add_torrent_params load_torrent_parsed( + bdecode_node const& torrent_file); + +} + +#endif diff --git a/docs/include/libtorrent/lsd.hpp b/docs/include/libtorrent/lsd.hpp new file mode 100644 index 0000000..f0d07f5 --- /dev/null +++ b/docs/include/libtorrent/lsd.hpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2007, 2009, 2012, 2014-2015, 2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_LSD_HPP +#define TORRENT_LSD_HPP + +#include "libtorrent/socket.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/aux_/lsd.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/address.hpp" + +namespace libtorrent { + +struct lsd : std::enable_shared_from_this +{ + lsd(io_context& ios, aux::lsd_callback& cb + , address listen_address, address netmask); + ~lsd(); + + void start(error_code& ec); + + void announce(sha1_hash const& ih, int listen_port); + void close(); + +private: + + std::shared_ptr self() { return shared_from_this(); } + + void announce_impl(sha1_hash const& ih, int listen_port, int retry_count); + void resend_announce(error_code const& e, sha1_hash const& info_hash + , int listen_port, int retry_count); + void on_announce(error_code const& ec, std::size_t len); + + aux::lsd_callback& m_callback; + + address m_listen_address; + address m_netmask; + + udp::socket m_socket; + std::array m_buffer; + udp::endpoint m_remote; + +#ifndef TORRENT_DISABLE_LOGGING + bool should_log() const; + void debug_log(char const* fmt, ...) const TORRENT_FORMAT(2, 3); +#endif + + // used to resend udp packets in case + // they time out + deadline_timer m_broadcast_timer; + + // this is a random (presumably unique) + // ID for this LSD node. It is used to + // ignore our own broadcast messages. + // There's no point in adding ourselves + // as a peer + int m_cookie; + + bool m_disabled = false; +}; + +} + +#endif diff --git a/docs/include/libtorrent/magnet_uri.hpp b/docs/include/libtorrent/magnet_uri.hpp new file mode 100644 index 0000000..a7b66c8 --- /dev/null +++ b/docs/include/libtorrent/magnet_uri.hpp @@ -0,0 +1,111 @@ +/* + +Copyright (c) 2007-2009, 2012-2013, 2016-2019, 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_MAGNET_URI_HPP_INCLUDED +#define TORRENT_MAGNET_URI_HPP_INCLUDED + +#include +#include "libtorrent/config.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/string_view.hpp" + +namespace libtorrent { + + struct torrent_handle; + struct session; + + // Generates a magnet URI from the specified torrent. + // + // Several fields from the add_torrent_params objects are recorded in the + // magnet link. In order to not include them, they have to be cleared before + // calling make_magnet_uri(). These fields are used: + // + // ``ti``, ``info_hashes``, ``url_seeds``, ``dht_nodes``, + // ``file_priorities``, ``trackers``, ``name``, ``peers``. + // + // Depending on what the use case for the resulting magnet link is, clearing + // ``peers`` and ``dht_nodes`` is probably a good idea if the add_torrent_params + // came from a running torrent. Those lists may be long and be ephemeral. + // + // If none of the ``info_hashes`` or ``ti`` fields are set, there is not + // info-hash available, and a magnet link cannot be created. In this case + // make_magnet_uri() returns an empty string. + // + // The recommended way to generate a magnet link from a torrent_handle is to + // call save_resume_data(), which will post a save_resume_data_alert + // containing an add_torrent_params object. This can then be passed to + // make_magnet_uri(). + // + // The overload that takes a torrent_handle will make blocking calls to + // query information about the torrent. If the torrent handle is invalid, + // an empty string is returned. + // + // For more information about magnet links, see magnet-links_. + TORRENT_EXPORT std::string make_magnet_uri(add_torrent_params const& atp); + TORRENT_EXPORT std::string make_magnet_uri(torrent_handle const& handle); + TORRENT_EXPORT std::string make_magnet_uri(torrent_info const& info); + +#if TORRENT_ABI_VERSION == 1 +#ifndef BOOST_NO_EXCEPTIONS + // deprecated in 0.14 + TORRENT_DEPRECATED_EXPORT + torrent_handle add_magnet_uri(session& ses, std::string const& uri + , std::string const& save_path + , storage_mode_t storage_mode = storage_mode_sparse + , bool paused = false + , void* userdata = nullptr); + + // deprecated in 0.16. Instead, pass in the magnet link as add_torrent_params::url + TORRENT_DEPRECATED_EXPORT + torrent_handle add_magnet_uri(session& ses, std::string const& uri + , add_torrent_params const& p); +#endif + + // deprecated in 0.16. Instead, pass in the magnet link as add_torrent_params::url + TORRENT_DEPRECATED_EXPORT + torrent_handle add_magnet_uri(session& ses, std::string const& uri + , add_torrent_params const& p, error_code& ec); +#endif // TORRENT_ABI_VERSION + + + // This function parses out information from the magnet link and populates the + // add_torrent_params object. The overload that does not take an + // ``error_code`` reference will throw a system_error on error + // The overload taking an ``add_torrent_params`` reference will fill in the + // fields specified in the magnet URI. + TORRENT_EXPORT add_torrent_params parse_magnet_uri(string_view uri, error_code& ec); + TORRENT_EXPORT add_torrent_params parse_magnet_uri(string_view uri); + TORRENT_EXPORT void parse_magnet_uri(string_view uri, add_torrent_params& p, error_code& ec); +} + +#endif diff --git a/docs/include/libtorrent/mmap_disk_io.hpp b/docs/include/libtorrent/mmap_disk_io.hpp new file mode 100644 index 0000000..b58ba53 --- /dev/null +++ b/docs/include/libtorrent/mmap_disk_io.hpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2007-2018, Steven Siloti +Copyright (c) 2007, 2013-2016, 2019-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_IO_THREAD +#define TORRENT_DISK_IO_THREAD + +#include "libtorrent/config.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/io_context.hpp" + +namespace libtorrent { + +#if TORRENT_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE + + struct counters; + struct settings_interface; + + // constructs a memory mapped file disk I/O object. + TORRENT_EXPORT std::unique_ptr mmap_disk_io_constructor( + io_context& ios, settings_interface const&, counters& cnt); + +#endif // HAVE_MMAP || HAVE_MAP_VIEW_OF_FILE + +} + +#endif // TORRENT_DISK_IO_THREAD diff --git a/docs/include/libtorrent/mmap_storage.hpp b/docs/include/libtorrent/mmap_storage.hpp new file mode 100644 index 0000000..e9b6127 --- /dev/null +++ b/docs/include/libtorrent/mmap_storage.hpp @@ -0,0 +1,232 @@ +/* + +Copyright (c) 2003, 2009, 2011, 2013-2022, Arvid Norberg +Copyright (c) 2003, Daniel Wallin +Copyright (c) 2016, 2018, Alden Torres +Copyright (c) 2018-2019, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STORAGE_HPP_INCLUDE +#define TORRENT_STORAGE_HPP_INCLUDE + +#include "libtorrent/config.hpp" + +#if TORRENT_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE + +#include +#include +#include + +#include "libtorrent/fwd.hpp" +#include "libtorrent/aux_/disk_job_fence.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/part_file.hpp" +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/open_mode.hpp" // for aux::open_mode_t +#include "libtorrent/disk_interface.hpp" // for disk_job_flags_t +#include "libtorrent/aux_/mmap.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + +namespace aux { + struct session_settings; + struct file_view_pool; +} + + struct TORRENT_EXTRA_EXPORT mmap_storage + : std::enable_shared_from_this + , aux::disk_job_fence + { + // constructs the mmap_storage based on the give file_storage (fs). + // ``mapped`` is an optional argument (it may be nullptr). If non-nullptr it + // represents the file mapping that have been made to the torrent before + // adding it. That's where files are supposed to be saved and looked for + // on disk. ``save_path`` is the root save folder for this torrent. + // ``file_view_pool`` is the cache of file mappings that the storage will use. + // All files it opens will ask the file_view_pool to open them. ``file_prio`` + // is a vector indicating the priority of files on startup. It may be + // an empty vector. Any file whose index is not represented by the vector + // (because the vector is too short) are assumed to have priority 1. + // this is used to treat files with priority 0 slightly differently. + mmap_storage(storage_params const& params, aux::file_view_pool&); + + // hidden + ~mmap_storage(); + mmap_storage(mmap_storage const&) = delete; + mmap_storage& operator=(mmap_storage const&) = delete; + + void abort_jobs(); + + bool has_any_file(storage_error&); + void set_file_priority(settings_interface const& + , aux::vector& prio + , storage_error&); + void rename_file(file_index_t index, std::string const& new_filename + , storage_error&); + void release_files(storage_error&); + void delete_files(remove_flags_t options, storage_error&); + status_t initialize(settings_interface const&, storage_error&); + std::pair move_storage(std::string save_path + , move_flags_t, storage_error&); + bool verify_resume_data(add_torrent_params const& rd + , aux::vector const& links + , storage_error&); + bool tick(); + + int read(settings_interface const&, span bufs + , piece_index_t piece, int offset, aux::open_mode_t mode + , disk_job_flags_t flags + , storage_error&); + int write(settings_interface const&, span bufs + , piece_index_t piece, int offset, aux::open_mode_t mode + , disk_job_flags_t flags + , storage_error&); + int hash(settings_interface const&, hasher& ph, std::ptrdiff_t len + , piece_index_t piece, int offset, aux::open_mode_t mode + , disk_job_flags_t flags, storage_error&); + int hash2(settings_interface const&, hasher256& ph, std::ptrdiff_t len + , piece_index_t piece, int offset, aux::open_mode_t mode + , disk_job_flags_t flags, storage_error&); + + // if the files in this storage are mapped, returns the mapped + // file_storage, otherwise returns the original file_storage object. + file_storage const& files() const { return m_mapped_files ? *m_mapped_files : m_files; } + + bool set_need_tick() + { + bool const prev = m_need_tick; + m_need_tick = true; + return prev; + } + + void do_tick() + { + m_need_tick = false; + tick(); + } + + void set_owner(std::shared_ptr const& tor) { m_torrent = tor; } + + storage_index_t storage_index() const { return m_storage_index; } + void set_storage_index(storage_index_t st) { m_storage_index = st; } + + private: + + bool m_need_tick = false; + bool m_use_mmap_writes = false; + + file_storage const& m_files; + + // the reason for this to be a void pointer + // is to avoid creating a dependency on the + // torrent. This shared_ptr is here only + // to keep the torrent object alive until + // the storage destructs. This is because + // the file_storage object is owned by the torrent. + std::shared_ptr m_torrent; + + storage_index_t m_storage_index{0}; + + void need_partfile(); + + std::unique_ptr m_mapped_files; + + // in order to avoid calling stat() on each file multiple times + // during startup, cache the results in here, and clear it all + // out once the torrent starts (to avoid getting stale results) + // each entry represents the size and timestamp of the file + mutable stat_cache m_stat_cache; + + // helper function to open a file in the file pool with the right mode + std::shared_ptr open_file(settings_interface const&, file_index_t + , aux::open_mode_t, storage_error&) const; + std::shared_ptr open_file_impl(settings_interface const& + , file_index_t, aux::open_mode_t, storage_error&) const; + + bool use_partfile(file_index_t index) const; + void use_partfile(file_index_t index, bool b); + + aux::vector m_file_priority; + std::string m_save_path; + std::string m_part_file_name; + + // this this is an array indexed by file-index. Each slot represents + // whether this file has the part-file enabled for it. This is used for + // backwards compatibility with pre-partfile versions of libtorrent. If + // this vector is empty, the default is that files *do* use the partfile. + // on startup, any 0-priority file that's found in it's original location + // is expected to be an old-style (pre-partfile) torrent storage, and + // those files have their slot set to false in this vector. + // note that the vector is *sparse*, it's only allocated if a file has its + // entry set to false, and only indices up to that entry. + aux::vector m_use_partfile; + + // the file pool is a member of the disk_io_thread + // to make all storage instances share the pool + aux::file_view_pool& m_pool; + + // used for skipped files + std::unique_ptr m_part_file; + + // this is a bitfield with one bit per file. A bit being set means + // we've written to that file previously. If we do write to a file + // whose bit is 0, we set the file size, to make the file allocated + // on disk (in full allocation mode) and just sparsely allocated in + // case of sparse allocation mode + mutable std::mutex m_file_created_mutex; + mutable typed_bitfield m_file_created; + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + // Windows has a race condition when unmapping a view while a new + // view or mapping object is being created in a different thread. + // The race can cause a page of written data to be zeroed out before + // it is written out to disk. To avoid the race these calls must be + // serialized on a per-file basis. See github issue #3842 for details. + + // This array stores a mutex for each file in the storage object + // It must be acquired before calling CreateFileMapping or UnmapViewOfFile + mutable std::shared_ptr m_file_open_unmap_lock; +#endif + + bool m_allocate_files; + }; + +} + +#endif // TORRENT_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE + +#endif // TORRENT_STORAGE_HPP_INCLUDED diff --git a/docs/include/libtorrent/natpmp.hpp b/docs/include/libtorrent/natpmp.hpp new file mode 100644 index 0000000..68ebf5b --- /dev/null +++ b/docs/include/libtorrent/natpmp.hpp @@ -0,0 +1,220 @@ +/* + +Copyright (c) 2007-2010, 2015-2020, 2022, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2018, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_NATPMP_HPP +#define TORRENT_NATPMP_HPP + +#include "libtorrent/io_context.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/aux_/portmap.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/enum_net.hpp" // for ip_interface +#include "libtorrent/aux_/listen_socket_handle.hpp" + +namespace libtorrent { + +namespace errors { + // See RFC 6887 Section 7.4 + enum pcp_errors + { + pcp_success = 0, + pcp_unsupp_version, + pcp_not_authorized, + pcp_malformed_request, + pcp_unsupp_opcode, + pcp_unsupp_option, + pcp_malformed_option, + pcp_network_failure, + pcp_no_resources, + pcp_unsupp_protocol, + pcp_user_ex_quota, + pcp_cannot_provide_external, + pcp_address_mismatch, + pcp_excessive_remote_peers, + }; + + boost::system::error_code make_error_code(pcp_errors e); +} // namespace errors + + TORRENT_EXPORT boost::system::error_category& pcp_category(); +} // namespace libtorrent + +namespace boost { +namespace system { + template<> struct is_error_code_enum + { static const bool value = true; }; +} +} + +namespace libtorrent { + +struct TORRENT_EXTRA_EXPORT natpmp final + : std::enable_shared_from_this + , single_threaded +{ + natpmp(io_context& ios, aux::portmap_callback& cb, aux::listen_socket_handle ls); + + void start(ip_interface const& ip); + + // maps the ports, if a port is set to 0 + // it will not be mapped + port_mapping_t add_mapping(portmap_protocol p, int external_port, tcp::endpoint local_ep + , std::string const& device); + void delete_mapping(port_mapping_t mapping_index); + bool get_mapping(port_mapping_t mapping_index, int& local_port, int& external_port + , portmap_protocol& protocol) const; + + void close(); + +private: + static error_code from_result_code(int version, int result); + + std::shared_ptr self() { return shared_from_this(); } + + void update_mapping(port_mapping_t i); + void send_map_request(port_mapping_t i); + void send_get_ip_address_request(); + void on_resend_request(port_mapping_t i, error_code const& e); + void resend_request(port_mapping_t); + void on_reply(error_code const& e + , std::size_t bytes_transferred); + void try_next_mapping(port_mapping_t i); + void update_expiration_timer(); + void mapping_expired(error_code const& e, port_mapping_t i); + void close_impl(); + + void disable(error_code const& ec); + + enum protocol_version + { + version_natpmp = 0, + version_pcp = 2, + }; + + static char const* version_to_string(protocol_version version); + + // See RFC 6887 Section 19.2 + enum pcp_opcode + { + opcode_announce = 0, + opcode_map, + opcode_peer, + }; + + struct mapping_t : aux::base_mapping + { + // random identifier, used by PCP + std::array nonce; + + // only valid if the router supports PCP + address external_address; + + // the local port for this mapping. If this is set + // to 0, the mapping is not in use + int local_port = 0; + + // set to true when the first map request is sent + bool map_sent = false; + + // set to true while we're waiting for a response + bool outstanding_request = false; + }; + +#ifndef TORRENT_DISABLE_LOGGING + bool should_log() const; + void log(char const* fmt, ...) const TORRENT_FORMAT(2, 3); + void mapping_log(char const* op, mapping_t const& m) const; +#endif + + aux::portmap_callback& m_callback; + + protocol_version m_version = version_pcp; + + aux::vector m_mappings; + + // the endpoint to the nat router + udp::endpoint m_nat_endpoint; + + // this is the mapping that is currently + // being updated. It is -1 in case no + // mapping is being updated at the moment + port_mapping_t m_currently_mapping{-1}; + + // current retry count + int m_retry_count = 0; + + // used to receive responses in + // 1100 octets is the maximum size of a PCP packet + char m_response_buffer[1100]; + + // router external IP address + // this is only used if the router does not support PCP + // with PCP the external IP is stored with the mapping + address m_external_ip; + + // the endpoint we received the message from + udp::endpoint m_remote; + + // the udp socket used to communicate + // with the NAT router + udp::socket m_socket; + + // used to resend udp packets in case + // they time out + deadline_timer m_send_timer; + + // timer used to refresh mappings + deadline_timer m_refresh_timer; + + // the mapping index that will expire next + port_mapping_t m_next_refresh{-1}; + + io_context& m_ioc; + + aux::listen_socket_handle m_listen_handle; + + bool m_disabled = false; + + bool m_abort = false; +}; + +} // namespace libtorrent + +#endif diff --git a/docs/include/libtorrent/netlink.hpp b/docs/include/libtorrent/netlink.hpp new file mode 100644 index 0000000..e1d9a26 --- /dev/null +++ b/docs/include/libtorrent/netlink.hpp @@ -0,0 +1,203 @@ +/* + +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2016, Steven Siloti +Copyright (c) 2017, 2019, Arvid Norberg +Copyright (c) 2018, Eugene Shalygin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_NETLINK_HPP +#define TORRENT_NETLINK_HPP + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_NETLINK + +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + + template + class basic_nl_endpoint + { + public: + using protocol_type = Protocol; + using data_type = boost::asio::detail::socket_addr_type; + + basic_nl_endpoint() noexcept : basic_nl_endpoint(protocol_type(), 0, 0) {} + + basic_nl_endpoint(protocol_type netlink_family, std::uint32_t group, std::uint32_t pid = 0) + : m_proto(netlink_family) + { + std::memset(&m_sockaddr, 0, sizeof(sockaddr_nl)); + m_sockaddr.nl_family = AF_NETLINK; + m_sockaddr.nl_groups = group; + m_sockaddr.nl_pid = pid; + } + + basic_nl_endpoint(basic_nl_endpoint const& other) = default; + basic_nl_endpoint(basic_nl_endpoint&& other) noexcept = default; + + basic_nl_endpoint& operator=(basic_nl_endpoint const& other) + { + m_sockaddr = other.m_sockaddr; + return *this; + } + + basic_nl_endpoint& operator=(basic_nl_endpoint&& other) noexcept + { + m_sockaddr = other.m_sockaddr; + return *this; + } + + protocol_type protocol() const + { + return m_proto; + } + + data_type* data() + { + return reinterpret_cast(&m_sockaddr); + } + + const data_type* data() const + { + return reinterpret_cast(&m_sockaddr); + } + + std::size_t size() const + { + return sizeof(m_sockaddr); + } + + std::size_t capacity() const + { + return sizeof(m_sockaddr); + } + + // commented the comparison operators for now, until the + // same operators are implemented for sockaddr_nl + /* + friend bool operator==(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return l.m_sockaddr == r.m_sockaddr; + } + + friend bool operator!=(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return !(l.m_sockaddr == r.m_sockaddr); + } + + friend bool operator<(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return l.m_sockaddr < r.m_sockaddr; + } + + friend bool operator>(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return r.m_sockaddr < l.m_sockaddr; + } + + friend bool operator<=(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return !(r < l); + } + + friend bool operator>=(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return !(l < r); + } + */ + + private: + protocol_type m_proto; + sockaddr_nl m_sockaddr; + }; + + class netlink + { + public: + using endpoint = basic_nl_endpoint; + using socket = boost::asio::basic_raw_socket; + + netlink() : netlink(NETLINK_ROUTE) {} + + explicit netlink(int nl_family) + : m_nl_family(nl_family) + { + } + + int type() const + { + return SOCK_RAW; + } + + int protocol() const + { + return m_nl_family; + } + + int family() const + { + return AF_NETLINK; + } + + friend bool operator==(const netlink& l, const netlink& r) + { + return l.m_nl_family == r.m_nl_family; + } + + friend bool operator!=(const netlink& l, const netlink& r) + { + return l.m_nl_family != r.m_nl_family; + } + + private: + int m_nl_family; + }; + +} + +#endif // TORRENT_USE_NETLINK + +#endif diff --git a/docs/include/libtorrent/operations.hpp b/docs/include/libtorrent/operations.hpp new file mode 100644 index 0000000..5dfb607 --- /dev/null +++ b/docs/include/libtorrent/operations.hpp @@ -0,0 +1,265 @@ +/* + +Copyright (c) 2015, 2017-2021, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_OPERATIONS_HPP_INCLUDED +#define TORRENT_OPERATIONS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include + +namespace libtorrent { + + // these constants are used to identify the operation that failed, causing a + // peer to disconnect + enum class operation_t : std::uint8_t + { + // the error was unexpected and it is unknown which operation caused it + unknown, + + // this is used when the bittorrent logic + // determines to disconnect + bittorrent, + + // a call to iocontrol failed + iocontrol, + + // a call to ``getpeername()`` failed (querying the remote IP of a + // connection) + getpeername, + + // a call to getname failed (querying the local IP of a + // connection) + getname, + + // an attempt to allocate a receive buffer failed + alloc_recvbuf, + + // an attempt to allocate a send buffer failed + alloc_sndbuf, + + // writing to a file failed + file_write, + + // reading from a file failed + file_read, + + // a non-read and non-write file operation failed + file, + + // a socket write operation failed + sock_write, + + // a socket read operation failed + sock_read, + + // a call to open(), to create a socket socket failed + sock_open, + + // a call to bind() on a socket failed + sock_bind, + + // an attempt to query the number of bytes available to read from a socket + // failed + available, + + // a call related to bittorrent protocol encryption failed + encryption, + + // an attempt to connect a socket failed + connect, + + // establishing an SSL connection failed + ssl_handshake, + + // a connection failed to satisfy the bind interface setting + get_interface, + + // a call to listen() on a socket + sock_listen, + + // a call to the ioctl to bind a socket to a specific network device or + // adapter + sock_bind_to_device, + + // a call to accept() on a socket + sock_accept, + + // convert a string into a valid network address + parse_address, + + // enumeration network devices or adapters + enum_if, + + // invoking stat() on a file + file_stat, + + // copying a file + file_copy, + + // allocating storage for a file + file_fallocate, + + // creating a hard link + file_hard_link, + + // removing a file + file_remove, + + // renaming a file + file_rename, + + // opening a file + file_open, + + // creating a directory + mkdir, + + // check fast resume data against files on disk + check_resume, + + // an unknown exception + exception, + + // allocate space for a piece in the cache + alloc_cache_piece, + + // move a part-file + partfile_move, + + // read from a part file + partfile_read, + + // write to a part-file + partfile_write, + + // a hostname lookup + hostname_lookup, + + // create or read a symlink + symlink, + + // handshake with a peer or server + handshake, + + // set socket option + sock_option, + + // enumeration of network routes + enum_route, + + // moving read/write position in a file, operation_t::hostname_lookup + file_seek, + + // an async wait operation on a timer + timer, + + // call to mmap() (or windows counterpart) + file_mmap, + + // call to ftruncate() (or SetEndOfFile() on windows) + file_truncate, + }; + + // maps an operation id (from peer_error_alert and peer_disconnected_alert) + // to its name. See operation_t for the constants + TORRENT_EXPORT char const* operation_name(operation_t op); + +#if TORRENT_ABI_VERSION == 1 + enum deprecated_operation_t : std::uint8_t + { + // the error was unexpected and it is unknown which operation caused it + op_unknown TORRENT_DEPRECATED_ENUM, + + // this is used when the bittorrent logic + // determines to disconnect + op_bittorrent TORRENT_DEPRECATED_ENUM , + + // a call to ``iocontrol()`` failed + op_iocontrol TORRENT_DEPRECATED_ENUM, + + // a call to ``getpeername()`` failed (querying the remote IP of a + // connection) + op_getpeername TORRENT_DEPRECATED_ENUM, + + // a call to ``getsockname()`` failed (querying the local IP of a + // connection) + op_getname TORRENT_DEPRECATED_ENUM, + + // an attempt to allocate a receive buffer failed + op_alloc_recvbuf TORRENT_DEPRECATED_ENUM, + + // an attempt to allocate a send buffer failed + op_alloc_sndbuf TORRENT_DEPRECATED_ENUM, + + // writing to a file failed + op_file_write TORRENT_DEPRECATED_ENUM, + + // reading from a file failed + op_file_read TORRENT_DEPRECATED_ENUM, + + // a non-read and non-write file operation failed + op_file TORRENT_DEPRECATED_ENUM, + + // a socket write operation failed + op_sock_write TORRENT_DEPRECATED_ENUM, + + // a socket read operation failed + op_sock_read TORRENT_DEPRECATED_ENUM, + + // a call to open(), to create a socket socket failed + op_sock_open TORRENT_DEPRECATED_ENUM, + + // a call to bind() on a socket failed + op_sock_bind TORRENT_DEPRECATED_ENUM, + + // an attempt to query the number of bytes available to read from a socket + // failed + op_available TORRENT_DEPRECATED_ENUM, + + // a call related to bittorrent protocol encryption failed + op_encryption TORRENT_DEPRECATED_ENUM, + + // an attempt to connect a socket failed + op_connect TORRENT_DEPRECATED_ENUM, + + // establishing an SSL connection failed + op_ssl_handshake TORRENT_DEPRECATED_ENUM, + + // a connection failed to satisfy the bind interface setting + op_get_interface TORRENT_DEPRECATED_ENUM, + }; +#endif + +} + +#endif // TORRENT_OPERATIONS_HPP_INCLUDED diff --git a/docs/include/libtorrent/optional.hpp b/docs/include/libtorrent/optional.hpp new file mode 100644 index 0000000..bf85715 --- /dev/null +++ b/docs/include/libtorrent/optional.hpp @@ -0,0 +1,51 @@ +/* + +Copyright (c) 2017, 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + + +#ifndef TORRENT_OPTIONAL_HPP_INCLUDED +#define TORRENT_OPTIONAL_HPP_INCLUDED + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + + template + T value_or(boost::optional opt, U def) + { + return opt ? *opt : T(def); + } +} + +#endif + diff --git a/docs/include/libtorrent/parse_url.hpp b/docs/include/libtorrent/parse_url.hpp new file mode 100644 index 0000000..b5f5b9e --- /dev/null +++ b/docs/include/libtorrent/parse_url.hpp @@ -0,0 +1,66 @@ +/* + +Copyright (c) 2008-2009, 2014, 2016-2017, 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PARSE_URL_HPP_INCLUDED +#define TORRENT_PARSE_URL_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include +#include + +#include "libtorrent/error_code.hpp" +#include "libtorrent/string_view.hpp" + +namespace libtorrent { + + // returns protocol, auth, hostname, port, path + TORRENT_EXTRA_EXPORT std::tuple + parse_url_components(std::string url, error_code& ec); + + // split a URL in its base and path parts + TORRENT_EXTRA_EXPORT std::tuple + split_url(std::string url, error_code& ec); + + // returns true if the hostname contains any IDNA (internationalized domain + // name) labels. + TORRENT_EXTRA_EXPORT bool is_idna(string_view hostname); + + // the query string is the part of the URL immediately following "?", i.e. + // the query string arguments. This function returns true if any of the + // arguments are "info_hash", "port", "key", "event", "uploaded", + // "downloaded", "left" or "corrupt". + TORRENT_EXTRA_EXPORT bool has_tracker_query_string(string_view query_string); +} + +#endif diff --git a/docs/include/libtorrent/part_file.hpp b/docs/include/libtorrent/part_file.hpp new file mode 100644 index 0000000..e4a0d5d --- /dev/null +++ b/docs/include/libtorrent/part_file.hpp @@ -0,0 +1,138 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, d-komarov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PART_FILE_HPP_INCLUDE +#define TORRENT_PART_FILE_HPP_INCLUDE + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/aux_/open_mode.hpp" +#include "libtorrent/aux_/storage_utils.hpp" // for iovec_t + +namespace libtorrent { + + using slot_index_t = aux::strong_typedef; + + struct TORRENT_EXTRA_EXPORT part_file + { + // create a part file at ``path``, that can hold ``num_pieces`` pieces. + // each piece being ``piece_size`` number of bytes + part_file(std::string path, std::string name, int num_pieces, int piece_size); + ~part_file(); + + int write(span buf, piece_index_t piece, int offset, error_code& ec); + int read(span buf, piece_index_t piece, int offset, error_code& ec); + int hash(hasher& ph, std::ptrdiff_t len, piece_index_t piece, int offset, error_code& ec); + int hash2(hasher256& ph, std::ptrdiff_t len, piece_index_t piece, int offset, error_code& ec); + + // free the slot the given piece is stored in. We no longer need to store this + // piece in the part file + void free_piece(piece_index_t piece); + + void move_partfile(std::string const& path, error_code& ec); + + // the function is called for every block of data belonging to the + // specified range that's in the part_file. The first parameter is the + // offset within the range + void export_file(std::function)> f + , std::int64_t offset, std::int64_t size, error_code& ec); + + // flush the metadata + void flush_metadata(error_code& ec); + + private: + + aux::file_handle open_file(aux::open_mode_t mode, error_code& ec); + void flush_metadata_impl(error_code& ec); + + std::int64_t slot_offset(slot_index_t const slot) const + { + return static_cast(slot) * static_cast(m_piece_size) + + m_header_size; + } + + template + int do_hash(Hasher& ph, std::ptrdiff_t len, piece_index_t piece, int offset, error_code& ec); + + std::string m_path; + std::string const m_name; + + // allocate a slot and return the slot index + slot_index_t allocate_slot(piece_index_t piece); + + // this mutex must be held while accessing the data + // structure. Not while reading or writing from the file though! + // it's important to support multithreading + std::mutex m_mutex; + + // this is a list of unallocated slots in the part file + // within the m_num_allocated range + std::vector m_free_slots; + + // this is the number of slots allocated + slot_index_t m_num_allocated{0}; + + // the max number of pieces in the torrent this part file is + // backing + int const m_max_pieces; + + // number of bytes each piece contains + int const m_piece_size; + + // this is the size of the part_file header, it is added + // to offsets when calculating the offset to read and write + // payload data from + int const m_header_size; + + // if this is true, the metadata in memory has changed since + // we last saved or read it from disk. It means that we + // need to flush the metadata before closing the file + bool m_dirty_metadata = false; + + // maps a piece index to the part-file slot it is stored in + std::unordered_map m_piece_map; + }; +} + +#endif diff --git a/docs/include/libtorrent/pe_crypto.hpp b/docs/include/libtorrent/pe_crypto.hpp new file mode 100644 index 0000000..3c2280e --- /dev/null +++ b/docs/include/libtorrent/pe_crypto.hpp @@ -0,0 +1,162 @@ +/* + +Copyright (c) 2007, Un Shyam +Copyright (c) 2007-2009, 2011-2012, 2014-2019, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PE_CRYPTO_HPP_INCLUDED +#define TORRENT_PE_CRYPTO_HPP_INCLUDED + +#if !defined TORRENT_DISABLE_ENCRYPTION + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/aux_/receive_buffer.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/aux_/array.hpp" + +#include +#include +#include + +namespace libtorrent { + + namespace mp = boost::multiprecision; + + using key_t = mp::number>; + + TORRENT_EXTRA_EXPORT std::array export_key(key_t const& k); + + // RC4 state from libtomcrypt + struct rc4 { + int x; + int y; + aux::array buf; + }; + + // TODO: 3 dh_key_exchange should probably move into its own file + class TORRENT_EXTRA_EXPORT dh_key_exchange + { + public: + dh_key_exchange(); + + // Get local public key + key_t const& get_local_key() const { return m_dh_local_key; } + + // read remote_pubkey, generate and store shared secret in + // m_dh_shared_secret. + void compute_secret(std::uint8_t const* remote_pubkey); + void compute_secret(key_t const& remote_pubkey); + + key_t const& get_secret() const { return m_dh_shared_secret; } + + sha1_hash const& get_hash_xor_mask() const { return m_xor_mask; } + + private: + + key_t m_dh_local_key; + key_t m_dh_local_secret; + key_t m_dh_shared_secret; + sha1_hash m_xor_mask; + }; + + struct TORRENT_EXTRA_EXPORT encryption_handler + { + std::tuple>> + encrypt(span> iovec); + + int decrypt(aux::crypto_receive_buffer& recv_buffer + , std::size_t& bytes_transferred); + + bool switch_send_crypto(std::shared_ptr crypto + , int pending_encryption); + + void switch_recv_crypto(std::shared_ptr crypto + , aux::crypto_receive_buffer& recv_buffer); + + bool is_send_plaintext() const + { + return m_send_barriers.empty() || m_send_barriers.back().next != INT_MAX; + } + + bool is_recv_plaintext() const + { + return m_dec_handler.get() == nullptr; + } + + private: + struct barrier + { + barrier(std::shared_ptr plugin, int n) + : enc_handler(plugin), next(n) {} + std::shared_ptr enc_handler; + // number of bytes to next barrier + int next; + }; + std::list m_send_barriers; + std::shared_ptr m_dec_handler; + }; + + struct TORRENT_EXTRA_EXPORT rc4_handler : crypto_plugin + { + public: + rc4_handler(); + + // Input keys must be 20 bytes + void set_incoming_key(span key) override; + void set_outgoing_key(span key) override; + + std::tuple>> + encrypt(span> buf) override; + + std::tuple decrypt(span> buf) override; + + private: + rc4 m_rc4_incoming; + rc4 m_rc4_outgoing; + + // determines whether or not encryption and decryption is enabled + bool m_encrypt; + bool m_decrypt; + }; + +} // namespace libtorrent + +#endif // TORRENT_DISABLE_ENCRYPTION + +#endif // TORRENT_PE_CRYPTO_HPP_INCLUDED diff --git a/docs/include/libtorrent/peer.hpp b/docs/include/libtorrent/peer.hpp new file mode 100644 index 0000000..a00bf9a --- /dev/null +++ b/docs/include/libtorrent/peer.hpp @@ -0,0 +1,76 @@ +/* + +Copyright (c) 2003-2004, 2006, 2012, 2014-2017, 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_HPP_INCLUDED +#define TORRENT_PEER_HPP_INCLUDED + +#include + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/address.hpp" + +namespace libtorrent { + + struct TORRENT_EXTRA_EXPORT peer_entry + { + std::string hostname; + peer_id pid; + std::uint16_t port; + + bool operator==(const peer_entry& p) const + { return pid == p.pid; } + + bool operator<(const peer_entry& p) const + { return pid < p.pid; } + }; + + struct ipv4_peer_entry + { + address_v4::bytes_type ip; + std::uint16_t port; + }; + + struct ipv6_peer_entry + { + address_v6::bytes_type ip; + std::uint16_t port; + }; + +#if TORRENT_USE_I2P + struct i2p_peer_entry + { + sha256_hash destination; + }; +#endif +} + +#endif // TORRENT_PEER_HPP_INCLUDED diff --git a/docs/include/libtorrent/peer_class.hpp b/docs/include/libtorrent/peer_class.hpp new file mode 100644 index 0000000..75402a9 --- /dev/null +++ b/docs/include/libtorrent/peer_class.hpp @@ -0,0 +1,159 @@ +/* + +Copyright (c) 2014-2018, 2020, Arvid Norberg +Copyright (c) 2016, 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CLASS_HPP_INCLUDED +#define TORRENT_PEER_CLASS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/bandwidth_limit.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/deque.hpp" + +#include +#include +#include +#include + +namespace libtorrent { + + using peer_class_t = aux::strong_typedef; + + // holds settings for a peer class. Used in set_peer_class() and + // get_peer_class() calls. + struct TORRENT_EXPORT peer_class_info + { + // ``ignore_unchoke_slots`` determines whether peers should always + // unchoke a peer, regardless of the choking algorithm, or if it should + // honor the unchoke slot limits. It's used for local peers by default. + // If *any* of the peer classes a peer belongs to has this set to true, + // that peer will be unchoked at all times. + bool ignore_unchoke_slots; + + // adjusts the connection limit (global and per torrent) that applies to + // this peer class. By default, local peers are allowed to exceed the + // normal connection limit for instance. This is specified as a percent + // factor. 100 makes the peer class apply normally to the limit. 200 + // means as long as there are fewer connections than twice the limit, we + // accept this peer. This factor applies both to the global connection + // limit and the per-torrent limit. Note that if not used carefully one + // peer class can potentially completely starve out all other over time. + int connection_limit_factor; + + // not used by libtorrent. It's intended as a potentially user-facing + // identifier of this peer class. + std::string label; + + // transfer rates limits for the whole peer class. They are specified in + // bytes per second and apply to the sum of all peers that are members of + // this class. + int upload_limit; + int download_limit; + + // relative priorities used by the bandwidth allocator in the rate + // limiter. If no rate limits are in use, the priority is not used + // either. Priorities start at 1 (0 is not a valid priority) and may not + // exceed 255. + int upload_priority; + int download_priority; + }; + + struct TORRENT_EXTRA_EXPORT peer_class + { + friend struct peer_class_pool; + + explicit peer_class(std::string l) + : ignore_unchoke_slots(false) + , connection_limit_factor(100) + , label(std::move(l)) + , in_use(true) + , references(1) + { + priority[0] = 1; + priority[1] = 1; + } + + void clear() + { + in_use = false; + label.clear(); + } + + void set_info(peer_class_info const* pci); + void get_info(peer_class_info* pci) const; + + void set_upload_limit(int limit); + void set_download_limit(int limit); + + // the bandwidth channels, upload and download + // keeps track of the current quotas + aux::bandwidth_channel channel[2]; + + bool ignore_unchoke_slots; + int connection_limit_factor; + + // priority for bandwidth allocation + // in rate limiter. One for upload and one + // for download + int priority[2]; + + // the name of this peer class + std::string label; + + private: + // this is set to false when this slot is not in use for a peer_class + bool in_use; + + int references; + }; + + struct TORRENT_EXTRA_EXPORT peer_class_pool + { + peer_class_t new_peer_class(std::string label); + void decref(peer_class_t c); + void incref(peer_class_t c); + peer_class* at(peer_class_t c); + peer_class const* at(peer_class_t c) const; + + private: + + // state for peer classes (a peer can belong to multiple classes) + // this can control + aux::deque m_peer_classes; + + // indices in m_peer_classes that are no longer used + std::vector m_free_list; + }; +} + +#endif // TORRENT_PEER_CLASS_HPP_INCLUDED diff --git a/docs/include/libtorrent/peer_class_set.hpp b/docs/include/libtorrent/peer_class_set.hpp new file mode 100644 index 0000000..0e2646c --- /dev/null +++ b/docs/include/libtorrent/peer_class_set.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2010, 2013-2015, 2019, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CLASS_SET_HPP_INCLUDED +#define TORRENT_PEER_CLASS_SET_HPP_INCLUDED + +#include "libtorrent/peer_class.hpp" +#include "libtorrent/aux_/array.hpp" + +namespace libtorrent { + + // this represents an object that can have many peer classes applied + // to it. Most notably, peer connections and torrents derive from this. + struct TORRENT_EXTRA_EXPORT peer_class_set + { + peer_class_set() : m_size(0) {} + void add_class(peer_class_pool& pool, peer_class_t c); + bool has_class(peer_class_t c) const; + void remove_class(peer_class_pool& pool, peer_class_t c); + int num_classes() const { return m_size; } + peer_class_t class_at(int i) const + { + TORRENT_ASSERT(i >= 0 && i < int(m_size)); + return m_class[i]; + } + + private: + + // the number of elements used in the m_class array + std::int8_t m_size; + + // if this object belongs to any peer-class, this vector contains all + // class IDs. Each ID refers to a an entry in m_ses.m_peer_classes which + // holds the metadata about the class. Classes affect bandwidth limits + // among other things + aux::array m_class; + }; +} + +#endif // TORRENT_PEER_CLASS_SET_HPP_INCLUDED diff --git a/docs/include/libtorrent/peer_class_type_filter.hpp b/docs/include/libtorrent/peer_class_type_filter.hpp new file mode 100644 index 0000000..bc6815b --- /dev/null +++ b/docs/include/libtorrent/peer_class_type_filter.hpp @@ -0,0 +1,146 @@ +/* + +Copyright (c) 2014, 2016-2017, 2019, Arvid Norberg +Copyright (c) 2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CLASS_TYPE_FILTER_HPP_INCLUDED +#define TORRENT_PEER_CLASS_TYPE_FILTER_HPP_INCLUDED + +#include +#include + +#include "aux_/export.hpp" +#include "peer_class.hpp" // for peer_class_t + +namespace libtorrent { + + // ``peer_class_type_filter`` is a simple container for rules for adding and subtracting + // peer-classes from peers. It is applied *after* the peer class filter is applied (which + // is based on the peer's IP address). + struct TORRENT_EXPORT peer_class_type_filter + { + // hidden + peer_class_type_filter() + { + m_peer_class_type_mask.fill(0xffffffff); + m_peer_class_type.fill(0); + } + + enum socket_type_t : std::uint8_t + { + // these match the socket types from socket_type.hpp + // shifted one down + tcp_socket = 0, + utp_socket, + ssl_tcp_socket, + ssl_utp_socket, + i2p_socket, + num_socket_types + }; + + // ``add()`` and ``remove()`` adds and removes a peer class to be added + // to new peers based on socket type. + void add(socket_type_t const st, peer_class_t const peer_class) + { + TORRENT_ASSERT(peer_class < peer_class_t{32}); + if (peer_class > peer_class_t{31}) return; + + TORRENT_ASSERT(st < num_socket_types); + if (st >= num_socket_types) return; + m_peer_class_type[st] |= 1 << static_cast(peer_class); + } + void remove(socket_type_t const st, peer_class_t const peer_class) + { + TORRENT_ASSERT(peer_class < peer_class_t{32}); + if (peer_class > peer_class_t{31}) return; + + TORRENT_ASSERT(st < num_socket_types); + if (st >= num_socket_types) return; + m_peer_class_type[st] &= ~(1 << static_cast(peer_class)); + } + + // ``disallow()`` and ``allow()`` adds and removes a peer class to be + // removed from new peers based on socket type. + // + // The ``peer_class`` argument cannot be greater than 31. The bitmasks representing + // peer classes in the ``peer_class_type_filter`` are 32 bits. + void disallow(socket_type_t const st, peer_class_t const peer_class) + { + TORRENT_ASSERT(peer_class < peer_class_t{32}); + if (peer_class > peer_class_t{31}) return; + + TORRENT_ASSERT(st < num_socket_types); + if (st >= num_socket_types) return; + m_peer_class_type_mask[st] &= ~(1 << static_cast(peer_class)); + } + void allow(socket_type_t const st, peer_class_t const peer_class) + { + TORRENT_ASSERT(peer_class < peer_class_t{32}); + if (peer_class > peer_class_t{31}) return; + + TORRENT_ASSERT(st < num_socket_types); + if (st >= num_socket_types) return; + m_peer_class_type_mask[st] |= 1 << static_cast(peer_class); + } + + // takes a bitmask of peer classes and returns a new bitmask of + // peer classes after the rules have been applied, based on the socket type argument + // (``st``). + std::uint32_t apply(socket_type_t const st, std::uint32_t peer_class_mask) + { + TORRENT_ASSERT(st < num_socket_types); + if (st >= num_socket_types) return peer_class_mask; + + // filter peer classes based on type + peer_class_mask &= m_peer_class_type_mask[st]; + // add peer classes based on type + peer_class_mask |= m_peer_class_type[st]; + return peer_class_mask; + } + + friend bool operator==(peer_class_type_filter const& lhs + , peer_class_type_filter const& rhs) + { + return lhs.m_peer_class_type_mask == rhs.m_peer_class_type_mask + && lhs.m_peer_class_type == rhs.m_peer_class_type; + } + + private: + // maps socket type to a bitmask that's used to filter out + // (mask) bits from the m_peer_class_filter. + std::array m_peer_class_type_mask; + // peer class bitfield added based on socket type + std::array m_peer_class_type; + }; + +} + +#endif diff --git a/docs/include/libtorrent/peer_connection.hpp b/docs/include/libtorrent/peer_connection.hpp new file mode 100644 index 0000000..e27de87 --- /dev/null +++ b/docs/include/libtorrent/peer_connection.hpp @@ -0,0 +1,1262 @@ +/* + +Copyright (c) 2016, Falcosc +Copyright (c) 2003-2022, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016-2018, 2020, Alden Torres +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2017-2018, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CONNECTION_HPP_INCLUDED +#define TORRENT_PEER_CONNECTION_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/aux_/bandwidth_limit.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/chained_buffer.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/aux_/bandwidth_socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/sliding_average.hpp" +#include "libtorrent/peer_class.hpp" +#include "libtorrent/peer_class_set.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/disk_observer.hpp" +#include "libtorrent/peer_connection_interface.hpp" +#include "libtorrent/socket.hpp" // for tcp::endpoint +#include "libtorrent/io_context.hpp" +#include "libtorrent/aux_/receive_buffer.hpp" +#include "libtorrent/aux_/allocating_handler.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/piece_block.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/piece_picker.hpp" // for picker_options_t +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/socket_type.hpp" + +#include +#include +#include +#include +#include // for std::forward +#include // for make_tuple +#include +#include + +namespace libtorrent { + + struct torrent; + struct torrent_peer; + struct disk_interface; + +#ifndef TORRENT_DISABLE_EXTENSIONS + struct peer_plugin; +#endif + +namespace aux { + + struct session_interface; + + struct min_value_t {}; + static const min_value_t min_value{}; + + struct relative_time + { + relative_time() : m_time_diff(0) {} + explicit relative_time(min_value_t) : m_time_diff(std::numeric_limits::min()) {} + void set(time_point const reference, time_point const new_value) noexcept + { + m_time_diff = duration_cast(new_value - reference); + } + + time_point get(time_point reference) const noexcept + { + return reference + m_time_diff; + } + private: + milliseconds32 m_time_diff; + }; + + template + T clamp_assign(int const v) + { + auto const limit = std::numeric_limits::max(); + if (v < 0) return 0; + if (v > int(limit)) return limit; + return static_cast(v); + } +} + + struct pending_block + { + pending_block(piece_block const& b) // NOLINT + : block(b), send_buffer_offset(not_in_buffer), not_wanted(false) + , timed_out(false), busy(false) + {} + + piece_block block; + + static constexpr std::uint32_t not_in_buffer = 0x1fffffff; + + // the number of bytes into the send buffer this request is. Every time + // some portion of the send buffer is transmitted, this offset is + // decremented by the number of bytes sent. once this drops below 0, the + // request_time field is set to the current time. + // if the request has not been written to the send buffer, this field + // remains not_in_buffer. + std::uint32_t send_buffer_offset:29; + + // if any of these are set to true, this block + // is not allocated + // in the piece picker anymore, and open for + // other peers to pick. This may be caused by + // it either timing out or being received + // unexpectedly from the peer + std::uint32_t not_wanted:1; + std::uint32_t timed_out:1; + + // the busy flag is set if the block was + // requested from another peer when this + // request was queued. We only allow a single + // busy request at a time in each peer's queue + std::uint32_t busy:1; + + bool operator==(pending_block const& b) const + { + return b.block == block + && b.not_wanted == not_wanted + && b.timed_out == timed_out; + } + }; + + // argument pack passed to peer_connection constructor + struct peer_connection_args + { + aux::session_interface* ses; + aux::session_settings const* sett; + counters* stats_counters; + disk_interface* disk_thread; + io_context* ios; + std::weak_ptr tor; + aux::socket_type s; + tcp::endpoint endp; + torrent_peer* peerinfo; + peer_id our_peer_id; + }; + + struct TORRENT_EXTRA_EXPORT peer_connection_hot_members + { + // if tor is set, this is an outgoing connection + peer_connection_hot_members( + std::weak_ptr t + , aux::session_interface& ses + , aux::session_settings const& sett) + : m_torrent(std::move(t)) + , m_ses(ses) + , m_settings(sett) + , m_disconnecting(false) + , m_connecting(!m_torrent.expired()) + , m_endgame_mode(false) + , m_snubbed(false) + , m_interesting(false) + , m_choked(true) + , m_ignore_stats(false) + {} + + // explicitly disallow assignment, to silence msvc warning + peer_connection_hot_members& operator=(peer_connection_hot_members const&) = delete; + + protected: + + // the pieces the other end have + typed_bitfield m_have_piece; + + // this is the torrent this connection is + // associated with. If the connection is an + // incoming connection, this is set to zero + // until the info_hash is received. Then it's + // set to the torrent it belongs to. + + // TODO: make this a raw pointer (to save size in + // the first cache line) and make the constructor + // take a raw pointer. torrent objects should always + // outlive their peers + std::weak_ptr m_torrent; + + public: + + // a back reference to the session + // the peer belongs to. + aux::session_interface& m_ses; + + // settings that apply to this peer + aux::session_settings const& m_settings; + + protected: + + // this is true if this connection has been added + // to the list of connections that will be closed. + bool m_disconnecting:1; + + // this is true until this socket has become + // writable for the first time (i.e. the + // connection completed). While connecting + // the timeout will not be triggered. This is + // because windows XP SP2 may delay connection + // attempts, which means that the connection + // may not even have been attempted when the + // time out is reached. + bool m_connecting:1; + + // this is set to true if the last time we tried to + // pick a piece to download, we could only find + // blocks that were already requested from other + // peers. In this case, we should not try to pick + // another piece until the last one we requested is done + bool m_endgame_mode:1; + + // set to true when a piece request times out. The + // result is that the desired pending queue size + // is set to 1 + bool m_snubbed:1; + + // the peer has pieces we are interested in + bool m_interesting:1; + + // we have choked the upload to the peer + bool m_choked:1; + + // when this is set, the transfer stats for this connection + // is not included in the torrent or session stats + bool m_ignore_stats:1; + }; + + enum class connection_type : std::uint8_t + { + bittorrent, + url_seed, + http_seed + }; + + using request_flags_t = flags::bitfield_flag; + + struct TORRENT_EXTRA_EXPORT peer_connection + : peer_connection_hot_members + , aux::bandwidth_socket + , peer_class_set + , disk_observer + , peer_connection_interface + , std::enable_shared_from_this + { + friend struct invariant_access; + friend struct torrent; + friend struct cork; + + // explicitly disallow assignment, to silence msvc warning + peer_connection& operator=(peer_connection const&) = delete; + + void on_exception(std::exception const& e); + void on_error(error_code const& ec); + + virtual connection_type type() const = 0; + + enum channels + { + upload_channel, + download_channel, + num_channels + }; + + explicit peer_connection(peer_connection_args& pack); + + // this function is called after it has been constructed and properly + // reference counted. It is safe to call self() in this function + // and schedule events with references to itself (that is not safe to + // do in the constructor). + virtual void start(); + + ~peer_connection() override; + + void set_peer_info(torrent_peer* pi) override + { + TORRENT_ASSERT(m_peer_info == nullptr || pi == nullptr ); + TORRENT_ASSERT(pi != nullptr || m_disconnect_started); + m_peer_info = pi; + } + + torrent_peer* peer_info_struct() const override + { return m_peer_info; } + + // this is called when the peer object is created, in case + // it was let in by the connections limit slack. This means + // the peer needs to, as soon as the handshake is done, either + // disconnect itself or another peer. + void peer_exceeds_limit() + { m_exceeded_limit = true; } + + // this is called if this peer causes another peer + // to be disconnected, in which case it has fulfilled + // its requirement. + void peer_disconnected_other() + { m_exceeded_limit = false; } + + void send_allowed_set(); + +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extension(std::shared_ptr); + peer_plugin const* find_plugin(string_view type); +#endif + + // this function is called once the torrent associated + // with this peer connection has retrieved the meta- + // data. If the torrent was spawned with metadata + // this is called from the constructor. + void init(); + + // this is called when the metadata is retrieved + // and the files has been checked + virtual void on_metadata() {} + + void on_metadata_impl(); + + void picker_options(picker_options_t o) { m_picker_options = o; } + + int prefer_contiguous_blocks() const + { + if (on_parole()) return 1; + return int(m_prefer_contiguous_blocks); + } + + bool on_parole() const; + + picker_options_t picker_options() const; + + void prefer_contiguous_blocks(int const num) + { + m_prefer_contiguous_blocks = aux::clamp_assign(num); + } + + bool request_large_blocks() const + { return m_request_large_blocks; } + + void request_large_blocks(bool b) + { m_request_large_blocks = b; } + + void set_endgame(bool b); + bool endgame() const { return m_endgame_mode; } + + bool no_download() const { return m_no_download; } + void no_download(bool b) { m_no_download = b; } + + bool ignore_stats() const { return m_ignore_stats; } + void ignore_stats(bool b) { m_ignore_stats = b; } + + std::uint32_t peer_rank() const; + + void fast_reconnect(bool r); + bool fast_reconnect() const override { return m_fast_reconnect; } + + // this is called when we receive a new piece + // (and it has passed the hash check) + void received_piece(piece_index_t index); + + // this adds an announcement in the announcement queue + // it will let the peer know that we have the given piece + void announce_piece(piece_index_t index); + +#ifndef TORRENT_DISABLE_SUPERSEEDING + // this will tell the peer to announce the given piece + // and only allow it to request that piece + void superseed_piece(piece_index_t replace_piece, piece_index_t new_piece); + bool super_seeded_piece(piece_index_t index) const + { + return m_superseed_piece[0] == index + || m_superseed_piece[1] == index; + } +#endif + + // tells if this connection has data it want to send + // and has enough upload bandwidth quota left to send it. + bool can_write() const; + bool can_read(); + + bool is_seed() const; + int num_have_pieces() const { return m_num_pieces; } + +#ifndef TORRENT_DISABLE_SHARE_MODE + void set_share_mode(bool); + bool share_mode() const { return m_share_mode; } +#endif + + void set_upload_only(bool); + bool upload_only() const { return m_upload_only || is_seed() || m_have_all; } + + void set_holepunch_mode() override; + + // will send a keep-alive message to the peer + void keep_alive(); + + peer_id const& pid() const override { return m_peer_id; } + void set_pid(peer_id const& peer_id) { m_peer_id = peer_id; } + bool has_piece(piece_index_t i) const; + + std::vector const& download_queue() const; + std::vector const& request_queue() const; + std::vector const& upload_queue() const; + + void clear_request_queue(); + void clear_download_queue(); + + // estimate of how long it will take until we have + // received all piece requests that we have sent + // if extra_bytes is specified, it will include those + // bytes as if they've been requested + time_duration download_queue_time(int extra_bytes = 0) const; + + bool is_interesting() const { return m_interesting; } + bool is_choked() const override { return m_choked; } + + bool is_peer_interested() const { return m_peer_interested; } + bool has_peer_choked() const { return m_peer_choked; } + + void choke_this_peer(); + void maybe_unchoke_this_peer(); + + void update_interest(); + + void get_peer_info(peer_info& p) const override; + + // returns the torrent this connection is a part of + // may be zero if the connection is an incoming connection + // and it hasn't received enough information to determine + // which torrent it should be associated with + std::weak_ptr associated_torrent() const + { return m_torrent; } + + // get the info hash associated with this peer + // this will be a sha1 hash or truncated sha256 hash depending + // on which protocol version this connection is using + sha1_hash associated_info_hash() const; + + stat const& statistics() const override { return m_statistics; } + void add_stat(std::int64_t downloaded, std::int64_t uploaded) override; + void sent_bytes(int bytes_payload, int bytes_protocol); + void received_bytes(int bytes_payload, int bytes_protocol); + void trancieve_ip_packet(int bytes, bool ipv6); + void sent_syn(bool ipv6); + void received_synack(bool ipv6); + + // is called once every second by the main loop + void second_tick(int tick_interval_ms); + + aux::socket_type const& get_socket() const { return m_socket; } + aux::socket_type& get_socket() { return m_socket; } + tcp::endpoint const& remote() const override { return m_remote; } + tcp::endpoint local_endpoint() const override { return m_local; } + +#if TORRENT_USE_I2P + std::string const& destination() const override; + std::string const& local_i2p_endpoint() const override; +#endif + + typed_bitfield const& get_bitfield() const; + std::vector const& allowed_fast(); + std::vector const& suggested_pieces() const { return m_suggested_pieces; } + + time_point connected_time() const { return m_connect; } + time_point last_received() const { return m_last_receive.get(m_connect); } + + // this will cause this peer_connection to be disconnected. + void disconnect(error_code const& ec + , operation_t op, disconnect_severity_t = peer_connection_interface::normal) override; + + // called when a connect attempt fails (not when an + // established connection fails) + void connect_failed(error_code const& e); + bool is_disconnecting() const override { return m_disconnecting; } + + // this is called when the connection attempt has succeeded + // and the peer_connection is supposed to set m_connecting + // to false, and stop monitor writability + void on_connection_complete(error_code const& e); + + // returns true if this connection is still waiting to + // finish the connection attempt + bool is_connecting() const { return m_connecting; } + + // trust management. + virtual void received_valid_data(piece_index_t index); + // returns false if the peer should not be + // disconnected + virtual bool received_invalid_data(piece_index_t index, bool single_peer); + + // a connection is local if it was initiated by us. + // if it was an incoming connection, it is remote + bool is_outgoing() const final { return m_outgoing; } + + bool received_listen_port() const { return m_received_listen_port; } + void received_listen_port() + { m_received_listen_port = true; } + + bool on_local_network() const; + bool ignore_unchoke_slots() const; + + bool failed() const override { return m_failed; } + + int desired_queue_size() const + { + // this peer is in end-game mode we only want + // one outstanding request + return (m_endgame_mode || m_snubbed) ? 1 : m_desired_queue_size; + } + + // compares this connection against the given connection + // for which one is more eligible for an unchoke. + // returns true if this is more eligible + + int download_payload_rate() const { return m_statistics.download_payload_rate(); } + + // resets the byte counters that are used to measure + // the number of bytes transferred within unchoke cycles + void reset_choke_counters(); + + // if this peer connection is useless (neither party is + // interested in the other), disconnect it + // returns true if the connection was disconnected + bool disconnect_if_redundant(); + +#ifndef TORRENT_DISABLE_LOGGING + bool should_log(peer_log_alert::direction_t direction) const final; + void peer_log(peer_log_alert::direction_t direction + , char const* event, char const* fmt, ...) const noexcept final TORRENT_FORMAT(4,5); + void peer_log(peer_log_alert::direction_t direction + , char const* event) const noexcept; +#endif + + // the message handlers are called + // each time a recv() returns some new + // data, the last time it will be called + // is when the entire packet has been + // received, then it will no longer + // be called. i.e. most handlers need + // to check how much of the packet they + // have received before any processing + void incoming_keepalive(); + void incoming_choke(); + void incoming_unchoke(); + void incoming_interested(); + void incoming_not_interested(); + void incoming_have(piece_index_t piece_index); + void incoming_dont_have(piece_index_t piece_index); + void incoming_bitfield(typed_bitfield const& bits); + void incoming_request(peer_request const& r); + void incoming_piece(peer_request const& p, char const* data); + void incoming_piece_fragment(int bytes); + void start_receive_piece(peer_request const& r); + void incoming_cancel(peer_request const& r); + + bool can_disconnect(error_code const& ec) const; + void incoming_dht_port(int listen_port); + + void incoming_reject_request(peer_request const& r); + void incoming_have_all(); + void incoming_have_none(); + void incoming_allowed_fast(piece_index_t index); + void incoming_suggest(piece_index_t index); + + void set_has_metadata(bool m) { m_has_metadata = m; } + bool has_metadata() const { return m_has_metadata; } + + // the following functions appends messages + // to the send buffer + bool send_choke(); + bool send_unchoke(); + void send_interested(); + void send_not_interested(); + void send_suggest(piece_index_t piece); + void send_upload_only(bool enabled); + + void snub_peer(); + // reject any request in the request + // queue from this piece + void reject_piece(piece_index_t index); + + bool can_request_time_critical() const; + + // returns true if the specified block was actually made time-critical. + // if the block was already time-critical, it returns false. + bool make_time_critical(piece_block const& block); + + static constexpr request_flags_t time_critical = 0_bit; + static constexpr request_flags_t busy = 1_bit; + + // adds a block to the request queue + // returns true if successful, false otherwise + bool add_request(piece_block const& b, request_flags_t flags = {}); + + // clears the request queue and sends cancels for all messages + // in the download queue + void cancel_all_requests(); + + // removes a block from the request queue or download queue + // sends a cancel message if appropriate + // refills the request queue, and possibly ignoring pieces requested + // by peers in the ignore list (to avoid recursion) + // if force is true, the blocks is also freed from the piece + // picker, allowing another peer to request it immediately + void cancel_request(piece_block const& b, bool force = false); + void send_block_requests(); + void send_block_requests_impl(); + + void assign_bandwidth(int channel, int amount) override; + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + // is true until we can be sure that the other end + // speaks our protocol (be it bittorrent or http). + virtual bool in_handshake() const = 0; + + // returns the block currently being + // downloaded. And the progress of that + // block. If the peer isn't downloading + // a piece for the moment, implementers + // must return an object with the piece_index + // value invalid (the default constructor). + virtual piece_block_progress downloading_piece_progress() const; + + void send_buffer(span buf); + void setup_send(); + + template + void append_send_buffer(Holder buffer, int size) + { + TORRENT_ASSERT(is_single_thread()); + m_send_buffer.append_buffer(std::move(buffer), size); + } + + int outstanding_bytes() const { return m_outstanding_bytes; } + + int send_buffer_size() const + { return m_send_buffer.size(); } + + int send_buffer_capacity() const + { return m_send_buffer.capacity(); } + + void max_out_request_queue(int s); + int max_out_request_queue() const; + + std::time_t last_seen_complete() const { return m_last_seen_complete; } + void set_last_seen_complete(int ago) { m_last_seen_complete = aux::posix_time() - ago; } + + std::int64_t uploaded_in_last_round() const + { return m_statistics.total_payload_upload() - m_uploaded_at_last_round; } + + std::int64_t downloaded_in_last_round() const + { return m_statistics.total_payload_download() - m_downloaded_at_last_round; } + + std::int64_t uploaded_since_unchoked() const + { return m_statistics.total_payload_upload() - m_uploaded_at_last_unchoke; } + + // the time we last unchoked this peer + time_point time_of_last_unchoke() const + { return m_last_unchoke.get(m_connect); } + + // called when the disk write buffer is drained again, and we can + // start downloading payload again + void on_disk() override; + + int num_reading_bytes() const { return m_reading_bytes; } + + void setup_receive(); + + std::shared_ptr self() + { + TORRENT_ASSERT(!m_destructed); + TORRENT_ASSERT(m_in_use == 1337); + TORRENT_ASSERT(!m_in_constructor); + return shared_from_this(); + } + + counters& stats_counters() const { return m_counters; } + + int get_priority(int channel) const; + + protected: + + virtual void get_specific_peer_info(peer_info& p) const = 0; + + virtual void write_choke() = 0; + virtual void write_unchoke() = 0; + virtual void write_interested() = 0; + virtual void write_not_interested() = 0; + virtual void write_request(peer_request const& r) = 0; + virtual void write_cancel(peer_request const& r) = 0; + virtual void write_have(piece_index_t index) = 0; + virtual void write_dont_have(piece_index_t index) = 0; + virtual void write_keepalive() = 0; + virtual void write_piece(peer_request const& r, disk_buffer_holder buffer) = 0; + virtual void write_suggest(piece_index_t piece) = 0; + virtual void write_bitfield() = 0; + + virtual void write_reject_request(peer_request const& r) = 0; + virtual void write_allow_fast(piece_index_t piece) = 0; + virtual void write_upload_only(bool enabled) = 0; + + virtual void on_connected() = 0; + virtual void on_tick() {} + + // implemented by concrete connection classes + virtual void on_receive(error_code const& error + , std::size_t bytes_transferred) = 0; + virtual void on_sent(error_code const& error + , std::size_t bytes_transferred) = 0; + + void send_piece_suggestions(int num); + + virtual + std::tuple>> + hit_send_barrier(span> /* iovec */) + { + return std::make_tuple(INT_MAX + , span>()); + } + + void attach_to_torrent(info_hash_t const& ih); + + bool validate_piece_request(peer_request const& p) const; + + void update_desired_queue_size(); + + void set_send_barrier(int bytes) + { + TORRENT_ASSERT(bytes == INT_MAX || bytes <= send_buffer_size()); + m_send_barrier = bytes; + } + + int get_send_barrier() const { return m_send_barrier; } + + virtual int timeout() const; + + io_context& get_context() { return m_ios; } + + private: + + // callbacks for data being sent or received + void on_send_data(error_code const& error + , std::size_t bytes_transferred); + void on_receive_data(error_code const& error + , std::size_t bytes_transferred); + + void account_received_bytes(int bytes_transferred); + + void do_update_interest(); + void fill_send_buffer(); + void on_disk_read_complete(disk_buffer_holder buffer + , storage_error const& error, peer_request const&, time_point issue_time); + void on_disk_write_complete(storage_error const& error + , peer_request const&, std::shared_ptr); + void on_seed_mode_hashed(piece_index_t piece + , sha1_hash const& piece_hash, aux::vector const& block_hashes + , storage_error const& error); + + // this is for a future per-block request feature +#if 0 + void on_hash2_complete(storage_error const& error, peer_request const& r + , sha256_hash const& hash); +#endif + int request_timeout() const; + void check_graceful_pause(); + + int wanted_transfer(int channel); + int request_bandwidth(int channel, int bytes = 0); + + aux::socket_type m_socket; + + // the queue of blocks we have requested + // from this peer + aux::vector m_download_queue; + + // the queue of requests we have got + // from this peer that haven't been issued + // to the disk thread yet + aux::vector m_requests; + + // this peer's peer info struct. This may + // be 0, in case the connection is incoming + // and hasn't been added to a torrent yet. + torrent_peer* m_peer_info; + + // stats counters + counters& m_counters; + + // the number of pieces this peer + // has. Must be the same as + // std::count(m_have_piece.begin(), + // m_have_piece.end(), true) + int m_num_pieces; + + public: + // upload and download channel state + // enum from peer_info::bw_state + bandwidth_state_flags_t m_channel_state[2]; + + protected: + aux::receive_buffer m_recv_buffer; + + // number of bytes this peer can send and receive + int m_quota[2]; + + // the blocks we have reserved in the piece + // picker and will request from this peer. + std::vector m_request_queue; + + // this is the limit on the number of outstanding requests + // we have to this peer. This is initialized to the settings + // in the settings_pack. But it may be lowered + // if the peer is known to require a smaller limit (like BitComet). + // or if the extended handshake sets a limit. + // web seeds also has a limit on the queue size. + std::uint16_t m_max_out_request_queue; + + // this is the peer we're actually talking to + // it may not necessarily be the peer we're + // connected to, in case we use a proxy + tcp::endpoint m_remote; + + public: + aux::chained_buffer m_send_buffer; + private: + + // the disk thread to use to issue disk jobs to + disk_interface& m_disk_thread; + + // io service + io_context& m_ios; + + protected: +#ifndef TORRENT_DISABLE_EXTENSIONS + std::list> m_extensions; +#endif + private: + + // the average time between incoming pieces. Or, if there is no + // outstanding request, the time since the piece was requested. It + // is essentially an estimate of the time it will take to completely + // receive a payload message after it has been requested. + sliding_average m_request_time; + + // keep the io_context running as long as we + // have peer connections + executor_work_guard m_work; + + // the time when we last got a part of a + // piece packet from this peer + aux::relative_time m_last_piece; + + // the time we sent a request to + // this peer the last time + aux::relative_time m_last_request; + + // the time we received the last + // piece request from the peer + aux::relative_time m_last_incoming_request{aux::min_value}; + + // the time when we unchoked this peer + aux::relative_time m_last_unchoke; + + // if we're unchoked by this peer, this + // was the time + aux::relative_time m_last_unchoked; + + // the time we last choked this peer. min_time() in + // case we never unchoked it + aux::relative_time m_last_choke{aux::min_value}; + + // timeouts + aux::relative_time m_last_receive; + aux::relative_time m_last_sent; + + // the last time we filled our send buffer with payload + // this is used for timeouts + aux::relative_time m_last_sent_payload; + + // the time when the first entry in the request queue was requested. Used + // for request timeout. it doesn't necessarily represent the time when a + // specific request was made. Since requests can be handled out-of-order, + // it represents whichever request the other end decided to respond to. + // Once we get that response, we set it to the current time. + // for more information, see the blog post at: + // http://blog.libtorrent.org/2011/11/block-request-time-outs/ + aux::relative_time m_requested; + + // the time when this peer sent us a not_interested message + // the last time. + aux::relative_time m_became_uninterested; + + // the time when we sent a not_interested message to + // this peer the last time. + aux::relative_time m_became_uninteresting; + + // the time when async_connect was called + // or when the incoming connection was established + time_point m_connect = aux::time_now(); + + // the total payload download bytes + // at the last unchoke round. This is used to + // measure the number of bytes transferred during + // an unchoke cycle, to unchoke peers the more bytes + // they sent us + std::int64_t m_downloaded_at_last_round = 0; + std::int64_t m_uploaded_at_last_round = 0; + + // this is the number of bytes we had uploaded the + // last time this peer was unchoked. This does not + // reset each unchoke interval/round. This is used to + // track upload across rounds, for the full duration of + // the peer being unchoked. Specifically, it's used + // for the round-robin unchoke algorithm. + std::int64_t m_uploaded_at_last_unchoke = 0; + + // the number of payload bytes downloaded last second tick + std::int32_t m_downloaded_last_second = 0; + + // the number of payload bytes uploaded last second tick + std::int32_t m_uploaded_last_second = 0; + + // the number of bytes that the other + // end has to send us in order to respond + // to all outstanding piece requests we + // have sent to it + int m_outstanding_bytes = 0; + + aux::handler_storage m_read_handler_storage; + aux::handler_storage m_write_handler_storage; + + // these are pieces we have recently sent suggests for to this peer. + // it just serves as a queue to remember what we've sent, to avoid + // re-sending suggests for the same piece + // i.e. outgoing suggest pieces + aux::vector m_suggest_pieces; + + // the pieces we will send to the peer + // if requested (regardless of choke state) + std::vector m_accept_fast; + + // a sent-piece counter for the allowed fast set + // to avoid exploitation. Each slot is a counter + // for one of the pieces from the allowed-fast set + aux::vector m_accept_fast_piece_cnt; + + // the pieces the peer will send us if + // requested (regardless of choke state) + std::vector m_allowed_fast; + + // pieces that has been suggested to be downloaded from this peer + // i.e. incoming suggestions + // TODO: 2 this should really be a circular buffer + aux::vector m_suggested_pieces; + + // the time when this peer last saw a complete copy + // of this torrent + time_t m_last_seen_complete = 0; + + // the block we're currently receiving. Or + // (-1, -1) if we're not receiving one + piece_block m_receiving_block = piece_block::invalid; + + // the local endpoint for this peer, i.e. our address + // and our port. If this is set for outgoing connections + // before the connection completes, it means we want to + // force the connection to be bound to the specified interface. + // if it ends up being bound to a different local IP, the connection + // is closed. + tcp::endpoint m_local; + + // remote peer's id + peer_id m_peer_id; + + protected: + + template + void wrap(Fun f, Args&&... a); + + // statistics about upload and download speeds + // and total amount of uploads and downloads for + // this peer + // TODO: factor this out into its own class with a virtual interface + // torrent and session should implement this interface + stat m_statistics; + + // the number of outstanding bytes expected + // to be received by extensions + int m_extension_outstanding_bytes = 0; + + // the number of time critical requests + // queued up in the m_request_queue that + // soon will be committed to the download + // queue. This is included in download_queue_time() + // so that it can be used while adding more + // requests and take the previous requests + // into account without submitting it all + // immediately + std::uint16_t m_queued_time_critical = 0; + + // the number of bytes we are currently reading + // from disk, that will be added to the send + // buffer as soon as they complete + int m_reading_bytes = 0; + + // options used for the piece picker. These flags will + // be augmented with flags controlled by other settings + // like sequential download etc. These are here to + // let plugins control flags that should always be set + picker_options_t m_picker_options{}; + + // the number of invalid piece-requests + // we have got from this peer. If the request + // queue gets empty, and there have been + // invalid requests, we can assume the + // peer is waiting for those pieces. + // we can then clear its download queue + // by sending choke, unchoke. + std::uint16_t m_num_invalid_requests = 0; + +#ifndef TORRENT_DISABLE_SUPERSEEDING + // if [0] is -1, super-seeding is not active. If it is >= 0 + // this is the piece that is available to this peer. Only + // these two pieces can be downloaded from us by this peer. + // This will remain the current piece for this peer until + // another peer sends us a have message for this piece + std::array m_superseed_piece = {{piece_index_t(-1), piece_index_t(-1)}}; +#endif + + // the number of bytes send to the disk-io + // thread that hasn't yet been completely written. + int m_outstanding_writing_bytes = 0; + + // max transfer rates seen on this peer + int m_download_rate_peak = 0; + int m_upload_rate_peak = 0; + + // stop sending data after this many bytes, INT_MAX = inf + int m_send_barrier = INT_MAX; + + // the number of request we should queue up + // at the remote end. + // TODO: 2 rename this target queue size + std::uint16_t m_desired_queue_size = 4; + + // if set to non-zero, this peer will always prefer + // to request entire n pieces, rather than blocks. + // where n is the value of this variable. + // if it is 0, the download rate limit setting + // will be used to determine if whole pieces + // are preferred. + std::uint16_t m_prefer_contiguous_blocks = 0; + + // this is the number of times this peer has had + // a request rejected because of a disk I/O failure. + // once this reaches a certain threshold, the + // peer is disconnected in order to avoid infinite + // loops of consistent failures + std::uint8_t m_disk_read_failures = 0; + + // this is used in seed mode whenever we trigger a hash check + // for a piece, before we read it. It's used to throttle + // the hash checks to just a few per peer at a time. + std::uint8_t m_outstanding_piece_verification:3; + + // is true if it was we that connected to the peer + // and false if we got an incoming connection + // could be considered: true = local, false = remote + bool m_outgoing:1; + + // is true if we learn the incoming connections listening + // during the extended handshake + bool m_received_listen_port:1; + + // if this is true, the disconnection + // timestamp is not updated when the connection + // is closed. This means the time until we can + // reconnect to this peer is shorter, and likely + // immediate. + bool m_fast_reconnect:1; + + // this is set to true if the connection timed + // out or closed the connection. In that + // case we will not try to reconnect to + // this peer + bool m_failed:1; + + // this is set to true if the connection attempt + // succeeded. i.e. the TCP 3-way handshake + bool m_connected:1; + + // if this is true, the blocks picked by the piece + // picker will be merged before passed to the + // request function. i.e. subsequent blocks are + // merged into larger blocks. This is used by + // the http-downloader, to request whole pieces + // at a time. + bool m_request_large_blocks:1; + +#ifndef TORRENT_DISABLE_SHARE_MODE + // set to true if this peer is in share mode + bool m_share_mode:1; +#endif + + // set to true when this peer has told us explicitly that it is only + // uploading. A seed is *implicitly* upload only, so this is not + // necessarily true. + bool m_upload_only:1; + + // this is set to true once the bitfield is received + bool m_bitfield_received:1; + + // if this is set to true, the client will not + // pick any pieces from this peer + bool m_no_download:1; + + // indicates that we want to request more blocks from this peer + bool m_deferred_send_block_requests:1; + + // set to true while we're trying to holepunch + bool m_holepunch_mode:1; + + // the other side has told us that it won't send anymore + // data to us for a while + bool m_peer_choked:1; + + // this is set to true when a have_all + // message is received. This information + // is used to fill the bitmask in init() + bool m_have_all:1; + + // other side says that it's interested in downloading + // from us. + bool m_peer_interested:1; + + // set to true when we should recalculate interest + // for this peer. Since this is a fairly expensive + // operation, it's delayed until the second_tick is + // fired, so that multiple events that wants to recalc + // interest are coalesced into only triggering it once + // the actual computation is done in do_update_interest(). + bool m_need_interest_update:1; + + // set to true if this peer has metadata, and false + // otherwise. + bool m_has_metadata:1; + + // this is set to true if this peer was accepted exceeding + // the connection limit. It means it has to disconnect + // itself, or some other peer, as soon as it's completed + // the handshake. We need to wait for the handshake in + // order to know which torrent it belongs to, to know which + // other peers to compare it to. + bool m_exceeded_limit:1; + + // this is slow-start at the bittorrent layer. It affects how we increase + // desired queue size (i.e. the number of outstanding requests we keep). + // While the underlying transport protocol is in slow-start, the number of + // outstanding requests need to increase at the same pace to keep up. + bool m_slow_start:1; + +#if TORRENT_USE_ASSERTS + public: + bool m_in_constructor = true; + bool m_disconnect_started = false; + bool m_initialized = false; + int m_in_use = 1337; + int m_received_in_piece = 0; + bool m_destructed = false; + // this is true while there is an outstanding + // async write job on the socket + bool m_socket_is_writing = false; + bool is_single_thread() const; +#endif + }; + + struct cork + { + explicit cork(peer_connection& p): m_pc(p) + { + if (m_pc.m_channel_state[peer_connection::upload_channel] & peer_info::bw_network) + return; + + // pretend that there's an outstanding send operation already, to + // prevent future calls to setup_send() from actually causing an + // async_send() to be issued. + m_pc.m_channel_state[peer_connection::upload_channel] |= peer_info::bw_network; + m_need_uncork = true; + } + cork(cork const&) = delete; + cork& operator=(cork const&) = delete; + + ~cork() + { + if (!m_need_uncork) return; + try { + m_pc.m_channel_state[peer_connection::upload_channel] &= ~peer_info::bw_network; + m_pc.setup_send(); + } + catch (std::bad_alloc const&) { + m_pc.disconnect(make_error_code(boost::system::errc::not_enough_memory) + , operation_t::sock_write); + } + catch (boost::system::system_error const& err) { + m_pc.disconnect(err.code(), operation_t::sock_write); + } + catch (...) { + m_pc.disconnect(make_error_code(boost::system::errc::not_enough_memory) + , operation_t::sock_write); + } + } + private: + peer_connection& m_pc; + bool m_need_uncork = false; + }; + +} + +#endif // TORRENT_PEER_CONNECTION_HPP_INCLUDED diff --git a/docs/include/libtorrent/peer_connection_handle.hpp b/docs/include/libtorrent/peer_connection_handle.hpp new file mode 100644 index 0000000..e0c75b7 --- /dev/null +++ b/docs/include/libtorrent/peer_connection_handle.hpp @@ -0,0 +1,158 @@ +/* + +Copyright (c) 2015, 2017, Steven Siloti +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2016-2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CONNECTION_HANDLE_HPP_INCLUDED +#define TORRENT_PEER_CONNECTION_HANDLE_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/operations.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/peer_connection.hpp" // for connection_type +#include "libtorrent/error_code.hpp" + +namespace libtorrent { + +struct bt_peer_connection; + +// the peer_connection_handle class provides a handle to the internal peer +// connection object, to be used by plugins. This is a low level interface that +// may not be stable across libtorrent versions +struct TORRENT_EXPORT peer_connection_handle +{ + explicit peer_connection_handle(std::weak_ptr impl) + : m_connection(std::move(impl)) + {} + + connection_type type() const; + + void add_extension(std::shared_ptr); + peer_plugin const* find_plugin(string_view type) const; + + bool is_seed() const; + + bool upload_only() const; + + peer_id const& pid() const; + bool has_piece(piece_index_t i) const; + + bool is_interesting() const; + bool is_choked() const; + + bool is_peer_interested() const; + bool has_peer_choked() const; + + void choke_this_peer(); + void maybe_unchoke_this_peer(); + + void get_peer_info(peer_info& p) const; + + torrent_handle associated_torrent() const; + + tcp::endpoint const& remote() const; + tcp::endpoint local_endpoint() const; + + void disconnect(error_code const& ec, operation_t op + , disconnect_severity_t = peer_connection_interface::normal); + bool is_disconnecting() const; + bool is_connecting() const; + bool is_outgoing() const; + + bool on_local_network() const; + bool ignore_unchoke_slots() const; + + bool failed() const; + + bool should_log(peer_log_alert::direction_t direction) const; + void peer_log(peer_log_alert::direction_t direction + , char const* event, char const* fmt = "", ...) const TORRENT_FORMAT(4,5); + + bool can_disconnect(error_code const& ec) const; + + bool has_metadata() const; + + bool in_handshake() const; + + void send_buffer(char const* begin, int size); + + std::time_t last_seen_complete() const; + time_point time_of_last_unchoke() const; + + bool operator==(peer_connection_handle const& o) const + { return !lt(m_connection, o.m_connection) && !lt(o.m_connection, m_connection); } + bool operator!=(peer_connection_handle const& o) const + { return lt(m_connection, o.m_connection) || lt(o.m_connection, m_connection); } + bool operator<(peer_connection_handle const& o) const + { return lt(m_connection, o.m_connection); } + + std::shared_ptr native_handle() const + { + return m_connection.lock(); + } + +private: + std::weak_ptr m_connection; + + // copied from boost::weak_ptr + bool lt(std::weak_ptr const& a + , std::weak_ptr const& b) const + { + return a.owner_before(b); + } +}; + +// The bt_peer_connection_handle provides a handle to the internal bittorrent +// peer connection object to plugins. It's low level and may not be a stable API +// across libtorrent versions. +struct TORRENT_EXPORT bt_peer_connection_handle : peer_connection_handle +{ + explicit bt_peer_connection_handle(peer_connection_handle pc) + : peer_connection_handle(std::move(pc)) + {} + + bool packet_finished() const; + bool support_extensions() const; + + bool supports_encryption() const; + + void switch_send_crypto(std::shared_ptr crypto); + void switch_recv_crypto(std::shared_ptr crypto); + + std::shared_ptr native_handle() const; +}; + +} // namespace libtorrent + +#endif // TORRENT_PEER_CONNECTION_HANDLE_HPP_INCLUDED diff --git a/docs/include/libtorrent/peer_connection_interface.hpp b/docs/include/libtorrent/peer_connection_interface.hpp new file mode 100644 index 0000000..ef8b0fe --- /dev/null +++ b/docs/include/libtorrent/peer_connection_interface.hpp @@ -0,0 +1,88 @@ +/* + +Copyright (c) 2014-2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CONNECTION_INTERFACE_HPP +#define TORRENT_PEER_CONNECTION_INTERFACE_HPP + +#include "libtorrent/fwd.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/operations.hpp" // for operation_t enum +#include "libtorrent/units.hpp" + +namespace libtorrent { + + struct torrent_peer; + class stat; + + using disconnect_severity_t = aux::strong_typedef; + + // TODO: make this interface smaller! + struct TORRENT_EXTRA_EXPORT peer_connection_interface + { + static constexpr disconnect_severity_t normal{0}; + static constexpr disconnect_severity_t failure{1}; + static constexpr disconnect_severity_t peer_error{2}; + +#if TORRENT_USE_I2P + virtual std::string const& destination() const = 0; + virtual std::string const& local_i2p_endpoint() const = 0; +#endif + virtual tcp::endpoint const& remote() const = 0; + virtual tcp::endpoint local_endpoint() const = 0; + virtual void disconnect(error_code const& ec + , operation_t op, disconnect_severity_t = peer_connection_interface::normal) = 0; + virtual peer_id const& pid() const = 0; + virtual peer_id our_pid() const = 0; + virtual void set_holepunch_mode() = 0; + virtual torrent_peer* peer_info_struct() const = 0; + virtual void set_peer_info(torrent_peer* pi) = 0; + virtual bool is_outgoing() const = 0; + virtual void add_stat(std::int64_t downloaded, std::int64_t uploaded) = 0; + virtual bool fast_reconnect() const = 0; + virtual bool is_choked() const = 0; + virtual bool failed() const = 0; + virtual stat const& statistics() const = 0; + virtual void get_peer_info(peer_info& p) const = 0; +#ifndef TORRENT_DISABLE_LOGGING + virtual bool should_log(peer_log_alert::direction_t direction) const = 0; + virtual void peer_log(peer_log_alert::direction_t direction + , char const* event, char const* fmt = "", ...) const noexcept TORRENT_FORMAT(4,5) = 0; +#endif + protected: + ~peer_connection_interface() {} + }; +} + +#endif diff --git a/docs/include/libtorrent/peer_id.hpp b/docs/include/libtorrent/peer_id.hpp new file mode 100644 index 0000000..0f3a480 --- /dev/null +++ b/docs/include/libtorrent/peer_id.hpp @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2003, 2009, 2013, 2016-2017, 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_ID_HPP_INCLUDED +#define TORRENT_PEER_ID_HPP_INCLUDED + +#include "libtorrent/sha1_hash.hpp" + +namespace libtorrent { + + using peer_id = sha1_hash; +} + +#endif // TORRENT_PEER_ID_HPP_INCLUDED diff --git a/docs/include/libtorrent/peer_info.hpp b/docs/include/libtorrent/peer_info.hpp new file mode 100644 index 0000000..451b099 --- /dev/null +++ b/docs/include/libtorrent/peer_info.hpp @@ -0,0 +1,481 @@ +/* + +Copyright (c) 2003-2011, 2013-2021, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016, Alden Torres +Copyright (c) 2019, Amir Abrams +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_INFO_HPP_INCLUDED +#define TORRENT_PEER_INFO_HPP_INCLUDED + +#include "libtorrent/socket.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/aux_/deprecated.hpp" + +namespace libtorrent { + + // flags for the peer_info::flags field. Indicates various states + // the peer may be in. These flags are not mutually exclusive, but + // not every combination of them makes sense either. + using peer_flags_t = flags::bitfield_flag; + + // the flags indicating which sources a peer can + // have come from. A peer may have been seen from + // multiple sources + using peer_source_flags_t = flags::bitfield_flag; + + // flags indicating what is blocking network transfers in up- and down + // direction + using bandwidth_state_flags_t = flags::bitfield_flag; + + using connection_type_t = flags::bitfield_flag; + +TORRENT_VERSION_NAMESPACE_2 + + // holds information and statistics about one peer + // that libtorrent is connected to + struct TORRENT_EXPORT peer_info + { + // hidden + peer_info(); + ~peer_info(); + peer_info(peer_info const&); + peer_info(peer_info&&); + peer_info& operator=(peer_info const&); + + // A human readable string describing the software at the other end of + // the connection. In some cases this information is not available, then + // it will contain a string that may give away something about which + // software is running in the other end. In the case of a web seed, the + // server type and version will be a part of this string. This is UTF-8 + // encoded. + std::string client; + + // a bitfield, with one bit per piece in the torrent. Each bit tells you + // if the peer has that piece (if it's set to 1) or if the peer miss that + // piece (set to 0). + typed_bitfield pieces; + + // the total number of bytes downloaded from and uploaded to this peer. + // These numbers do not include the protocol chatter, but only the + // payload data. + std::int64_t total_download; + std::int64_t total_upload; + + // the time since we last sent a request to this peer and since any + // transfer occurred with this peer + time_duration last_request; + time_duration last_active; + + // the time until all blocks in the request queue will be downloaded + time_duration download_queue_time; + +#if TORRENT_ABI_VERSION == 1 + using peer_flags_t = libtorrent::peer_flags_t; + using peer_source_flags = libtorrent::peer_source_flags_t; +#endif + + // **we** are interested in pieces from this peer. + static constexpr peer_flags_t interesting = 0_bit; + + // **we** have choked this peer. + static constexpr peer_flags_t choked = 1_bit; + + // the peer is interested in **us** + static constexpr peer_flags_t remote_interested = 2_bit; + + // the peer has choked **us**. + static constexpr peer_flags_t remote_choked = 3_bit; + + // means that this peer supports the + // `extension protocol`__. + // + // __ extension_protocol.html + static constexpr peer_flags_t supports_extensions = 4_bit; + + // The connection was initiated by us, the peer has a + // listen port open, and that port is the same as in the + // address of this peer. If this flag is not set, this + // peer connection was opened by this peer connecting to + // us. + static constexpr peer_flags_t outgoing_connection = 5_bit; + + // deprecated synonym for outgoing_connection + static constexpr peer_flags_t local_connection = 5_bit; + + // The connection is opened, and waiting for the + // handshake. Until the handshake is done, the peer + // cannot be identified. + static constexpr peer_flags_t handshake = 6_bit; + + // The connection is in a half-open state (i.e. it is + // being connected). + static constexpr peer_flags_t connecting = 7_bit; + +#if TORRENT_ABI_VERSION == 1 + // The connection is currently queued for a connection + // attempt. This may happen if there is a limit set on + // the number of half-open TCP connections. + TORRENT_DEPRECATED static constexpr peer_flags_t queued = 8_bit; +#endif + + // The peer has participated in a piece that failed the + // hash check, and is now "on parole", which means we're + // only requesting whole pieces from this peer until + // it either fails that piece or proves that it doesn't + // send bad data. + static constexpr peer_flags_t on_parole = 9_bit; + + // This peer is a seed (it has all the pieces). + static constexpr peer_flags_t seed = 10_bit; + + // This peer is subject to an optimistic unchoke. It has + // been unchoked for a while to see if it might unchoke + // us in return an earn an upload/unchoke slot. If it + // doesn't within some period of time, it will be choked + // and another peer will be optimistically unchoked. + static constexpr peer_flags_t optimistic_unchoke = 11_bit; + + // This peer has recently failed to send a block within + // the request timeout from when the request was sent. + // We're currently picking one block at a time from this + // peer. + static constexpr peer_flags_t snubbed = 12_bit; + + // This peer has either explicitly (with an extension) + // or implicitly (by becoming a seed) told us that it + // will not downloading anything more, regardless of + // which pieces we have. + static constexpr peer_flags_t upload_only = 13_bit; + + // This means the last time this peer picket a piece, + // it could not pick as many as it wanted because there + // were not enough free ones. i.e. all pieces this peer + // has were already requested from other peers. + static constexpr peer_flags_t endgame_mode = 14_bit; + + // This flag is set if the peer was in holepunch mode + // when the connection succeeded. This typically only + // happens if both peers are behind a NAT and the peers + // connect via the NAT holepunch mechanism. + static constexpr peer_flags_t holepunched = 15_bit; + + // indicates that this socket is running on top of the + // I2P transport. + static constexpr peer_flags_t i2p_socket = 16_bit; + + // indicates that this socket is a uTP socket + static constexpr peer_flags_t utp_socket = 17_bit; + + // indicates that this socket is running on top of an SSL + // (TLS) channel + static constexpr peer_flags_t ssl_socket = 18_bit; + + // this connection is obfuscated with RC4 + static constexpr peer_flags_t rc4_encrypted = 19_bit; + + // the handshake of this connection was obfuscated + // with a Diffie-Hellman exchange + static constexpr peer_flags_t plaintext_encrypted = 20_bit; + + // tells you in which state the peer is in. It is set to + // any combination of the peer_flags_t flags above. + peer_flags_t flags; + + // The peer was received from the tracker. + static constexpr peer_source_flags_t tracker = 0_bit; + + // The peer was received from the kademlia DHT. + static constexpr peer_source_flags_t dht = 1_bit; + + // The peer was received from the peer exchange + // extension. + static constexpr peer_source_flags_t pex = 2_bit; + + // The peer was received from the local service + // discovery (The peer is on the local network). + static constexpr peer_source_flags_t lsd = 3_bit; + + // The peer was added from the fast resume data. + static constexpr peer_source_flags_t resume_data = 4_bit; + + // we received an incoming connection from this peer + static constexpr peer_source_flags_t incoming = 5_bit; + + // a combination of flags describing from which sources this peer + // was received. A combination of the peer_source_flags_t above. + peer_source_flags_t source; + + // the current upload and download speed we have to and from this peer + // (including any protocol messages). updated about once per second + int up_speed; + int down_speed; + + // The transfer rates of payload data only updated about once per second + int payload_up_speed; + int payload_down_speed; + + // the peer's id as used in the bittorrent protocol. This id can be used + // to extract 'fingerprints' from the peer. Sometimes it can tell you + // which client the peer is using. See identify_client()_ + peer_id pid; + + // the number of bytes we have requested from this peer, but not yet + // received. + int queue_bytes; + + // the number of seconds until the current front piece request will time + // out. This timeout can be adjusted through + // ``settings_pack::request_timeout``. + // -1 means that there is not outstanding request. + int request_timeout; + + // the number of bytes allocated + // and used for the peer's send buffer, respectively. + int send_buffer_size; + int used_send_buffer; + + // the number of bytes + // allocated and used as receive buffer, respectively. + int receive_buffer_size; + int used_receive_buffer; + int receive_buffer_watermark; + + // the number of pieces this peer has participated in sending us that + // turned out to fail the hash check. + int num_hashfails; + + // this is the number of requests we have sent to this peer that we + // haven't got a response for yet + int download_queue_length; + + // the number of block requests that have timed out, and are still in the + // download queue + int timed_out_requests; + + // the number of busy requests in the download queue. A busy request is a + // request for a block we've also requested from a different peer + int busy_requests; + + // the number of requests messages that are currently in the send buffer + // waiting to be sent. + int requests_in_buffer; + + // the number of requests that is tried to be maintained (this is + // typically a function of download speed) + int target_dl_queue_length; + + // the number of piece-requests we have received from this peer + // that we haven't answered with a piece yet. + int upload_queue_length; + + // the number of times this peer has "failed". i.e. failed to connect or + // disconnected us. The failcount is decremented when we see this peer in + // a tracker response or peer exchange message. + int failcount; + + // You can know which piece, and which part of that piece, that is + // currently being downloaded from a specific peer by looking at these + // four members. ``downloading_piece_index`` is the index of the piece + // that is currently being downloaded. This may be set to -1 if there's + // currently no piece downloading from this peer. If it is >= 0, the + // other three members are valid. ``downloading_block_index`` is the + // index of the block (or sub-piece) that is being downloaded. + // ``downloading_progress`` is the number of bytes of this block we have + // received from the peer, and ``downloading_total`` is the total number + // of bytes in this block. + piece_index_t downloading_piece_index; + int downloading_block_index; + int downloading_progress; + int downloading_total; + +#if TORRENT_ABI_VERSION <= 2 + using connection_type_t = libtorrent::connection_type_t; +#endif + // Regular bittorrent connection + static constexpr connection_type_t standard_bittorrent = 0_bit; + + // HTTP connection using the `BEP 19`_ protocol + static constexpr connection_type_t web_seed = 1_bit; + + // HTTP connection using the `BEP 17`_ protocol + static constexpr connection_type_t http_seed = 2_bit; + + // the kind of connection this peer uses. See connection_type_t. + connection_type_t connection_type; + +#if TORRENT_ABI_VERSION == 1 + // an estimate of the rate this peer is downloading at, in + // bytes per second. + TORRENT_DEPRECATED int remote_dl_rate; +#endif + + // the number of bytes this peer has pending in the disk-io thread. + // Downloaded and waiting to be written to disk. This is what is capped + // by ``settings_pack::max_queued_disk_bytes``. + int pending_disk_bytes; + + // number of outstanding bytes to read + // from disk + int pending_disk_read_bytes; + + // the number of bytes this peer has been assigned to be allowed to send + // and receive until it has to request more quota from the bandwidth + // manager. + int send_quota; + int receive_quota; + + // an estimated round trip time to this peer, in milliseconds. It is + // estimated by timing the TCP ``connect()``. It may be 0 for + // incoming connections. + int rtt; + + // the number of pieces this peer has. + int num_pieces; + + // the highest download and upload rates seen on this connection. They + // are given in bytes per second. This number is reset to 0 on reconnect. + int download_rate_peak; + int upload_rate_peak; + + // the progress of the peer in the range [0, 1]. This is always 0 when + // floating point operations are disabled, instead use ``progress_ppm``. + float progress; // [0, 1] + + // indicates the download progress of the peer in the range [0, 1000000] + // (parts per million). + int progress_ppm; + +#if TORRENT_ABI_VERSION == 1 + // this is an estimation of the upload rate, to this peer, where it will + // unchoke us. This is a coarse estimation based on the rate at which + // we sent right before we were choked. This is primarily used for the + // bittyrant choking algorithm. + TORRENT_DEPRECATED int estimated_reciprocation_rate; +#endif + + // the IP-address to this peer. The type is an asio endpoint. For + // more info, see the asio_ documentation. This field is not valid for + // i2p peers. Instead use the i2p_destination() function. + // + // .. _asio: http://asio.sourceforge.net/asio-0.3.8/doc/asio/reference.html + tcp::endpoint ip; + + // the IP and port pair the socket is bound to locally. i.e. the IP + // address of the interface it's going out over. This may be useful for + // multi-homed clients with multiple interfaces to the internet. + // This field is not valid for i2p peers. + tcp::endpoint local_endpoint; + + // The peer is not waiting for any external events to + // send or receive data. + static constexpr bandwidth_state_flags_t bw_idle = 0_bit; + + // The peer is waiting for the rate limiter. + static constexpr bandwidth_state_flags_t bw_limit = 1_bit; + + // The peer has quota and is currently waiting for a + // network read or write operation to complete. This is + // the state all peers are in if there are no bandwidth + // limits. + static constexpr bandwidth_state_flags_t bw_network = 2_bit; + + // The peer is waiting for the disk I/O thread to catch + // up writing buffers to disk before downloading more. + static constexpr bandwidth_state_flags_t bw_disk = 4_bit; + + // bitmasks indicating what state this peer + // is in with regards to sending and receiving data. The states are + // defined as independent flags of type bandwidth_state_flags_t, in this + // class. + bandwidth_state_flags_t read_state; + bandwidth_state_flags_t write_state; + +#if TORRENT_USE_I2P + // If this peer is an i2p peer, this function returns the destination + // address of the peer + sha256_hash i2p_destination() const; + + // internal + void set_i2p_destination(sha256_hash dest); +#endif + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED static constexpr bandwidth_state_flags_t bw_torrent = bw_limit; + TORRENT_DEPRECATED static constexpr bandwidth_state_flags_t bw_global = bw_limit; + + // the number of bytes per second we are allowed to send to or receive + // from this peer. It may be -1 if there's no local limit on the peer. + // The global limit and the torrent limit may also be enforced. + TORRENT_DEPRECATED int upload_limit; + TORRENT_DEPRECATED int download_limit; + + // a measurement of the balancing of free download (that we get) and free + // upload that we give. Every peer gets a certain amount of free upload, + // but this member says how much *extra* free upload this peer has got. + // If it is a negative number it means that this was a peer from which we + // have got this amount of free download. + TORRENT_DEPRECATED std::int64_t load_balancing; +#endif + }; + +TORRENT_VERSION_NAMESPACE_2_END + +#if TORRENT_ABI_VERSION == 1 + // internal + struct TORRENT_EXTRA_EXPORT peer_list_entry + { + // internal + enum flags_t + { + banned = 1 + }; + + // internal + tcp::endpoint ip; + // internal + int flags; + // internal + std::uint8_t failcount; + // internal + std::uint8_t source; + }; +#endif + +} + +#endif // TORRENT_PEER_INFO_HPP_INCLUDED diff --git a/docs/include/libtorrent/peer_list.hpp b/docs/include/libtorrent/peer_list.hpp new file mode 100644 index 0000000..502919b --- /dev/null +++ b/docs/include/libtorrent/peer_list.hpp @@ -0,0 +1,271 @@ +/* + +Copyright (c) 2003-2005, 2007-2009, 2011-2012, 2014-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2009, Daniel Wallin +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2016, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_POLICY_HPP_INCLUDED +#define TORRENT_POLICY_HPP_INCLUDED + +#include + +#include "libtorrent/fwd.hpp" +#include "libtorrent/string_util.hpp" // for allocate_string_copy +#include "libtorrent/request_blocks.hpp" // for source_rank + +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/aux_/invariant_check.hpp" +#include "libtorrent/ip_voter.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/peer_connection_interface.hpp" +#include "libtorrent/aux_/deque.hpp" +#include "libtorrent/peer_info.hpp" // for peer_source_flags_t +#include "libtorrent/string_view.hpp" +#include "libtorrent/pex_flags.hpp" + +namespace libtorrent { + + struct torrent_peer_allocator_interface; + + // this object is used to communicate torrent state and + // some configuration to the peer_list object. This make + // the peer_list type not depend on the torrent type directly. + struct torrent_state + { + bool is_finished = false; + bool allow_multiple_connections_per_ip = false; + + // this is set by peer_list::add_peer to either true or false + // true means the peer we just added was new, false means + // we already knew about the peer + bool first_time_seen = false; + + int max_peerlist_size = 1000; + int min_reconnect_time = 60; + + // the number of iterations over the peer list for this operation + int loop_counter = 0; + + // these are used only by find_connect_candidates in order + // to implement peer ranking. See: + // http://blog.libtorrent.org/2012/12/swarm-connectivity/ + external_ip ip; + int port = 0; + + // the number of times a peer must fail before it's no longer considered + // a connect candidate + int max_failcount = 3; + + // if any peer were removed during this call, they are returned in + // this vector. The caller would want to make sure there are no + // references to these torrent_peers anywhere + std::vector erased; + }; + + struct erase_peer_flags_tag; + using erase_peer_flags_t = flags::bitfield_flag; + + struct TORRENT_EXTRA_EXPORT peer_list : single_threaded + { + explicit peer_list(torrent_peer_allocator_interface& alloc); + ~peer_list(); + + void clear(); + + // not copyable + peer_list(peer_list const&) = delete; + peer_list& operator=(peer_list const&) = delete; + +#if TORRENT_USE_I2P + torrent_peer* add_i2p_peer(string_view destination + , peer_source_flags_t src, pex_flags_t flags + , torrent_state* state); +#endif + + // this is called once for every torrent_peer we get from + // the tracker, pex, lsd or dht. + torrent_peer* add_peer(tcp::endpoint const& remote + , peer_source_flags_t, pex_flags_t, torrent_state*); + + // false means duplicate connection + bool update_peer_port(int port, torrent_peer* p + , peer_source_flags_t src + , torrent_state* state); + + // called when an incoming connection is accepted + // false means the connection was refused or failed + bool new_connection(peer_connection_interface& c, int session_time, torrent_state* state); + + // the given connection was just closed + void connection_closed(const peer_connection_interface& c + , int session_time, torrent_state* state); + + bool ban_peer(torrent_peer* p); + void set_connection(torrent_peer* p, peer_connection_interface* c); + void set_failcount(torrent_peer* p, int f); + void inc_failcount(torrent_peer* p); + + void apply_ip_filter(ip_filter const& filter, torrent_state* state + , std::vector
    & banned); + void apply_port_filter(port_filter const& filter, torrent_state* state + , std::vector& banned); + + void set_seed(torrent_peer* p, bool s); + + // this clears all cached peer priorities. It's called when + // our external IP changes + void clear_peer_prio(); + +#if TORRENT_USE_ASSERTS + bool has_connection(const peer_connection_interface* p); +#endif +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + int num_peers() const { return int(m_peers.size()); } + int num_candidate_cache() const { return int(m_candidate_cache.size()); } + + using peers_t = aux::deque; + using iterator = peers_t::iterator; + using const_iterator = peers_t::const_iterator; + iterator begin() { return m_peers.begin(); } + iterator end() { return m_peers.end(); } + const_iterator begin() const { return m_peers.begin(); } + const_iterator end() const { return m_peers.end(); } + + std::pair find_peers(address const& a) + { +#if TORRENT_USE_I2P + if (a == address()) + return std::pair(m_peers.end(), m_peers.end()); +#endif + return std::equal_range( + m_peers.begin(), m_peers.end(), a, peer_address_compare()); + } + + std::pair find_peers(address const& a) const + { + return std::equal_range( + m_peers.begin(), m_peers.end(), a, peer_address_compare()); + } + + torrent_peer* connect_one_peer(int session_time, torrent_state* state); + + bool has_peer(torrent_peer const* p) const; + + int num_seeds() const { return int(m_num_seeds); } + int num_connect_candidates() const { return m_num_connect_candidates; } + + void erase_peer(torrent_peer* p, torrent_state* state); + void erase_peer(iterator i, torrent_state* state); + + void set_max_failcount(torrent_state* st); + + private: + + void recalculate_connect_candidates(torrent_state* state); + + void update_connect_candidates(int delta); + + void update_peer(torrent_peer* p, peer_source_flags_t src + , pex_flags_t flags, tcp::endpoint const& remote); + bool insert_peer(torrent_peer* p, iterator iter + , pex_flags_t flags, torrent_state* state); + + void find_connect_candidates(std::vector& peers + , int session_time, torrent_state* state); + + bool is_connect_candidate(torrent_peer const& p) const; + bool is_erase_candidate(torrent_peer const& p) const; + bool is_force_erase_candidate(torrent_peer const& pe) const; + bool should_erase_immediately(torrent_peer const& p) const; + + static constexpr erase_peer_flags_t force_erase = 1_bit; + void erase_peers(torrent_state* state, erase_peer_flags_t flags = {}); + + peers_t m_peers; + + // this should be nullptr for the most part. It's set + // to point to a valid torrent_peer object if that + // object needs to be kept alive. If we ever feel + // like removing a torrent_peer from m_peers, we + // first check if the peer matches this one, and + // if so, don't delete it. + torrent_peer* m_locked_peer; + + // the peer allocator, as stored from the constructor + // this must be available in the destructor to free all peers + torrent_peer_allocator_interface& m_peer_allocator; + + // the number of seeds in the torrent_peer list + std::uint32_t m_num_seeds:31; + + // this was the state of the torrent the + // last time we recalculated the number of + // connect candidates. Since seeds (or upload + // only) peers are not connect candidates + // when we're finished, the set depends on + // this state. Every time m_torrent->is_finished() + // is different from this state, we need to + // recalculate the connect candidates. + std::uint32_t m_finished:1; + + // since the torrent_peer list can grow too large + // to scan all of it, start at this index + int m_round_robin = 0; + + // a list of good connect candidates + std::vector m_candidate_cache; + + // The number of peers in our torrent_peer list + // that are connect candidates. i.e. they're + // not already connected and they have not + // yet reached their max try count and they + // have the connectable state (we have a listen + // port for them). + int m_num_connect_candidates = 0; + + // if a peer has failed this many times or more, we don't consider + // it a connect candidate anymore. + int m_max_failcount = 3; + }; + +} + +#endif // TORRENT_POLICY_HPP_INCLUDED diff --git a/docs/include/libtorrent/peer_request.hpp b/docs/include/libtorrent/peer_request.hpp new file mode 100644 index 0000000..a96970b --- /dev/null +++ b/docs/include/libtorrent/peer_request.hpp @@ -0,0 +1,59 @@ +/* + +Copyright (c) 2004, 2009, 2013-2014, 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_REQUEST_HPP_INCLUDED +#define TORRENT_PEER_REQUEST_HPP_INCLUDED + +#include "libtorrent/units.hpp" + +namespace libtorrent { + + // represents a byte range within a piece. Internally this is is used for + // incoming piece requests. + struct TORRENT_EXPORT peer_request + { + // The index of the piece in which the range starts. + piece_index_t piece; + // The byte offset within that piece where the range starts. + int start; + // The size of the range, in bytes. + int length; + + // returns true if the right hand side peer_request refers to the same + // range as this does. + bool operator==(peer_request const& r) const + { return piece == r.piece && start == r.start && length == r.length; } + }; +} + +#endif // TORRENT_PEER_REQUEST_HPP_INCLUDED diff --git a/docs/include/libtorrent/performance_counters.hpp b/docs/include/libtorrent/performance_counters.hpp new file mode 100644 index 0000000..db79c0e --- /dev/null +++ b/docs/include/libtorrent/performance_counters.hpp @@ -0,0 +1,500 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2019, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PERFORMANCE_COUNTERS_HPP_INCLUDED +#define TORRENT_PERFORMANCE_COUNTERS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/array.hpp" + +#include +#include +#include + +namespace libtorrent { + + struct TORRENT_EXPORT counters + { + // internal + enum stats_counter_t + { + // the number of peers that were disconnected this + // tick due to protocol error + error_peers, + disconnected_peers, + eof_peers, + connreset_peers, + connrefused_peers, + connaborted_peers, + notconnected_peers, + perm_peers, + buffer_peers, + unreachable_peers, + broken_pipe_peers, + addrinuse_peers, + no_access_peers, + invalid_arg_peers, + aborted_peers, + + piece_requests, + max_piece_requests, + invalid_piece_requests, + choked_piece_requests, + cancelled_piece_requests, + piece_rejects, + error_incoming_peers, + error_outgoing_peers, + error_rc4_peers, + error_encrypted_peers, + error_tcp_peers, + error_utp_peers, + + // the number of times the piece picker was + // successfully invoked, split by the reason + // it was invoked + reject_piece_picks, + unchoke_piece_picks, + incoming_redundant_piece_picks, + incoming_piece_picks, + end_game_piece_picks, + snubbed_piece_picks, + interesting_piece_picks, + hash_fail_piece_picks, + + // these counters indicate which parts + // of the piece picker CPU is spent in + piece_picker_partial_loops, + piece_picker_suggest_loops, + piece_picker_sequential_loops, + piece_picker_reverse_rare_loops, + piece_picker_rare_loops, + piece_picker_rand_start_loops, + piece_picker_rand_loops, + piece_picker_busy_loops, + + // reasons to disconnect peers + connect_timeouts, + uninteresting_peers, + timeout_peers, + no_memory_peers, + too_many_peers, + transport_timeout_peers, + num_banned_peers, + banned_for_hash_failure, + + // connection attempts (not necessarily successful) + connection_attempts, + // the number of iterations over the peer list when finding + // a connect candidate + connection_attempt_loops, + + // the number of peer connection attempts made as high + // priority connections for new torrents + boost_connection_attempts, + + // calls to torrent::connect_to_peer() that failed + missed_connection_attempts, + + // calls to peer_list::connect_one_peer() resulting in + // no peer candidate being found + no_peer_connection_attempts, + + // successful incoming connections (not rejected for any reason) + incoming_connections, + + // counts events where the network + // thread wakes up + on_read_counter, + on_write_counter, + on_tick_counter, + on_lsd_counter, + on_lsd_peer_counter, + on_udp_counter, + on_accept_counter, + on_disk_queue_counter, + on_disk_counter, + + // bittorrent message counters + // how about dont-have, share-mode, upload-only + num_incoming_choke, + num_incoming_unchoke, + num_incoming_interested, + num_incoming_not_interested, + num_incoming_have, + num_incoming_bitfield, + num_incoming_request, + num_incoming_piece, + num_incoming_cancel, + num_incoming_dht_port, + num_incoming_suggest, + num_incoming_have_all, + num_incoming_have_none, + num_incoming_reject, + num_incoming_allowed_fast, + num_incoming_ext_handshake, + num_incoming_pex, + num_incoming_metadata, + num_incoming_extended, + + num_outgoing_choke, + num_outgoing_unchoke, + num_outgoing_interested, + num_outgoing_not_interested, + num_outgoing_have, + num_outgoing_bitfield, + num_outgoing_request, + num_outgoing_piece, + num_outgoing_cancel, + num_outgoing_dht_port, + num_outgoing_suggest, + num_outgoing_have_all, + num_outgoing_have_none, + num_outgoing_reject, + num_outgoing_allowed_fast, + num_outgoing_ext_handshake, + num_outgoing_pex, + num_outgoing_metadata, + num_outgoing_extended, + num_outgoing_hash_request, + num_outgoing_hashes, + num_outgoing_hash_reject, + + num_piece_passed, + num_piece_failed, + + num_have_pieces, + num_total_pieces_added, + + num_blocks_written, + num_blocks_read, + num_blocks_hashed, + num_write_ops, + num_read_ops, + num_read_back, + + disk_read_time, + disk_write_time, + disk_hash_time, + disk_job_time, + + waste_piece_timed_out, + waste_piece_cancelled, + waste_piece_unknown, + waste_piece_seed, + waste_piece_end_game, + waste_piece_closing, + + sent_payload_bytes, + sent_bytes, + sent_ip_overhead_bytes, + sent_tracker_bytes, + recv_payload_bytes, + recv_bytes, + recv_ip_overhead_bytes, + recv_tracker_bytes, + + recv_failed_bytes, + recv_redundant_bytes, + + dht_messages_in, + dht_messages_in_dropped, + dht_messages_out, + dht_messages_out_dropped, + dht_bytes_in, + dht_bytes_out, + + dht_ping_in, + dht_ping_out, + dht_find_node_in, + dht_find_node_out, + dht_get_peers_in, + dht_get_peers_out, + dht_announce_peer_in, + dht_announce_peer_out, + dht_get_in, + dht_get_out, + dht_put_in, + dht_put_out, + dht_sample_infohashes_in, + dht_sample_infohashes_out, + + dht_invalid_announce, + dht_invalid_get_peers, + dht_invalid_find_node, + dht_invalid_put, + dht_invalid_get, + dht_invalid_sample_infohashes, + + // uTP counters. + utp_packet_loss, + utp_timeout, + utp_packets_in, + utp_packets_out, + utp_fast_retransmit, + utp_packet_resend, + utp_samples_above_target, + utp_samples_below_target, + utp_payload_pkts_in, + utp_payload_pkts_out, + utp_invalid_pkts_in, + utp_redundant_pkts_in, + + // the buffer sizes accepted by + // socket send calls. The larger + // the more efficient. The size is + // 1 << n, where n is the number + // at the end of the counter name + + // 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, + // 16384, 32768, 65536, 131072, 262144, 524288, 1048576 + socket_send_size3, + socket_send_size4, + socket_send_size5, + socket_send_size6, + socket_send_size7, + socket_send_size8, + socket_send_size9, + socket_send_size10, + socket_send_size11, + socket_send_size12, + socket_send_size13, + socket_send_size14, + socket_send_size15, + socket_send_size16, + socket_send_size17, + socket_send_size18, + socket_send_size19, + socket_send_size20, + + // the buffer sizes returned by + // socket recv calls. The larger + // the more efficient. The size is + // 1 << n, where n is the number + // at the end of the counter name + + // 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, + // 16384, 32768, 65536, 131072, 262144, 524288, 1048576 + socket_recv_size3, + socket_recv_size4, + socket_recv_size5, + socket_recv_size6, + socket_recv_size7, + socket_recv_size8, + socket_recv_size9, + socket_recv_size10, + socket_recv_size11, + socket_recv_size12, + socket_recv_size13, + socket_recv_size14, + socket_recv_size15, + socket_recv_size16, + socket_recv_size17, + socket_recv_size18, + socket_recv_size19, + socket_recv_size20, + + num_stats_counters + }; + + // == ALL FOLLOWING ARE GAUGES == + + // it is important that all gauges have a higher index than counters. + // This assumption is relied upon in other parts of the code + + // internal + enum stats_gauge_t + { + num_checking_torrents = num_stats_counters, + num_stopped_torrents, + num_upload_only_torrents, // upload_only means finished + num_downloading_torrents, + num_seeding_torrents, + num_queued_seeding_torrents, + num_queued_download_torrents, + num_error_torrents, + + // the number of torrents that don't have the + // IP filter applied to them. + non_filter_torrents, + + // these counter indices deliberately + // match the order of socket type IDs + // defined in socket_type.hpp. + num_tcp_peers, + num_socks5_peers, + num_http_proxy_peers, + num_utp_peers, + num_i2p_peers, + num_ssl_peers, + num_ssl_socks5_peers, + num_ssl_http_proxy_peers, + num_ssl_utp_peers, + + // the number of peer connections that are half-open (i.e. in the + // process of completing a connection attempt) and fully connected. + // These states are mutually exclusive (a connection cannot be in both + // states simultaneously). + num_peers_half_open, + num_peers_connected, + + // the number of peers interested in us (``up_interested``) and peers + // we are interested in (``down_interested``). + num_peers_up_interested, + num_peers_down_interested, + + // the total number of unchoked peers (``up_unchoked_all``), the number + // of peers unchoked via the optimistic unchoke + // (``up_unchoked_optimistic``) and peers unchoked via the + // reciprocation (regular) unchoke mechanism (``up_unchoked``). + // and the number of peers that have unchoked us (``down_unchoked``). + num_peers_up_unchoked_all, + num_peers_up_unchoked_optimistic, + num_peers_up_unchoked, + num_peers_down_unchoked, + + // the number of peers with at least one piece request pending, + // downloading (``down_requests``) or uploading (``up_requests``) + num_peers_up_requests, + num_peers_down_requests, + + // the number of peers that have at least one outstanding disk request, + // either reading (``up_disk``) or writing (``down_disk``). + num_peers_up_disk, + num_peers_down_disk, + + // the number of peers in end-game mode. End game mode is where there + // are no blocks that we have not sent any requests to download. In this + // mode, blocks are allowed to be requested from more than one peer at + // at time. + num_peers_end_game, + request_latency, + + disk_blocks_in_use, + queued_disk_jobs, + num_running_disk_jobs, + num_read_jobs, + num_write_jobs, + num_jobs, + num_writing_threads, + num_running_threads, + blocked_disk_jobs, + queued_write_bytes, + num_unchoke_slots, + + num_fenced_read, + num_fenced_write, + num_fenced_hash, + num_fenced_move_storage, + num_fenced_release_files, + num_fenced_delete_files, + num_fenced_check_fastresume, + num_fenced_save_resume_data, + num_fenced_rename_file, + num_fenced_stop_torrent, + num_fenced_flush_piece, + num_fenced_flush_hashed, + num_fenced_flush_storage, + num_fenced_file_priority, + num_fenced_load_torrent, + num_fenced_clear_piece, + num_fenced_tick_storage, + + dht_nodes, + dht_node_cache, + dht_torrents, + dht_peers, + dht_immutable_data, + dht_mutable_data, + dht_allocated_observers, + + has_incoming_connections, + + limiter_up_queue, + limiter_down_queue, + limiter_up_bytes, + limiter_down_bytes, + + // the number of uTP connections in each respective state + // these must be defined in the same order as the state_t enum + // in utp_stream + num_utp_idle, + num_utp_syn_sent, + num_utp_connected, + num_utp_fin_sent, + num_utp_close_wait, + num_utp_deleted, + + num_outstanding_accept, + + num_queued_tracker_announces, + + num_counters, + num_gauges_counters = num_counters - num_stats_counters + }; +#ifdef ATOMIC_LLONG_LOCK_FREE +#define TORRENT_COUNTER_NOEXCEPT noexcept +#else +#define TORRENT_COUNTER_NOEXCEPT +#endif + + counters() TORRENT_COUNTER_NOEXCEPT; + + counters(counters const&) TORRENT_COUNTER_NOEXCEPT; + counters& operator=(counters const&) & TORRENT_COUNTER_NOEXCEPT; + + // returns the new value + std::int64_t inc_stats_counter(int c, std::int64_t value = 1) TORRENT_COUNTER_NOEXCEPT; + std::int64_t operator[](int i) const TORRENT_COUNTER_NOEXCEPT; + + void set_value(int c, std::int64_t value) TORRENT_COUNTER_NOEXCEPT; + void blend_stats_counter(int c, std::int64_t value, int ratio) TORRENT_COUNTER_NOEXCEPT; + + private: + + // TODO: some space could be saved here by making gauges 32 bits + // TODO: restore these to regular integers. Instead have one copy + // of the counters per thread and collect them at convenient + // synchronization points +#ifdef ATOMIC_LLONG_LOCK_FREE + aux::array, num_counters> m_stats_counter; +#else + // if the atomic type isn't lock-free, use a single lock instead, for + // the whole array + mutable std::mutex m_mutex; + aux::array m_stats_counter; +#endif + }; +} + +#endif diff --git a/docs/include/libtorrent/pex_flags.hpp b/docs/include/libtorrent/pex_flags.hpp new file mode 100644 index 0000000..6310f3b --- /dev/null +++ b/docs/include/libtorrent/pex_flags.hpp @@ -0,0 +1,65 @@ +/* + +Copyright (c) 2017-2020, Arvid Norberg +Copyright (c) 2018, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEX_FLAGS_HPP_INCLUDE +#define TORRENT_PEX_FLAGS_HPP_INCLUDE + +#include + +#include "libtorrent/flags.hpp" + +namespace libtorrent { + + using pex_flags_t = flags::bitfield_flag; + + // the peer supports protocol encryption + constexpr pex_flags_t pex_encryption = 0_bit; + + // the peer is a seed + constexpr pex_flags_t pex_seed = 1_bit; + + // the peer supports the uTP, transport protocol over UDP. + constexpr pex_flags_t pex_utp = 2_bit; + + // the peer supports the holepunch extension If this flag is received from a + // peer, it can be used as a rendezvous point in case direct connections to + // the peer fail + constexpr pex_flags_t pex_holepunch = 3_bit; + + // protocol v2 + // this is not a standard flag, it is only used internally + constexpr pex_flags_t pex_lt_v2 = 7_bit; +} + +#endif + diff --git a/docs/include/libtorrent/piece_block.hpp b/docs/include/libtorrent/piece_block.hpp new file mode 100644 index 0000000..41256ab --- /dev/null +++ b/docs/include/libtorrent/piece_block.hpp @@ -0,0 +1,69 @@ +/* + +Copyright (c) 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PIECE_BLOCK_HPP_INCLUDED +#define TORRENT_PIECE_BLOCK_HPP_INCLUDED + +#include "libtorrent/units.hpp" + +namespace libtorrent { + + struct TORRENT_EXPORT piece_block + { + static const piece_block invalid; + + piece_block() = default; + piece_block(piece_index_t p_index, int b_index) + : piece_index(p_index) + , block_index(b_index) + { + } + piece_index_t piece_index {0}; + int block_index = 0; + + bool operator<(piece_block const& b) const + { + if (piece_index < b.piece_index) return true; + if (piece_index == b.piece_index) return block_index < b.block_index; + return false; + } + + bool operator==(piece_block const& b) const + { return piece_index == b.piece_index && block_index == b.block_index; } + + bool operator!=(piece_block const& b) const + { return piece_index != b.piece_index || block_index != b.block_index; } + }; +} +#endif diff --git a/docs/include/libtorrent/piece_block_progress.hpp b/docs/include/libtorrent/piece_block_progress.hpp new file mode 100644 index 0000000..16c6a53 --- /dev/null +++ b/docs/include/libtorrent/piece_block_progress.hpp @@ -0,0 +1,61 @@ +/* + +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2005, 2014, 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PIECE_BLOCK_PROGRESS_HPP_INCLUDED +#define TORRENT_PIECE_BLOCK_PROGRESS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/units.hpp" + +namespace libtorrent { + + struct piece_block_progress + { + static constexpr piece_index_t invalid_index{-1}; + + // the piece and block index + // determines exactly which + // part of the torrent that + // is currently being downloaded + piece_index_t piece_index{invalid_index}; + int block_index; + // the number of bytes we have received + // of this block + int bytes_downloaded; + // the number of bytes in the block + int full_block_bytes; + }; +} + +#endif // TORRENT_PIECE_BLOCK_PROGRESS_HPP_INCLUDED diff --git a/docs/include/libtorrent/piece_picker.hpp b/docs/include/libtorrent/piece_picker.hpp new file mode 100644 index 0000000..761a297 --- /dev/null +++ b/docs/include/libtorrent/piece_picker.hpp @@ -0,0 +1,925 @@ +/* + +Copyright (c) 2003-2021, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2019, Steven Siloti +Copyright (c) 2021, Denis Kuzmenok +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef TORRENT_PIECE_PICKER_HPP_INCLUDED +#define TORRENT_PIECE_PICKER_HPP_INCLUDED + +// heavy weight reference counting invariant checks +//#define TORRENT_DEBUG_REFCOUNTS + +#include "libtorrent/config.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/piece_block.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/array.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/alert_types.hpp" // for picker_flags_t +#include "libtorrent/download_priority.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/index_range.hpp" + +namespace libtorrent { + + struct torrent; + struct peer_connection; + template + struct typed_bitfield; + struct counters; + struct torrent_peer; + + using prio_index_t = aux::strong_typedef; + using picker_options_t = flags::bitfield_flag; + using download_queue_t = aux::strong_typedef; + using piece_extent_t = aux::strong_typedef; + + struct piece_count + { + // the number of pieces included in the "set" + int num_pieces; + // the number of bytes, out of those pieces, that are pad + // files + std::int64_t pad_bytes; + // true if the last piece is part of the set + bool last_piece; + }; + + // the piece picker tracks: + // 1. The blocks in which pieces we have sent requests for + // 2. Which peers we sent the requests to + // 3. The availability of each piece + // 4. The priority of each piece + // 5. Which blocks and pieces have been written to disk (versus being in the cache) + // 6. Which pieces have passed the hash check (i.e. we have them) + // 7. Cursors for sequential download + // 8. The number of pad-bytes in each piece + // All this in a data structure to make it cheap to pick the next piece to + // request from a peer. + // If we "have" a piece, it means it has passed the hash check. If a piece + // has been "flushed", it means it's been stored to disk. + // When saving resume data, we only care about "flushed" pieces. + struct TORRENT_EXTRA_EXPORT piece_picker + { + // only defined when TORRENT_PICKER_LOG is defined, used for debugging + // unit tests + friend void print_pieces(piece_picker const& p); + + public: + + enum + { + // the number of priority levels + priority_levels = 8, + // priority factor + prio_factor = 3, + // max blocks per piece + // there are counters in downloading_piece that only have 15 bits to + // count blocks per piece, that's restricting this + max_blocks_per_piece = (1 << 15) - 1 + }; + + struct block_info + { + block_info(): num_peers(0), state(state_none) {} + // the peer this block was requested or + // downloaded from. + torrent_peer* peer = nullptr; + // the number of peers that has this block in their + // download or request queues + unsigned num_peers:14; + // the state of this block + enum { state_none, state_requested, state_writing, state_finished }; + unsigned state:2; +#if TORRENT_USE_ASSERTS + // to allow verifying the invariant of blocks belonging to the right piece + piece_index_t piece_index{-1}; + std::set peers; +#endif + }; + + // pick rarest first + static constexpr picker_options_t rarest_first = 0_bit; + + // pick the most common first, or the last pieces if sequential + static constexpr picker_options_t reverse = 1_bit; + + // only pick pieces exclusively requested from this peer + static constexpr picker_options_t on_parole = 2_bit; + + // always pick partial pieces before any other piece + static constexpr picker_options_t prioritize_partials = 3_bit; + + // pick pieces in sequential order + static constexpr picker_options_t sequential = 4_bit; + + // 5_bit is available + + // only expands pieces (when prefer contiguous blocks is set) + // within properly aligned ranges, not the largest possible + // range of pieces. + static constexpr picker_options_t align_expanded_pieces = 6_bit; + + // this will create an affinity to pick pieces in extents of 4 MiB, in an + // attempt to improve disk I/O by picking ranges of pieces (if pieces are + // small) + static constexpr picker_options_t piece_extent_affinity = 7_bit; + + struct downloading_piece + { + downloading_piece() + : finished(0) + , passed_hash_check(0) + , writing(0) + , locked(0) + , requested(0) + , hashing(0) {} + + bool operator<(downloading_piece const& rhs) const { return index < rhs.index; } + + // the index of the piece + piece_index_t index{(std::numeric_limits::max)()}; + + // info about each block in this piece. this is an index into the + // m_block_info array, when multiplied by blocks_per_piece. + // The blocks_per_piece following entries contain information about + // all blocks in this piece. + std::uint16_t info_idx{(std::numeric_limits::max)()}; + + // the number of blocks in the finished state + std::uint16_t finished:15; + + // set to true when the hash check job + // returns with a valid hash for this piece. + // we might not 'have' the piece yet though, + // since it might not have been written to + // disk. This is not set of locked is + // set. + std::uint16_t passed_hash_check:1; + + // the number of blocks in the writing state + std::uint16_t writing:15; + + // when this is set, blocks from this piece may + // not be picked. This is used when the hash check + // fails or writing to the disk fails, while waiting + // to synchronize the disk thread and clear out any + // remaining state. Once this synchronization is + // done, restore_piece() is called to clear the + // locked flag. + std::uint16_t locked:1; + + // the number of blocks in the requested state + std::uint16_t requested:15; + +#if 1 + // set to 1 if there is an outstanding hash request for this piece + std::uint16_t hashing:1; + + // this is for a future per-block request feature +#else + // available for future use + std::uint16_t unused:1; + + // number of outstanding hash jobs for this piece + std::uint16_t hashing:15; + + // available for future use + std::uint16_t unused2:1; +#endif + }; + + piece_picker(std::int64_t total_size, int piece_size); + + void get_availability(aux::vector& avail) const; + int get_availability(piece_index_t piece) const; + + // increases the peer count for the given piece + // (is used when a HAVE message is received) + void inc_refcount(piece_index_t, torrent_peer const*); + void dec_refcount(piece_index_t, torrent_peer const*); + + // increases the peer count for the given piece + // (is used when a BITFIELD message is received) + void inc_refcount(typed_bitfield const& bitmask + , const torrent_peer* peer); + // decreases the peer count for the given piece + // (used when a peer disconnects) + void dec_refcount(typed_bitfield const& bitmask + , const torrent_peer* peer); + + // these will increase and decrease the peer count + // of all pieces. They are used when seeds join + // or leave the swarm. + void inc_refcount_all(const torrent_peer* peer); + void dec_refcount_all(const torrent_peer* peer); + + // we have every piece. This is used when creating a piece picker for a + // seed + void we_have_all(); + + // A piece completes when it has passed the hash check *and* been + // completely written to disk. The piece picker no longer need to track + // the state of individual blocks + // The refcounter will indicate that + // we are not interested in this piece anymore + // (i.e. we don't have to maintain a refcount) + void piece_flushed(piece_index_t); + void we_dont_have(piece_index_t); + + // the lowest piece index we do not have + piece_index_t cursor() const { return m_cursor; } + + // one past the last piece we do not have. + piece_index_t reverse_cursor() const { return m_reverse_cursor; } + + // sets all pieces to dont-have + void resize(std::int64_t total_size, int piece_size); + int num_pieces() const { return int(m_piece_map.size()); } + + // returns true if we have the piece or if the piece + // has passed the hash check + bool have_piece(piece_index_t) const; + + // returns true if the piece has been completely downloaded and + // successfully flushed to disk (i.e. "finished"). + bool is_piece_flushed(piece_index_t) const; + + bool is_downloading(piece_index_t const index) const + { + TORRENT_ASSERT(index >= piece_index_t(0)); + TORRENT_ASSERT(index < m_piece_map.end_index()); + + piece_pos const& p = m_piece_map[index]; + return p.downloading(); + } + + // sets the priority of a piece. + // returns true if the priority was changed from 0 to non-0 + // or vice versa + bool set_piece_priority(piece_index_t, download_priority_t); + + // returns the priority for the piece at 'index' + download_priority_t piece_priority(piece_index_t) const; + + // returns the current piece priorities for all pieces + void piece_priorities(std::vector&) const; + + // This function returns a list of all blocks in pieces that this client + // has and that are interesting to download, in the + // ``interesting_blocks`` out-parameter. The blocks are returned in + // priority order. + // + // If the caller of this function decides to download a block, it must + // mark it as being downloaded itself, by using the + // mark_as_downloading() member function. THIS IS DONE BY THE + // peer_connection::send_request() MEMBER FUNCTION! + // + // ``pieces`` should be the bitfield of all pieces, indicating which + // pieces the client has, that can be requested from it. + // + // The last argument is the torrent_peer pointer for the peer that + // we'll download from. + // + // ``prefer_contiguous_blocks`` indicates how many blocks we would like + // to request contiguously. The blocks are not merged by the piece + // picker, but may be coalesced later by the caller. This feature is + // used by web_peer_connection to request larger blocks at a time to + // mitigate limited pipelining and lack of keep-alive (i.e. higher + // overhead per request). + picker_flags_t pick_pieces(typed_bitfield const& pieces + , std::vector& interesting_blocks, int num_blocks + , int prefer_contiguous_blocks, torrent_peer* peer + , picker_options_t options, std::vector const& suggested_pieces + , int num_peers + , counters& pc + ) const; + + // picks blocks from each of the pieces in the piece_list + // vector that is also in the piece bitmask. The blocks + // are added to interesting_blocks, and busy blocks are + // added to backup_blocks. num blocks is the number of + // blocks to be picked. Blocks are not picked from pieces + // that are being downloaded + int add_blocks(piece_index_t piece, typed_bitfield const& pieces + , std::vector& interesting_blocks + , std::vector& backup_blocks + , std::vector& backup_blocks2 + , int num_blocks, int prefer_contiguous_blocks + , torrent_peer* peer, std::vector& ignore + , picker_options_t options) const; + + // clears the peer pointer in all downloading pieces with this + // peer pointer + void clear_peer(torrent_peer* peer); + +#if TORRENT_USE_INVARIANT_CHECKS + // this is an invariant check + void check_peers(); +#endif + + // returns true if any client is currently downloading this + // piece-block, or if it's queued for downloading by some client + // or if it already has been successfully downloaded + bool is_requested(piece_block block) const; + // returns true if the block has been downloaded + bool is_downloaded(piece_block block) const; + // returns true if the block has been downloaded and written to disk + bool is_finished(piece_block block) const; + + // marks this piece-block as queued for downloading + // options are flags from options_t. + bool mark_as_downloading(piece_block block, torrent_peer* peer + , picker_options_t options = {}); + + // returns true if the block was marked as writing, + // and false if the block is already finished or writing + bool mark_as_writing(piece_block block, torrent_peer* peer); + + void started_hash_job(piece_index_t piece); + void completed_hash_job(piece_index_t piece); + + void mark_as_canceled(piece_block block, torrent_peer* peer); + void mark_as_finished(piece_block block, torrent_peer* peer); + + void set_pad_bytes(piece_index_t p, int bytes); + + // prevent blocks from being picked from this piece. + // to unlock the piece, call restore_piece() on it + void lock_piece(piece_index_t piece); + + void write_failed(piece_block block); + int num_peers(piece_block block) const; + + void piece_passed(piece_index_t); + + // returns information about the given piece + void piece_info(piece_index_t, piece_picker::downloading_piece& st) const; + + struct piece_stats_t + { + int peer_count; + int priority; + bool have; + bool downloading; + }; + + piece_stats_t piece_stats(piece_index_t) const; + + // if a piece had a hash-failure, it must be restored and + // made available for redownloading + void restore_piece(piece_index_t, span blocks = {}); + + // clears the given piece's download flag + // this means that this piece-block can be picked again + void abort_download(piece_block block, torrent_peer* peer = nullptr); + + // returns true if all blocks in this piece are finished + // or if we have the piece + bool is_piece_finished(piece_index_t) const; + + // returns true if at least one block in this piece is being hashed + // only valid for v2 torrents + bool is_hashing(piece_index_t piece) const; + + // returns the number of blocks there is in the given piece + int blocks_in_piece(piece_index_t) const; + + // return the peer pointers to all peers that participated in + // this piece + std::vector get_downloaders(piece_index_t) const; + + std::vector get_download_queue() const; + int get_download_queue_size() const; + + void get_download_queue_sizes(int* partial + , int* full, int* finished, int* zero_prio) const; + + torrent_peer* get_downloader(piece_block block) const; + + + // piece states + // + // have: ----------- + // pieces: # # # # # # # # # # # + // filtered: ------- + // pads blk: ^ ^ ^ + // + // want-have: * * * * + // want: * * * * * * * + // total-have: * * * * * * + // + // we only care about: + // 1. pieces we have (less pad blocks we have) + // 2. pieces we have AND want (less pad blocks we have and want) + // 3. pieces we want (less pad blocks we want) + + // number of pieces not filtered, as well as the number of + // blocks out of those pieces that are pad blocks. + // ``last_piece`` is set if the last piece is one of the + // pieces. + piece_count want() const; + + // number of pieces we have out of the ones we have not filtered + piece_count have_want() const; + + // number of pieces we have (regardless of whether they are filtered) + piece_count have() const; + + piece_count all_pieces() const; + + int pad_bytes_in_piece(piece_index_t const index) const; + + // number of pieces whose hash has passed (but haven't necessarily + // been flushed to disk yet) + int num_have() const { return m_num_have; } + + // return true if all the pieces we want have passed the hash check (but + // may not have been written to disk yet) + bool is_finished() const + { + // this expression warrants some explanation: + // if the number of pieces we *want* to download + // is less than or (more likely) equal to the number of pieces that + // have passed the hash check (discounting the pieces that have passed + // the check but then had their priority set to 0). Then we're + // finished. Note that any piece we *have* implies it's both passed the + // hash check *and* been written to disk. + // num_pieces() - m_num_filtered - m_num_have_filtered + // <= (m_num_have - m_num_have_filtered) + // this can be simplified. Note how m_num_have_filtered appears on both + // side of the equation. + // + return num_pieces() - m_num_filtered <= m_num_have; + } + + bool is_seeding() const { return m_num_have == num_pieces(); } + + // the number of pieces we want and don't have + int num_want_left() const { return num_pieces() - m_num_have - m_num_filtered + m_num_have_filtered; } + +#if TORRENT_USE_INVARIANT_CHECKS + void check_piece_state() const; + // used in debug mode + void verify_priority(prio_index_t start, prio_index_t end, int prio) const; + void verify_pick(span picked + , typed_bitfield const& bits) const; + + void check_peer_invariant(typed_bitfield const& have + , torrent_peer const* p) const; + void check_invariant(const torrent* t = nullptr) const; +#endif + + // functor that compares indices on downloading_pieces + struct has_index + { + explicit has_index(piece_index_t const i) : index(i) + { TORRENT_ASSERT(i >= piece_index_t(0)); } + bool operator()(downloading_piece const& p) const + { return p.index == index; } + piece_index_t const index; + }; + + int blocks_in_last_piece() const + { return m_blocks_in_last_piece; } + + std::pair distributed_copies() const; + + // return the array of block_info objects for a given downloading_piece. + // this array has blocks_per_piece elements in it + span blocks_for_piece(downloading_piece const& dp) const; + + private: + + // picks blocks only from downloading pieces + int add_blocks_downloading(downloading_piece const& dp + , typed_bitfield const& pieces + , std::vector& interesting_blocks + , std::vector& backup_blocks + , std::vector& backup_blocks2 + , int num_blocks, int prefer_contiguous_blocks + , torrent_peer* peer + , picker_options_t options) const; + + int block_size() const + { + TORRENT_ASSERT(m_piece_size > 0); + TORRENT_ASSERT(default_block_size > 0); + return (std::min)(m_piece_size, default_block_size); + } + int blocks_per_piece() const; + int piece_size(piece_index_t p) const; + + void account_have(piece_index_t); + void account_lost(piece_index_t); + + piece_extent_t extent_for(piece_index_t) const; + index_range extent_for(piece_extent_t) const; + + void record_downloading_piece(piece_index_t const p); + + std::int64_t num_pad_bytes() const { return m_num_pad_bytes; } + + span mutable_blocks_for_piece(downloading_piece const& dp); + + std::tuple requested_from( + piece_picker::downloading_piece const& p + , int num_blocks_in_piece, torrent_peer* peer) const; + + bool can_pick(piece_index_t piece, typed_bitfield const& bitmask) const; + bool is_piece_free(piece_index_t piece, typed_bitfield const& bitmask) const; + index_range + expand_piece(piece_index_t piece, int contiguous_blocks + , typed_bitfield const& have + , picker_options_t options) const; + + struct piece_pos + { + piece_pos() {} + piece_pos(int const peer_count_, int const index_) + : peer_count(static_cast(peer_count_)) + , download_state(static_cast(piece_pos::piece_open)) + , piece_priority(static_cast(default_priority)) + , index(index_) + { + TORRENT_ASSERT(peer_count_ >= 0); + TORRENT_ASSERT(peer_count_ < (std::numeric_limits::max)()); + TORRENT_ASSERT(index_ >= 0); + } + + // the piece is partially downloaded or requested + static constexpr download_queue_t piece_downloading{0}; + + // partial pieces where all blocks in the piece have been requested + static constexpr download_queue_t piece_full{1}; + // partial pieces where all blocks in the piece have been received + // and are either finished or writing + static constexpr download_queue_t piece_finished{2}; + // partial pieces whose priority is 0 + static constexpr download_queue_t piece_zero_prio{3}; + + // the states up to this point indicate the piece is being + // downloaded (or at least has a partially downloaded piece + // in one of the m_downloads buckets). + static constexpr download_queue_t num_download_categories{4}; + + // the piece is open to be picked + static constexpr download_queue_t piece_open{4}; + + // this is not a new download category/download list bucket. + // it still goes into the piece_downloading bucket. However, + // it indicates that this piece only has outstanding requests + // from reverse peers. This is to de-prioritize it somewhat + static constexpr download_queue_t piece_downloading_reverse{5}; + static constexpr download_queue_t piece_full_reverse{6}; + + // returns one of the valid download categories of state_t or + // piece_open if this piece is not being downloaded + download_queue_t download_queue() const + { + if (state() == piece_downloading_reverse) + return piece_downloading; + if (state() == piece_full_reverse) + return piece_full; + return state(); + } + + bool reverse() const + { + return state() == piece_downloading_reverse + || state() == piece_full_reverse; + } + + void unreverse() + { + if (state() == piece_downloading_reverse) + state(piece_downloading); + else if (state() == piece_full_reverse) + state(piece_full); + } + + void make_reverse() + { + if (state() == piece_downloading) + state(piece_downloading_reverse); + else if (state() == piece_full) + state(piece_full_reverse); + } + + // the number of peers that has this piece + // (availability) + std::uint32_t peer_count : 26; + + // one of the download_queue_t values. This indicates whether this piece + // is currently being downloaded or not, and what state it's in if + // it is. Specifically, as an optimization, pieces that have all blocks + // requested from them are separated out into separate lists to make + // lookups quicker. The main oddity is that whether a downloading piece + // has only been requested from peers that are reverse, that's + // recorded as piece_downloading_reverse, which really means the same + // as piece_downloading, it just saves space to also indicate that it + // has a bit lower priority. The reverse bit is only relevant if the + // state is piece_downloading. + std::uint32_t download_state : 3; + + // TODO: 2 having 8 priority levels is probably excessive. It should + // probably be changed to 3 levels + dont-download + + // is 0 if the piece is filtered (not to be downloaded) + // 1 is low priority + // 2 is low priority + // 3 is mid priority + // 4 is default priority + // 5 is mid priority + // 6 is high priority + // 7 is high priority + std::uint32_t piece_priority : 3; + + // index in to the piece_info vector + prio_index_t index; + +#ifdef TORRENT_DEBUG_REFCOUNTS + // all the peers that have this piece + std::set have_peers; +#endif + + // index is set to this to indicate that we have the + // piece. There is no entry for the piece in the + // buckets if this is the case. + static constexpr prio_index_t we_have_index{-1}; + + // the priority value that means the piece is filtered + static constexpr std::uint32_t filter_priority = 0; + + // the max number the peer count can hold + static constexpr std::uint32_t max_peer_count = 0xffff; + + bool have() const { return index == we_have_index; } + void set_have() { index = we_have_index; TORRENT_ASSERT(have()); } + void set_not_have() { index = prio_index_t(0); TORRENT_ASSERT(!have()); } + bool downloading() const { return state() != piece_open; } + + bool filtered() const { return piece_priority == filter_priority; } + + // this function returns the effective priority of the piece. It's + // actually the sort order of this piece compared to other pieces. A + // lower index means it will be picked before a piece with a higher + // index. + // The availability of the piece (the number of peers that have this + // piece) is fundamentally controlling the priority. It's multiplied + // by 3 to form 3 levels of priority for each availability. + // + // downloading pieces (not reverse) + // | open pieces (not downloading) + // | | downloading pieces (reverse peers) + // | | | + // +---+---+---+ + // | 0 | 1 | 2 | + // +---+---+---+ + // this '3' is called prio_factor + // + // the manually set priority takes precedence over the availability + // by multiplying availability by priority. + + int priority(piece_picker const* picker) const + { + // filtered pieces (prio = 0), pieces we have or pieces with + // availability = 0 should not be present in the piece list + // returning -1 indicates that they shouldn't. + if (filtered() || have() || peer_count + picker->m_seeds == 0 + || state() == piece_full + || state() == piece_finished) + return -1; + + TORRENT_ASSERT(piece_priority > 0); + + // this is to keep downloading pieces at higher priority than + // pieces that are not being downloaded, and to make reverse + // downloading pieces to be lower priority + int adjustment = -2; + if (reverse()) adjustment = -1; + else if (state() != piece_open) adjustment = -3; + + // the + 1 here is because peer_count count be 0, it m_seeds + // is > 0. We don't actually care about seeds (except for the + // first one) since the order of the pieces is unaffected. + int availability = int(peer_count) + 1; + TORRENT_ASSERT(availability > 0); + TORRENT_ASSERT(int(priority_levels - piece_priority) > 0); + + return availability * int(priority_levels - piece_priority) + * prio_factor + adjustment; + } + + bool operator!=(piece_pos const& p) const + { return index != p.index || peer_count != p.peer_count; } + + bool operator==(piece_pos const& p) const + { return index == p.index && peer_count == p.peer_count; } + + download_queue_t state() const { return download_queue_t(download_state); } + void state(download_queue_t q) { download_state = static_cast(q); } + }; + +#ifndef TORRENT_DEBUG_REFCOUNTS + static_assert(sizeof(piece_pos) == sizeof(char) * 8, "unexpected struct size"); +#endif + + bool partial_compare_rarest_first(downloading_piece const* lhs + , downloading_piece const* rhs) const; + + void break_one_seed(); + + void update_pieces() const; + + prio_index_t priority_begin(int prio) const; + prio_index_t priority_end(int prio) const; + + // fills in the range [start, end) of pieces in + // m_pieces that have priority 'prio' + std::pair priority_range(int prio) const; + + // adds the piece 'index' to m_pieces + void add(piece_index_t index); + // removes the piece with the given priority and the + // elem_index in the m_pieces vector + void remove(int priority, prio_index_t elem_index); + // updates the position of the piece with the given + // priority and the elem_index in the m_pieces vector + void update(int priority, prio_index_t elem_index); + // shuffles the given piece inside it's priority range + void shuffle(int priority, prio_index_t elem_index); + + std::vector::iterator add_download_piece(piece_index_t); + void erase_download_piece(std::vector::iterator i); + + std::vector::const_iterator find_dl_piece(download_queue_t, piece_index_t) const; + std::vector::iterator find_dl_piece(download_queue_t, piece_index_t); + + // returns an iterator to the downloading piece, whichever + // download list it may live in now + std::vector::iterator update_piece_state( + std::vector::iterator dp); + + private: + +#if TORRENT_USE_ASSERTS || TORRENT_USE_INVARIANT_CHECKS + index_range categories() const + { return {{}, piece_picker::piece_pos::num_download_categories}; } +#endif + + // the following vectors are mutable because they sometimes may + // be updated lazily, triggered by const functions + + // this maps indices to number of peers that has this piece and + // index into the m_piece_info vectors. + // piece_pos::we_have_index means that we have the piece, so it + // doesn't exist in the piece_info buckets + // pieces with the filtered flag set doesn't have entries in + // the m_piece_info buckets either + // TODO: should this be allocated lazily? + mutable aux::vector m_piece_map; + + // tracks the number of bytes in a specific piece that are part of a pad + // file. The padding is assumed to be at the end of the piece, and the + // blocks covered by the pad bytes are not picked by the piece picker + std::unordered_map m_pads_in_piece; + + // when the adjacent_piece affinity is enabled, this contains the most + // recent "extents" of adjacent pieces that have been requested from + // this is mutable because it's updated by functions to pick pieces, which + // are const. That's an efficient place to update it, since it's being + // traversed already. + mutable std::vector m_recent_extents; + + // the number of bytes of pad file set in this piece picker + std::int64_t m_num_pad_bytes = 0; + + // the number of pad blocks that we already have + std::int64_t m_have_pad_bytes = 0; + + // the number of pad blocks part of filtered pieces we don't have + std::int64_t m_filtered_pad_bytes = 0; + + // the number of pad blocks we have that are also filtered + std::int64_t m_have_filtered_pad_bytes = 0; + + // the number of seeds. These are not added to + // the availability counters of the pieces + int m_seeds = 0; + + // this vector contains all piece indices that are pickable + // sorted by priority. Pieces are in random random order + // among pieces with the same priority + mutable aux::vector m_pieces; + + // these are indices to the priority boundaries inside + // the m_pieces vector. priority 0 always start at + // 0, priority 1 starts at m_priority_boundaries[0] etc. + mutable aux::vector m_priority_boundaries; + + // each piece that's currently being downloaded has an entry in this list + // with block allocations. i.e. it says which parts of the piece that is + // being downloaded. This list is ordered by piece index to make lookups + // efficient there are as many buckets as there are piece states. See + // piece_pos::state_t. The only download state that does not have a + // corresponding downloading_piece vector is piece_open and + // piece_downloading_reverse (the latter uses the same as + // piece_downloading). + aux::array + , static_cast(piece_pos::num_download_categories) + , download_queue_t> m_downloads; + + // this holds the information of the blocks in partially downloaded + // pieces. the downloading_piece::info index point into this vector for + // its storage + aux::vector m_block_info; + + // these are block ranges in m_block_info that are free. The numbers + // in here, when multiplied by blocks_per_piece is the index to the + // first block in the range that's free to use by a new downloading_piece. + // this is a free-list. + std::vector m_free_block_infos; + + std::uint16_t m_blocks_in_last_piece = 0; + int m_piece_size = 0; + std::int64_t m_total_size = 0; + + // the number of filtered pieces that we don't already + // have. total_number_of_pieces - number_of_pieces_we_have + // - num_filtered is supposed to the number of pieces + // we still want to download + // TODO: it would be more intuitive to account "wanted" pieces + // instead of filtered + int m_num_filtered = 0; + + // the number of pieces we have that also are filtered + int m_num_have_filtered = 0; + + // we have all pieces in the range [0, m_cursor) + // m_cursor is the first piece we don't have + piece_index_t m_cursor{0}; + + // we have all pieces in the range [m_reverse_cursor, end) + // m_reverse_cursor is the first piece where we also have + // all the subsequent pieces + piece_index_t m_reverse_cursor{0}; + + // the number of pieces we have (i.e. passed hash check). + // This includes pieces that we have filtered but still have + int m_num_have = 0; + + // if this is set to true, it means update_pieces() + // has to be called before accessing m_pieces. + mutable bool m_dirty = false; + public: + + enum { max_pieces = (std::numeric_limits::max)() - 1 }; + + }; +} + +#endif // TORRENT_PIECE_PICKER_HPP_INCLUDED diff --git a/docs/include/libtorrent/platform_util.hpp b/docs/include/libtorrent/platform_util.hpp new file mode 100644 index 0000000..311008e --- /dev/null +++ b/docs/include/libtorrent/platform_util.hpp @@ -0,0 +1,14 @@ +#ifndef TORRENT_PLATFORM_UTIL_HPP +#define TORRENT_PLATFORM_UTIL_HPP + +#include + +namespace libtorrent { + + int max_open_files(); + + void set_thread_name(char const* name); + +} + +#endif // TORRENT_PLATFORM_UTIL_HPP diff --git a/docs/include/libtorrent/portmap.hpp b/docs/include/libtorrent/portmap.hpp new file mode 100644 index 0000000..65a5c78 --- /dev/null +++ b/docs/include/libtorrent/portmap.hpp @@ -0,0 +1,58 @@ +/* + +Copyright (c) 2017-2019, Arvid Norberg +Copyright (c) 2018, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PORTMAP_HPP_INCLUDED +#define TORRENT_PORTMAP_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/units.hpp" + +namespace libtorrent { + + enum class portmap_transport : std::uint8_t + { + // natpmp can be NAT-PMP or PCP + natpmp, upnp + }; + + enum class portmap_protocol : std::uint8_t + { + none, tcp, udp + }; + + // this type represents an index referring to a port mapping + using port_mapping_t = aux::strong_typedef; + +} + +#endif //TORRENT_PORTMAP_HPP_INCLUDED diff --git a/docs/include/libtorrent/posix_disk_io.hpp b/docs/include/libtorrent/posix_disk_io.hpp new file mode 100644 index 0000000..487ac41 --- /dev/null +++ b/docs/include/libtorrent/posix_disk_io.hpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2016-2017, 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_POSIX_DISK_IO +#define TORRENT_POSIX_DISK_IO + +#include "libtorrent/config.hpp" +#include "libtorrent/io_context.hpp" + +#include + +namespace libtorrent { + + struct counters; + struct disk_interface; + struct settings_interface; + + // this is a simple posix disk I/O back-end, used for systems that don't + // have a 64 bit virtual address space or don't support memory mapped files. + // It's implemented using portable C file functions and is single-threaded. + TORRENT_EXPORT std::unique_ptr posix_disk_io_constructor( + io_context& ios, settings_interface const&, counters& cnt); +} + +#endif + diff --git a/docs/include/libtorrent/proxy_base.hpp b/docs/include/libtorrent/proxy_base.hpp new file mode 100644 index 0000000..0563474 --- /dev/null +++ b/docs/include/libtorrent/proxy_base.hpp @@ -0,0 +1,377 @@ +/* + +Copyright (c) 2007-2011, 2013-2015, 2017-2020, Arvid Norberg +Copyright (c) 2016, 2019, Alden Torres +Copyright (c) 2017, Jan Berkel +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PROXY_BASE_HPP_INCLUDED +#define TORRENT_PROXY_BASE_HPP_INCLUDED + +#include "libtorrent/io.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" + +namespace libtorrent { + +struct proxy_base +{ + using next_layer_type = tcp::socket; + using lowest_layer_type = tcp::socket::lowest_layer_type; + using endpoint_type = tcp::socket::endpoint_type; + using protocol_type = tcp::socket::protocol_type; + + explicit proxy_base(io_context& io_context); + ~proxy_base(); + proxy_base(proxy_base&&) noexcept = default; + proxy_base& operator=(proxy_base&&) = default; + proxy_base(proxy_base const&) = delete; + proxy_base& operator=(proxy_base const&) = delete; + + void set_proxy(std::string hostname, int port) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_hostname = std::move(hostname); + m_port = port; + } + + using executor_type = tcp::socket::executor_type; + executor_type get_executor() { return m_sock.get_executor(); } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler handler) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.async_read_some(buffers, std::move(handler)); + } + + template + std::size_t read_some(Mutable_Buffers const& buffers, error_code& ec) + { + TORRENT_ASSERT(m_magic == 0x1337); + return m_sock.read_some(buffers, ec); + } + + template + std::size_t write_some(Const_Buffers const& buffers, error_code& ec) + { + TORRENT_ASSERT(m_magic == 0x1337); + return m_sock.write_some(buffers, ec); + } + + std::size_t available(error_code& ec) const + { return m_sock.available(ec); } + +#ifndef BOOST_NO_EXCEPTIONS + std::size_t available() const + { return m_sock.available(); } + + template + std::size_t read_some(Mutable_Buffers const& buffers) + { + TORRENT_ASSERT(m_magic == 0x1337); + return m_sock.read_some(buffers); + } + + template + std::size_t write_some(Const_Buffers const& buffers) + { + TORRENT_ASSERT(m_magic == 0x1337); + return m_sock.write_some(buffers); + } + + template + void io_control(IO_Control_Command& ioc) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.io_control(ioc); + } +#endif + + template + void io_control(IO_Control_Command& ioc, error_code& ec) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.io_control(ioc, ec); + } + + template + void async_write_some(Const_Buffers const& buffers, Handler handler) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.async_write_some(buffers, std::move(handler)); + } + +#if BOOST_VERSION >= 106600 && !defined TORRENT_BUILD_SIMULATOR + // Compatibility with the async_wait method introduced in boost 1.66 + + static constexpr auto wait_read = tcp::socket::wait_read; + static constexpr auto wait_write = tcp::socket::wait_write; + static constexpr auto wait_error = tcp::socket::wait_error; + + template + void async_wait(tcp::socket::wait_type type, Handler handler) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.async_wait(type, std::move(handler)); + } +#endif + +#ifndef BOOST_NO_EXCEPTIONS + void non_blocking(bool b) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.non_blocking(b); + } +#endif + + void non_blocking(bool b, error_code& ec) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.non_blocking(b, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void set_option(SettableSocketOption const& opt) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.set_option(opt); + } +#endif + + template + void set_option(SettableSocketOption const& opt, error_code& ec) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.set_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void get_option(GettableSocketOption& opt) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.get_option(opt); + } +#endif + + template + void get_option(GettableSocketOption& opt, error_code& ec) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.get_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void bind(endpoint_type const& /* endpoint */) + { + TORRENT_ASSERT(m_magic == 0x1337); +// m_sock.bind(endpoint); + } +#endif + + void cancel() + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.cancel(); + } + + void cancel(error_code& ec) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.cancel(ec); + } + + void bind(endpoint_type const& /* endpoint */, error_code& /* ec */) + { + TORRENT_ASSERT(m_magic == 0x1337); + // the reason why we ignore binds here is because we don't + // (necessarily) yet know what address family the proxy + // will resolve to, and binding to the wrong one would + // break our connection attempt later. The caller here + // doesn't necessarily know that we're proxying, so this + // bind address is based on the final endpoint, not the + // proxy. + // TODO: it would be nice to remember the bind port and bind once we know where the proxy is +// m_sock.bind(endpoint, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void open(protocol_type const&) + { + TORRENT_ASSERT(m_magic == 0x1337); +// m_sock.open(p); + } +#endif + + void open(protocol_type const&, error_code&) + { + TORRENT_ASSERT(m_magic == 0x1337); + // we need to ignore this for the same reason as stated + // for ignoring bind() +// m_sock.open(p, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void close() + { + TORRENT_ASSERT(m_magic == 0x1337); + m_remote_endpoint = endpoint_type(); + m_sock.close(); + m_resolver.cancel(); + } +#endif + + void close(error_code& ec) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_remote_endpoint = endpoint_type(); + m_sock.close(ec); + m_resolver.cancel(); + } + +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type remote_endpoint() const + { + TORRENT_ASSERT(m_magic == 0x1337); + return m_remote_endpoint; + } +#endif + + endpoint_type remote_endpoint(error_code& ec) const + { + TORRENT_ASSERT(m_magic == 0x1337); + if (!m_sock.is_open()) ec = boost::asio::error::not_connected; + return m_remote_endpoint; + } + +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type local_endpoint() const + { + TORRENT_ASSERT(m_magic == 0x1337); + return m_sock.local_endpoint(); + } +#endif + + endpoint_type local_endpoint(error_code& ec) const + { + TORRENT_ASSERT(m_magic == 0x1337); + return m_sock.local_endpoint(ec); + } + + lowest_layer_type& lowest_layer() + { + return m_sock.lowest_layer(); + } + + next_layer_type& next_layer() + { + return m_sock; + } + + bool is_open() const { return m_sock.is_open(); } + +protected: + + // The handler must be taken as lvalue reference here since we may not call + // it. But if we do, we want the call operator to own the function object. + template + bool handle_error(error_code e, Handler&& h) + { + TORRENT_ASSERT(m_magic == 0x1337); + if (!e) return false; + std::forward(h)(e); + error_code ec; + close(ec); + return true; + } + + aux::noexcept_movable m_sock; + std::string m_hostname; // proxy host + int m_port = 0; // proxy port + + aux::noexcept_movable m_remote_endpoint; + + // TODO: 2 use the resolver interface that has a built-in cache + aux::noexcept_move_only m_resolver; + +#if TORRENT_USE_ASSERTS + int m_magic = 0x1337; +#endif +}; + +template +struct wrap_allocator_t +{ + wrap_allocator_t(Handler h, UnderlyingHandler uh) + : m_handler(std::move(h)) + , m_underlying_handler(std::move(uh)) + {} + + wrap_allocator_t(wrap_allocator_t const&) = default; + wrap_allocator_t(wrap_allocator_t&&) = default; + + template + void operator()(A&&... a) + { + m_handler(std::forward(a)..., std::move(m_underlying_handler)); + } + + using allocator_type = typename boost::asio::associated_allocator::type; + using executor_type = typename boost::asio::associated_executor::type; + + allocator_type get_allocator() const noexcept + { return boost::asio::get_associated_allocator(m_underlying_handler); } + + executor_type get_executor() const noexcept + { + return boost::asio::get_associated_executor(m_underlying_handler); + } + +private: + Handler m_handler; + UnderlyingHandler m_underlying_handler; +}; + +template +wrap_allocator_t wrap_allocator(Handler h, UnderlyingHandler u) +{ + return wrap_allocator_t{std::move(h), std::move(u)}; +} + + +} + +#endif diff --git a/docs/include/libtorrent/puff.hpp b/docs/include/libtorrent/puff.hpp new file mode 100644 index 0000000..9aa5911 --- /dev/null +++ b/docs/include/libtorrent/puff.hpp @@ -0,0 +1,35 @@ +/* puff.h + Copyright (C) 2002, 2003 Mark Adler, all rights reserved + version 1.7, 3 Mar 2002 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Mark Adler madler@alumni.caltech.edu + */ + +#ifndef PUFF_HPP_INCLUDED +#define PUFF_HPP_INCLUDED + +/* + * See puff.c for purpose and usage. + */ +int puff(unsigned char *dest, /* pointer to destination pointer */ + unsigned long *destlen, /* amount of output space */ + const unsigned char *source, /* pointer to source data pointer */ + unsigned long *sourcelen); /* amount of input available */ + +#endif // PUFF_HPP_INCLUDED diff --git a/docs/include/libtorrent/random.hpp b/docs/include/libtorrent/random.hpp new file mode 100644 index 0000000..bee257b --- /dev/null +++ b/docs/include/libtorrent/random.hpp @@ -0,0 +1,85 @@ +/* + +Copyright (c) 2011-2013, 2016-2021, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_RANDOM_HPP_INCLUDED +#define TORRENT_RANDOM_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/span.hpp" + +#include +#include +#include + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT std::uint32_t random(std::uint32_t m); + +namespace aux { + + TORRENT_EXTRA_EXPORT std::mt19937& random_engine(); + + template + void random_shuffle(Range& range) + { +#ifdef TORRENT_BUILD_SIMULATOR + // in simulations, we want all shuffles to be deterministic (as long as + // the random engine is deterministic + if (range.size() == 0) return; + for (auto i = range.size() - 1; i > 0; --i) { + auto const other = random(std::uint32_t(i)); + if (i == other) continue; + using std::swap; + swap(range.data()[i], range.data()[other]); + } +#else + std::shuffle(range.data(), range.data() + range.size(), random_engine()); +#endif + } + + // Fills the buffer with pseudo random bytes. + // + // This functions perform differently under different setups + // For Windows and all platforms when compiled with libcrypto, it + // generates cryptographically random bytes. + // If the above conditions are not true, then a standard + // fill of bytes is used. + TORRENT_EXTRA_EXPORT void random_bytes(span buffer); + + // Fills the buffer with random bytes from a strong entropy source. This can + // be used to generate secrets. + TORRENT_EXTRA_EXPORT void crypto_random_bytes(span buffer); +} +} + +#endif // TORRENT_RANDOM_HPP_INCLUDED diff --git a/docs/include/libtorrent/read_resume_data.hpp b/docs/include/libtorrent/read_resume_data.hpp new file mode 100644 index 0000000..db57f1f --- /dev/null +++ b/docs/include/libtorrent/read_resume_data.hpp @@ -0,0 +1,71 @@ +/* + +Copyright (c) 2015-2018, 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_READ_RESUME_DATA_HPP_INCLUDE +#define TORRENT_READ_RESUME_DATA_HPP_INCLUDE + +#include "libtorrent/fwd.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/torrent_info.hpp" // for load_torrent_limits + +namespace libtorrent { + + // these functions are used to parse resume data and populate the appropriate + // fields in an add_torrent_params object. This object can then be used to add + // the actual torrent_info object to and pass to session::add_torrent() or + // session::async_add_torrent(). + // + // If the client wants to override any field that was loaded from the resume + // data, e.g. save_path, those fields must be changed after loading resume + // data but before adding the torrent. + // + // The ``piece_limit`` parameter determines the largest number of pieces + // allowed in the torrent that may be loaded as part of the resume data, if + // it contains an ``info`` field. The overloads that take a flat buffer are + // instead configured with limits on torrent sizes via load_torrent limits. + // + // In order to support large torrents, it may also be necessary to raise the + // settings_pack::max_piece_count setting and pass a higher limit to calls + // to torrent_info::parse_info_section(). + TORRENT_EXPORT add_torrent_params read_resume_data(bdecode_node const& rd + , error_code& ec, int piece_limit = 0x200000); + TORRENT_EXPORT add_torrent_params read_resume_data(span buffer + , error_code& ec, load_torrent_limits const& cfg = {}); + TORRENT_EXPORT add_torrent_params read_resume_data(bdecode_node const& rd + , int piece_limit = 0x200000); + TORRENT_EXPORT add_torrent_params read_resume_data(span buffer + , load_torrent_limits const& cfg = {}); +} + +#endif diff --git a/docs/include/libtorrent/request_blocks.hpp b/docs/include/libtorrent/request_blocks.hpp new file mode 100644 index 0000000..fb01a8e --- /dev/null +++ b/docs/include/libtorrent/request_blocks.hpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2010, 2014-2015, 2017, 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_REQUEST_BLOCKS_HPP_INCLUDED +#define TORRENT_REQUEST_BLOCKS_HPP_INCLUDED + +#include "libtorrent/peer_info.hpp" + +namespace libtorrent { + + struct torrent; + struct peer_connection; + + // returns false if the piece picker was not invoked, because + // of an early exit condition. In this case, the stats counter + // shouldn't be incremented, since it won't use any significant + // amount of CPU + bool request_a_block(torrent& t, peer_connection& c); + + // returns the rank of a peer's source. We have an affinity + // to connecting to peers with higher rank. This is to avoid + // problems when our peer list is diluted by stale peers from + // the resume data for instance + int source_rank(peer_source_flags_t source_bitmask); +} + +#endif diff --git a/docs/include/libtorrent/resolve_links.hpp b/docs/include/libtorrent/resolve_links.hpp new file mode 100644 index 0000000..a11f003 --- /dev/null +++ b/docs/include/libtorrent/resolve_links.hpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2015-2020, 2022, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_RESOLVE_LINKS_HPP +#define TORRENT_RESOLVE_LINKS_HPP + +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/sha1_hash.hpp" // for sha256_hash + +namespace libtorrent { + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + // this class is used for mutable torrents, to discover identical files + // in other torrents. + struct TORRENT_EXTRA_EXPORT resolve_links + { + struct TORRENT_EXTRA_EXPORT link_t + { + std::shared_ptr ti; + std::string save_path; + file_index_t file_idx; + }; + + explicit resolve_links(std::shared_ptr ti); + + // check to see if any files are shared with this torrent + void match(std::shared_ptr const& ti + , std::string const& save_path); + + aux::vector const& get_links() const + { return m_links; } + + private: + + void match_v1(std::shared_ptr const& ti + , std::string const& save_path); + void match_v2(std::shared_ptr const& ti + , std::string const& save_path); + + // this is the torrent we're trying to find files for. + std::shared_ptr m_torrent_file; + + // each file in m_torrent_file has an entry in this vector. Any file + // that also exists somewhere else, is filled in with the corresponding + // torrent_info object and file index + aux::vector m_links; + + // maps file size to file index, in m_torrent_file + std::unordered_multimap m_file_sizes; + + // maps file root hash to file index, in m_torrent_file + std::unordered_multimap m_file_roots; + }; +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS + +} + +#endif diff --git a/docs/include/libtorrent/session.hpp b/docs/include/libtorrent/session.hpp new file mode 100644 index 0000000..38ca9a6 --- /dev/null +++ b/docs/include/libtorrent/session.hpp @@ -0,0 +1,299 @@ +/* + +Copyright (c) 2003-2004, 2006-2007, 2009-2010, 2013-2020, Arvid Norberg +Copyright (c) 2015, Steven Siloti +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_HPP_INCLUDED +#define TORRENT_SESSION_HPP_INCLUDED + +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/session_handle.hpp" +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/session_types.hpp" // for session_flags_t + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/fingerprint.hpp" +#include // for snprintf +#endif + +namespace libtorrent { + +TORRENT_VERSION_NAMESPACE_3 + struct plugin; + struct session_params; +TORRENT_VERSION_NAMESPACE_3_END + + // The default values of the session settings are set for a regular + // bittorrent client running on a desktop system. There are functions that + // can set the session settings to pre set settings for other environments. + // These can be used for the basis, and should be tweaked to fit your needs + // better. + // + // ``min_memory_usage`` returns settings that will use the minimal amount of + // RAM, at the potential expense of upload and download performance. It + // adjusts the socket buffer sizes, disables the disk cache, lowers the send + // buffer watermarks so that each connection only has at most one block in + // use at any one time. It lowers the outstanding blocks send to the disk + // I/O thread so that connections only have one block waiting to be flushed + // to disk at any given time. It lowers the max number of peers in the peer + // list for torrents. It performs multiple smaller reads when it hashes + // pieces, instead of reading it all into memory before hashing. + // + // This configuration is intended to be the starting point for embedded + // devices. It will significantly reduce memory usage. + // + // ``high_performance_seed`` returns settings optimized for a seed box, + // serving many peers and that doesn't do any downloading. It has a 128 MB + // disk cache and has a limit of 400 files in its file pool. It support fast + // upload rates by allowing large send buffers. + TORRENT_EXPORT settings_pack min_memory_usage(); + TORRENT_EXPORT settings_pack high_performance_seed(); +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + inline void min_memory_usage(settings_pack& set) + { set = min_memory_usage(); } + TORRENT_DEPRECATED + inline void high_performance_seed(settings_pack& set) + { set = high_performance_seed(); } +#endif + +namespace aux { + + struct session_impl; +} + + struct disk_interface; + struct counters; + struct settings_interface; + + // the constructor function for the default storage. On systems that support + // memory mapped files (and a 64 bit address space) the memory mapped storage + // will be constructed, otherwise the portable posix storage. + TORRENT_EXPORT std::unique_ptr default_disk_io_constructor( + io_context& ios, settings_interface const&, counters& cnt); + + // this is a holder for the internal session implementation object. Once the + // session destruction is explicitly initiated, this holder is used to + // synchronize the completion of the shutdown. The lifetime of this object + // may outlive session, causing the session destructor to not block. The + // session_proxy destructor will block however, until the underlying session + // is done shutting down. + struct TORRENT_EXPORT session_proxy + { + friend struct session; + // default constructor, does not refer to any session + // implementation object. + session_proxy(); + ~session_proxy(); + session_proxy(session_proxy const&); + session_proxy& operator=(session_proxy const&) &; + session_proxy(session_proxy&&) noexcept; + session_proxy& operator=(session_proxy&&) & noexcept; + private: + session_proxy( + std::shared_ptr ios + , std::shared_ptr t + , std::shared_ptr impl); + + std::shared_ptr m_io_service; + std::shared_ptr m_thread; + std::shared_ptr m_impl; + }; + + // The session holds all state that spans multiple torrents. Among other + // things it runs the network loop and manages all torrents. Once it's + // created, the session object will spawn the main thread that will do all + // the work. The main thread will be idle as long it doesn't have any + // torrents to participate in. + // + // You have some control over session configuration through the + // ``session_handle::apply_settings()`` member function. To change one or more + // configuration options, create a settings_pack. object and fill it with + // the settings to be set and pass it in to ``session::apply_settings()``. + // + // see apply_settings(). + struct TORRENT_EXPORT session : session_handle + { + // Constructs the session objects which acts as the container of torrents. + // In order to avoid a race condition between starting the session and + // configuring it, you can pass in a session_params object. Its settings + // will take effect before the session starts up. + // + // The overloads taking ``flags`` can be used to start a session in + // paused mode (by passing in ``session::paused``). Note that + // ``add_default_plugins`` do not have an affect on constructors that + // take a session_params object. It already contains the plugins to use. + explicit session(session_params const& params); + explicit session(session_params&& params); + session(session_params const& params, session_flags_t flags); + session(session_params&& params, session_flags_t flags); + session(); + + // Overload of the constructor that takes an external io_context to run + // the session object on. This is primarily useful for tests that may want + // to run multiple sessions on a single io_context, or low resource + // systems where additional threads are expensive and sharing an + // io_context with other events is fine. + // + // .. warning:: + // The session object does not cleanly terminate with an external + // ``io_context``. The ``io_context::run()`` call *must* have returned + // before it's safe to destruct the session. Which means you *MUST* + // call session::abort() and save the session_proxy first, then + // destruct the session object, then sync with the io_context, then + // destruct the session_proxy object. + session(session_params&& params, io_context& ios); + session(session_params const& params, io_context& ios); + session(session_params&& params, io_context& ios, session_flags_t); + session(session_params const& params, io_context& ios, session_flags_t); + + // hidden + session(session&&); + session& operator=(session&&) &; + + // hidden + session(session const&) = delete; + session& operator=(session const&) = delete; + +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // Constructs the session objects which acts as the container of torrents. + // It provides configuration options across torrents (such as rate limits, + // disk cache, ip filter etc.). In order to avoid a race condition between + // starting the session and configuring it, you can pass in a + // settings_pack object. Its settings will take effect before the session + // starts up. + // + // The ``flags`` parameter can be used to start default features (UPnP & + // NAT-PMP) and default plugins (ut_metadata, ut_pex and smart_ban). The + // default is to start those features. If you do not want them to start, + // pass 0 as the flags parameter. + TORRENT_DEPRECATED + session(settings_pack&& pack, session_flags_t const flags); + TORRENT_DEPRECATED + session(settings_pack const& pack, session_flags_t const flags); + explicit session(settings_pack&& pack) : session(std::move(pack), add_default_plugins) {} + explicit session(settings_pack const& pack) : session(pack, add_default_plugins) {} + + // overload of the constructor that takes an external io_context to run + // the session object on. This is primarily useful for tests that may want + // to run multiple sessions on a single io_context, or low resource + // systems where additional threads are expensive and sharing an + // io_context with other events is fine. + // + // .. warning:: + // The session object does not cleanly terminate with an external + // ``io_context``. The ``io_context::run()`` call _must_ have returned + // before it's safe to destruct the session. Which means you *MUST* + // call session::abort() and save the session_proxy first, then + // destruct the session object, then sync with the io_context, then + // destruct the session_proxy object. + TORRENT_DEPRECATED + session(settings_pack&&, io_context&, session_flags_t); + TORRENT_DEPRECATED + session(settings_pack const&, io_context&, session_flags_t); + session(settings_pack&& pack, io_context& ios) : session(std::move(pack), ios, add_default_plugins) {} + session(settings_pack const& pack, io_context& ios) : session(pack, ios, add_default_plugins) {} + +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // TORRENT_ABI_VERSION + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + TORRENT_DEPRECATED + session(fingerprint const& print + , session_flags_t const flags = start_default_features | add_default_plugins + , alert_category_t const alert_mask = alert_category::error); + + TORRENT_DEPRECATED + session(fingerprint const& print + , std::pair listen_port_range + , char const* listen_interface = "0.0.0.0" + , session_flags_t const flags = start_default_features | add_default_plugins + , alert_category_t const alert_mask = alert_category::error); + +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // TORRENT_ABI_VERSION + + // The destructor of session will notify all trackers that our torrents + // have been shut down. If some trackers are down, they will time out. + // All this before the destructor of session returns. So, it's advised + // that any kind of interface (such as windows) are closed before + // destructing the session object. Because it can take a few second for + // it to finish. The timeout can be set with apply_settings(). + ~session(); + + // In case you want to destruct the session asynchronously, you can + // request a session destruction proxy. If you don't do this, the + // destructor of the session object will block while the trackers are + // contacted. If you keep one ``session_proxy`` to the session when + // destructing it, the destructor will not block, but start to close down + // the session, the destructor of the proxy will then synchronize the + // threads. So, the destruction of the session is performed from the + // ``session`` destructor call until the ``session_proxy`` destructor + // call. The ``session_proxy`` does not have any operations on it (since + // the session is being closed down, no operations are allowed on it). + // The only valid operation is calling the destructor:: + // + // struct session_proxy {}; + session_proxy abort(); + + private: + + void start(session_flags_t, session_params&& params, io_context* ios); + +#if TORRENT_ABI_VERSION <= 2 + void start(session_flags_t flags, settings_pack&& sp, io_context* ios); +#endif + + void start(session_params const& params, io_context* ios) = delete; +#if TORRENT_ABI_VERSION <= 2 + void start(session_flags_t flags, settings_pack const& sp, io_context* ios) = delete; +#endif + + // data shared between the main thread + // and the working thread + std::shared_ptr m_io_service; + std::shared_ptr m_thread; + std::shared_ptr m_impl; + }; + +} + +#endif // TORRENT_SESSION_HPP_INCLUDED diff --git a/docs/include/libtorrent/session_handle.hpp b/docs/include/libtorrent/session_handle.hpp new file mode 100644 index 0000000..2dafaf5 --- /dev/null +++ b/docs/include/libtorrent/session_handle.hpp @@ -0,0 +1,1137 @@ +/* + +Copyright (c) 2014-2018, Steven Siloti +Copyright (c) 2015-2018, Alden Torres +Copyright (c) 2015-2022, Arvid Norberg +Copyright (c) 2020, AllSeeingEyeTolledEweSew +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_HANDLE_HPP_INCLUDED +#define TORRENT_SESSION_HANDLE_HPP_INCLUDED + +#include // for shared_ptr + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/alert.hpp" // alert_category::error +#include "libtorrent/peer_class.hpp" +#include "libtorrent/peer_class_type_filter.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/session_types.hpp" +#include "libtorrent/portmap.hpp" // for portmap_protocol + +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/kademlia/announce_flags.hpp" + +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/kademlia/dht_settings.hpp" +#endif + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/session_settings.hpp" +#include +#endif + +#include "libtorrent/extensions.hpp" +#include "libtorrent/session_types.hpp" // for session_flags_t + +namespace libtorrent { + + struct torrent; + +#if TORRENT_ABI_VERSION == 1 + struct session_status; + using user_load_function_t = std::function&, error_code&)>; +#endif + + // this class provides a non-owning handle to a session and a subset of the + // interface of the session class. If the underlying session is destructed + // any handle to it will no longer be valid. is_valid() will return false and + // any operation on it will throw a system_error exception, with error code + // invalid_session_handle. + struct TORRENT_EXPORT session_handle + { + friend struct session; + friend struct aux::session_impl; + + // hidden + session_handle() = default; + session_handle(session_handle const& t) = default; + session_handle(session_handle&& t) noexcept = default; + session_handle& operator=(session_handle const&) & = default; + session_handle& operator=(session_handle&&) & noexcept = default; + +#if TORRENT_ABI_VERSION == 1 + using save_state_flags_t = libtorrent::save_state_flags_t; + using session_flags_t = libtorrent::session_flags_t; +#endif + + // returns true if this handle refers to a valid session object. If the + // session has been destroyed, all session_handle objects will expire and + // not be valid. + bool is_valid() const { return !m_impl.expired(); } + + // saves settings (i.e. the settings_pack) + static constexpr save_state_flags_t save_settings = 0_bit; + +#if TORRENT_ABI_VERSION <= 2 + // saves dht_settings. All DHT settings are now part of the main + // settings_pack, and saved by setting the save_settings flag + TORRENT_DEPRECATED static constexpr save_state_flags_t save_dht_settings = 1_bit; +#endif + + // saves dht state such as nodes and node-id, possibly accelerating + // joining the DHT if provided at next session startup. + static constexpr save_state_flags_t save_dht_state = 2_bit; + +#if TORRENT_ABI_VERSION == 1 + // save pe_settings + TORRENT_DEPRECATED static constexpr save_state_flags_t save_encryption_settings = 3_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_as_map = 4_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_proxy = 5_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_i2p_proxy = 6_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_dht_proxy = 7_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_peer_proxy = 8_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_web_proxy = 9_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_tracker_proxy = 10_bit; +#endif + + // load or save state from plugins + static constexpr save_state_flags_t save_extension_state = 11_bit; + + // load or save the IP filter set on the session + static constexpr save_state_flags_t save_ip_filter = 12_bit; + +#if TORRENT_ABI_VERSION <= 2 + // deprecated in 2.0 + // instead of these functions, use session_state() below, and restore + // state using the session_params on session construction. + + // loads and saves all session settings, including dht_settings, + // encryption settings and proxy settings. ``save_state`` writes all keys + // to the ``entry`` that's passed in, which needs to either not be + // initialized, or initialized as a dictionary. + // + // ``load_state`` expects a bdecode_node which can be built from a bencoded + // buffer with bdecode(). + // + // The ``flags`` argument is used to filter which parts of the session + // state to save or load. By default, all state is saved/restored (except + // for the individual torrents). + // + // When saving settings, there are two fields that are *not* loaded. + // ``peer_fingerprint`` and ``user_agent``. Those are left as configured + // by the ``session_settings`` passed to the session constructor or + // subsequently set via apply_settings(). + TORRENT_DEPRECATED + void save_state(entry& e, save_state_flags_t flags = save_state_flags_t::all()) const; + TORRENT_DEPRECATED + void load_state(bdecode_node const& e, save_state_flags_t flags = save_state_flags_t::all()); +#endif + + // returns the current session state. This can be passed to + // write_session_params() to save the state to disk and restored using + // read_session_params() when constructing a new session. The kind of + // state that's included is all settings, the DHT routing table, possibly + // plugin-specific state. + // the flags parameter can be used to only save certain parts of the + // session state + session_params session_state(save_state_flags_t flags = save_state_flags_t::all()) const; + + // .. note:: + // these calls are potentially expensive and won't scale well with + // lots of torrents. If you're concerned about performance, consider + // using ``post_torrent_updates()`` instead. + // + // ``get_torrent_status`` returns a vector of the torrent_status for + // every torrent which satisfies ``pred``, which is a predicate function + // which determines if a torrent should be included in the returned set + // or not. Returning true means it should be included and false means + // excluded. The ``flags`` argument is the same as to + // torrent_handle::status(). Since ``pred`` is guaranteed to be + // called for every torrent, it may be used to count the number of + // torrents of different categories as well. + // + // ``refresh_torrent_status`` takes a vector of torrent_status structs + // (for instance the same vector that was returned by + // get_torrent_status() ) and refreshes the status based on the + // ``handle`` member. It is possible to use this function by first + // setting up a vector of default constructed ``torrent_status`` objects, + // only initializing the ``handle`` member, in order to request the + // torrent status for multiple torrents in a single call. This can save a + // significant amount of time if you have a lot of torrents. + // + // Any torrent_status object whose ``handle`` member is not referring to + // a valid torrent are ignored. + // + // The intended use of these functions is to start off by calling + // ``get_torrent_status()`` to get a list of all torrents that match your + // criteria. Then call ``refresh_torrent_status()`` on that list. This + // will only refresh the status for the torrents in your list, and thus + // ignore all other torrents you might be running. This may save a + // significant amount of time, especially if the number of torrents you're + // interested in is small. In order to keep your list of interested + // torrents up to date, you can either call ``get_torrent_status()`` from + // time to time, to include torrents you might have become interested in + // since the last time. In order to stop refreshing a certain torrent, + // simply remove it from the list. + std::vector get_torrent_status( + std::function const& pred + , status_flags_t flags = {}) const; + void refresh_torrent_status(std::vector* ret + , status_flags_t flags = {}) const; + + // This functions instructs the session to post the state_update_alert, + // containing the status of all torrents whose state changed since the + // last time this function was called. + // + // Only torrents who has the state subscription flag set will be + // included. This flag is on by default. See add_torrent_params. + // the ``flags`` argument is the same as for torrent_handle::status(). + // see status_flags_t in torrent_handle. + void post_torrent_updates(status_flags_t flags = status_flags_t::all()); + + // This function will post a session_stats_alert object, containing a + // snapshot of the performance counters from the internals of libtorrent. + // To interpret these counters, query the session via + // session_stats_metrics(). + // + // For more information, see the session-statistics_ section. + void post_session_stats(); + + // This will cause a dht_stats_alert to be posted. + void post_dht_stats(); + + // internal + io_context& get_context(); + + // set the DHT state for the session. This will be taken into account the + // next time the DHT is started, as if it had been passed in via the + // session_params on startup. + void set_dht_state(dht::dht_state const& st); + void set_dht_state(dht::dht_state&& st); + + // ``find_torrent()`` looks for a torrent with the given info-hash. In + // case there is such a torrent in the session, a torrent_handle to that + // torrent is returned. In case the torrent cannot be found, an invalid + // torrent_handle is returned. + // + // See ``torrent_handle::is_valid()`` to know if the torrent was found or + // not. + // + // ``get_torrents()`` returns a vector of torrent_handles to all the + // torrents currently in the session. + torrent_handle find_torrent(sha1_hash const& info_hash) const; + std::vector get_torrents() const; + + // You add torrents through the add_torrent() function where you give an + // object with all the parameters. The add_torrent() overloads will block + // until the torrent has been added (or failed to be added) and returns + // an error code and a torrent_handle. In order to add torrents more + // efficiently, consider using async_add_torrent() which returns + // immediately, without waiting for the torrent to add. Notification of + // the torrent being added is sent as add_torrent_alert. + // + // The ``save_path`` field in add_torrent_params must be set to a valid + // path where the files for the torrent will be saved. Even when using a + // custom storage, this needs to be set to something. If the save_path + // is empty, the call to add_torrent() will throw a system_error + // exception. + // + // The overload that does not take an error_code throws an exception on + // error and is not available when building without exception support. + // The torrent_handle returned by add_torrent() can be used to retrieve + // information about the torrent's progress, its peers etc. It is also + // used to abort a torrent. + // + // If the torrent you are trying to add already exists in the session (is + // either queued for checking, being checked or downloading) + // ``add_torrent()`` will throw system_error which derives from + // ``std::exception`` unless duplicate_is_error is set to false. In that + // case, add_torrent() will return the handle to the existing torrent. + // + // The add_torrent_params class has a flags field. It can be used to + // control what state the new torrent will be added in. Common flags to + // want to control are torrent_flags::paused and + // torrent_flags::auto_managed. In order to add a magnet link that will + // just download the metadata, but no payload, set the + // torrent_flags::upload_mode flag. + // + // Special consideration has to be taken when adding hybrid torrents + // (i.e. torrents that are BitTorrent v2 torrents that are backwards + // compatible with v1). For more details, see BitTorrent-v2-torrents_. +#ifndef BOOST_NO_EXCEPTIONS + torrent_handle add_torrent(add_torrent_params&& params); + torrent_handle add_torrent(add_torrent_params const& params); +#endif + torrent_handle add_torrent(add_torrent_params&& params, error_code& ec); + torrent_handle add_torrent(add_torrent_params const& params, error_code& ec); + void async_add_torrent(add_torrent_params&& params); + void async_add_torrent(add_torrent_params const& params); + +#ifndef BOOST_NO_EXCEPTIONS +#if TORRENT_ABI_VERSION == 1 + // deprecated in 0.14 + TORRENT_DEPRECATED + torrent_handle add_torrent( + torrent_info const& ti + , std::string const& save_path + , entry const& resume_data = entry() + , storage_mode_t storage_mode = storage_mode_sparse + , bool paused = false); + + // deprecated in 0.14 + TORRENT_DEPRECATED + torrent_handle add_torrent( + char const* tracker_url + , sha1_hash const& info_hash + , char const* name + , std::string const& save_path + , entry const& resume_data = entry() + , storage_mode_t storage_mode = storage_mode_sparse + , bool paused = false + , client_data_t userdata = {}); +#endif // TORRENT_ABI_VERSION +#endif + + // Pausing the session has the same effect as pausing every torrent in + // it, except that torrents will not be resumed by the auto-manage + // mechanism. Resuming will restore the torrents to their previous paused + // state. i.e. the session pause state is separate from the torrent pause + // state. A torrent is inactive if it is paused or if the session is + // paused. + void pause(); + void resume(); + bool is_paused() const; + +#if TORRENT_ABI_VERSION == 1 + // *the feature of dynamically loading/unloading torrents is deprecated + // and discouraged* + // + // This function enables dynamic-loading-of-torrent-files_. When a + // torrent is unloaded but needs to be available in memory, this function + // is called **from within the libtorrent network thread**. From within + // this thread, you can **not** use any of the public APIs of libtorrent + // itself. The info-hash of the torrent is passed in to the function + // and it is expected to fill in the passed in ``vector`` with the + // .torrent file corresponding to it. + // + // If there is an error loading the torrent file, the ``error_code`` + // (``ec``) should be set to reflect the error. In such case, the torrent + // itself is stopped and set to an error state with the corresponding + // error code. + // + // Given that the function is called from the internal network thread of + // libtorrent, it's important to not stall. libtorrent will not be able + // to send nor receive any data until the function call returns. + // + // The signature of the function to pass in is:: + // + // void fun(sha1_hash const& info_hash, std::vector& buf, error_code& ec); + TORRENT_DEPRECATED + void set_load_function(user_load_function_t fun); + +#include "libtorrent/aux_/disable_warnings_push.hpp" + + // deprecated in libtorrent 1.1, use performance_counters instead + // returns session wide-statistics and status. For more information, see + // the ``session_status`` struct. + TORRENT_DEPRECATED + session_status status() const; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // deprecated in 1.2 + TORRENT_DEPRECATED + void get_torrent_status(std::vector* ret + , std::function const& pred + , status_flags_t flags = {}) const; + + // ``start_dht`` starts the dht node and makes the trackerless service + // available to torrents. + // + // ``stop_dht`` stops the dht node. + // deprecated. use settings_pack::enable_dht instead + TORRENT_DEPRECATED + void start_dht(); + TORRENT_DEPRECATED + void stop_dht(); +#endif + +#if TORRENT_ABI_VERSION <= 2 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + // ``set_dht_settings`` sets some parameters available to the dht node. + // See dht_settings for more information. + // + // ``get_dht_settings()`` returns the current settings + void set_dht_settings(dht::dht_settings const& settings); + dht::dht_settings get_dht_settings() const; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif + + // ``is_dht_running()`` returns true if the DHT support has been started + // and false otherwise. + bool is_dht_running() const; + + // ``set_dht_storage`` set a dht custom storage constructor function + // to be used internally when the dht is created. + // + // Since the dht storage is a critical component for the dht behavior, + // this function will only be effective the next time the dht is started. + // If you never touch this feature, a default map-memory based storage + // is used. + // + // If you want to make sure the dht is initially created with your + // custom storage, create a session with the setting + // ``settings_pack::enable_dht`` to false, set your constructor function + // and call ``apply_settings`` with ``settings_pack::enable_dht`` to true. + void set_dht_storage(dht::dht_storage_constructor_type sc); + + // ``add_dht_node`` takes a host name and port pair. That endpoint will be + // pinged, and if a valid DHT reply is received, the node will be added to + // the routing table. + void add_dht_node(std::pair const& node); + +#if TORRENT_ABI_VERSION == 1 + // deprecated, use settings_pack::dht_bootstrap_nodes instead + // + // ``add_dht_router`` adds the given endpoint to a list of DHT router + // nodes. If a search is ever made while the routing table is empty, + // those nodes will be used as backups. Nodes in the router node list + // will also never be added to the regular routing table, which + // effectively means they are only used for bootstrapping, to keep the + // load off them. + // + // An example routing node that you could typically add is + // ``router.bittorrent.com``. + TORRENT_DEPRECATED + void add_dht_router(std::pair const& node); +#endif + + // query the DHT for an immutable item at the ``target`` hash. + // the result is posted as a dht_immutable_item_alert. + void dht_get_item(sha1_hash const& target); + + // query the DHT for a mutable item under the public key ``key``. + // this is an ed25519 key. ``salt`` is optional and may be left + // as an empty string if no salt is to be used. + // if the item is found in the DHT, a dht_mutable_item_alert is + // posted. + void dht_get_item(std::array key + , std::string salt = std::string()); + + // store the given bencoded data as an immutable item in the DHT. + // the returned hash is the key that is to be used to look the item + // up again. It's just the SHA-1 hash of the bencoded form of the + // structure. + sha1_hash dht_put_item(entry data); + + // store a mutable item. The ``key`` is the public key the blob is + // to be stored under. The optional ``salt`` argument is a string that + // is to be mixed in with the key when determining where in the DHT + // the value is to be stored. The callback function is called from within + // the libtorrent network thread once we've found where to store the blob, + // possibly with the current value stored under the key. + // The values passed to the callback functions are: + // + // entry& value + // the current value stored under the key (may be empty). Also expected + // to be set to the value to be stored by the function. + // + // std::array& signature + // the signature authenticating the current value. This may be zeros + // if there is currently no value stored. The function is expected to + // fill in this buffer with the signature of the new value to store. + // To generate the signature, you may want to use the + // ``sign_mutable_item`` function. + // + // std::int64_t& seq + // current sequence number. May be zero if there is no current value. + // The function is expected to set this to the new sequence number of + // the value that is to be stored. Sequence numbers must be monotonically + // increasing. Attempting to overwrite a value with a lower or equal + // sequence number will fail, even if the signature is correct. + // + // std::string const& salt + // this is the salt that was used for this put call. + // + // Since the callback function ``cb`` is called from within libtorrent, + // it is critical to not perform any blocking operations. Ideally not + // even locking a mutex. Pass any data required for this function along + // with the function object's context and make the function entirely + // self-contained. The only reason data blob's value is computed + // via a function instead of just passing in the new value is to avoid + // race conditions. If you want to *update* the value in the DHT, you + // must first retrieve it, then modify it, then write it back. The way + // the DHT works, it is natural to always do a lookup before storing and + // calling the callback in between is convenient. + void dht_put_item(std::array key + , std::function& + , std::int64_t&, std::string const&)> cb + , std::string salt = std::string()); + + // ``dht_get_peers()`` will issue a DHT get_peer request to the DHT for the + // specified info-hash. The response (the peers) will be posted back in a + // dht_get_peers_reply_alert. + // + // ``dht_announce()`` will issue a DHT announce request to the DHT to the + // specified info-hash, advertising the specified port. If the port is + // left at its default, 0, the port will be implied by the DHT message's + // source port (which may improve connectivity through a NAT). + // ``dht_announce()`` is not affected by the ``announce_port`` override setting. + // + // Both these functions are exposed for advanced custom use of the DHT. + // All torrents eligible to be announce to the DHT will be automatically, + // by libtorrent. + // + // For possible flags, see announce_flags_t. + void dht_get_peers(sha1_hash const& info_hash); + void dht_announce(sha1_hash const& info_hash, int port = 0, dht::announce_flags_t flags = {}); + + // Retrieve all the live DHT (identified by ``nid``) nodes. All the + // nodes id and endpoint will be returned in the list of nodes in the + // alert ``dht_live_nodes_alert``. + // Since this alert is a response to an explicit call, it will always be + // posted, regardless of the alert mask. + void dht_live_nodes(sha1_hash const& nid); + + // Query the DHT node specified by ``ep`` to retrieve a sample of the + // info-hashes that the node currently have in their storage. + // The ``target`` is included for iterative lookups so that indexing nodes + // can perform a key space traversal with a single RPC per node by adjusting + // the target value for each RPC. It has no effect on the returned sample value. + // The result is posted as a ``dht_sample_infohashes_alert``. + void dht_sample_infohashes(udp::endpoint const& ep, sha1_hash const& target); + + // Send an arbitrary DHT request directly to the specified endpoint. This + // function is intended for use by plugins. When a response is received + // or the request times out, a dht_direct_response_alert will be posted + // with the response (if any) and the userdata pointer passed in here. + // Since this alert is a response to an explicit call, it will always be + // posted, regardless of the alert mask. + void dht_direct_request(udp::endpoint const& ep, entry const& e, client_data_t userdata = {}); + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 0.15 + // use save_state and load_state instead + TORRENT_DEPRECATED + entry dht_state() const; + TORRENT_DEPRECATED + void start_dht(entry const& startup_state); +#endif + + // This function adds an extension to this session. The argument is a + // function object that is called with a ``torrent_handle`` and which should + // return a ``std::shared_ptr``. To write custom + // plugins, see `libtorrent plugins`_. For the typical bittorrent client + // all of these extensions should be added. The main plugins implemented + // in libtorrent are: + // + // uTorrent metadata + // Allows peers to download the metadata (.torrent files) from the swarm + // directly. Makes it possible to join a swarm with just a tracker and + // info-hash. + // + // .. code:: c++ + // + // #include + // ses.add_extension(<::create_ut_metadata_plugin); + // + // uTorrent peer exchange + // Exchanges peers between clients. + // + // .. code:: c++ + // + // #include + // ses.add_extension(<::create_ut_pex_plugin); + // + // smart ban plugin + // A plugin that, with a small overhead, can ban peers + // that sends bad data with very high accuracy. Should + // eliminate most problems on poisoned torrents. + // + // .. code:: c++ + // + // #include + // ses.add_extension(<::create_smart_ban_plugin); + // + // + // .. _`libtorrent plugins`: reference-Plugins.html + void add_extension(std::function( + torrent_handle const&, client_data_t)> ext); + void add_extension(std::shared_ptr ext); + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 0.15 + // use load_state and save_state instead + TORRENT_DEPRECATED + void load_state(entry const& ses_state + , save_state_flags_t flags = save_state_flags_t::all()); + TORRENT_DEPRECATED + entry state() const; +#endif // TORRENT_ABI_VERSION + + // Sets a filter that will be used to reject and accept incoming as well + // as outgoing connections based on their originating ip address. The + // default filter will allow connections to any ip address. To build a + // set of rules for which addresses are accepted and not, see ip_filter. + // + // Each time a peer is blocked because of the IP filter, a + // peer_blocked_alert is generated. ``get_ip_filter()`` Returns the + // ip_filter currently in the session. See ip_filter. + void set_ip_filter(ip_filter f); + ip_filter get_ip_filter() const; + + // apply port_filter ``f`` to incoming and outgoing peers. a port filter + // will reject making outgoing peer connections to certain remote ports. + // The main intention is to be able to avoid triggering certain + // anti-virus software by connecting to SMTP, FTP ports. + void set_port_filter(port_filter const& f); + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.1, use settings_pack::peer_fingerprint instead + TORRENT_DEPRECATED + void set_peer_id(peer_id const& pid); + + // deprecated in 1.1.7. read settings_pack::peer_fingerprint instead + TORRENT_DEPRECATED + peer_id id() const; +#endif + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + // sets the key sent to trackers. If it's not set, it is initialized + // by libtorrent. The key may be used by the tracker to identify the + // peer potentially across you changing your IP. + void set_key(std::uint32_t key); +#endif + + // built-in peer classes + static constexpr peer_class_t global_peer_class_id{0}; + static constexpr peer_class_t tcp_peer_class_id{1}; + static constexpr peer_class_t local_peer_class_id{2}; + + // ``is_listening()`` will tell you whether or not the session has + // successfully opened a listening port. If it hasn't, this function will + // return false, and then you can set a new + // settings_pack::listen_interfaces to try another interface and port to + // bind to. + // + // ``listen_port()`` returns the port we ended up listening on. + unsigned short listen_port() const; + unsigned short ssl_listen_port() const; + bool is_listening() const; + + // Sets the peer class filter for this session. All new peer connections + // will take this into account and be added to the peer classes specified + // by this filter, based on the peer's IP address. + // + // The ip-filter essentially maps an IP -> uint32. Each bit in that 32 + // bit integer represents a peer class. The least significant bit + // represents class 0, the next bit class 1 and so on. + // + // For more info, see ip_filter. + // + // For example, to make all peers in the range 200.1.1.0 - 200.1.255.255 + // belong to their own peer class, apply the following filter: + // + // .. code:: c++ + // + // ip_filter f = ses.get_peer_class_filter(); + // peer_class_t my_class = ses.create_peer_class("200.1.x.x IP range"); + // f.add_rule(make_address("200.1.1.0"), make_address("200.1.255.255") + // , 1 << static_cast(my_class)); + // ses.set_peer_class_filter(f); + // + // This setting only applies to new connections, it won't affect existing + // peer connections. + // + // This function is limited to only peer class 0-31, since there are only + // 32 bits in the IP range mapping. Only the set bits matter; no peer + // class will be removed from a peer as a result of this call, peer + // classes are only added. + // + // The ``peer_class`` argument cannot be greater than 31. The bitmasks + // representing peer classes in the ``peer_class_filter`` are 32 bits. + // + // The ``get_peer_class_filter()`` function returns the current filter. + // + // For more information, see peer-classes_. + void set_peer_class_filter(ip_filter const& f); + ip_filter get_peer_class_filter() const; + + // Sets and gets the *peer class type filter*. This is controls automatic + // peer class assignments to peers based on what kind of socket it is. + // + // It does not only support assigning peer classes, it also supports + // removing peer classes based on socket type. + // + // The order of these rules being applied are: + // + // 1. peer-class IP filter + // 2. peer-class type filter, removing classes + // 3. peer-class type filter, adding classes + // + // For more information, see peer-classes_. + void set_peer_class_type_filter(peer_class_type_filter const& f); + peer_class_type_filter get_peer_class_type_filter() const; + + // Creates a new peer class (see peer-classes_) with the given name. The + // returned integer is the new peer class identifier. Peer classes may + // have the same name, so each invocation of this function creates a new + // class and returns a unique identifier. + // + // Identifiers are assigned from low numbers to higher. So if you plan on + // using certain peer classes in a call to set_peer_class_filter(), + // make sure to create those early on, to get low identifiers. + // + // For more information on peer classes, see peer-classes_. + peer_class_t create_peer_class(char const* name); + + // This call dereferences the reference count of the specified peer + // class. When creating a peer class it's automatically referenced by 1. + // If you want to recycle a peer class, you may call this function. You + // may only call this function **once** per peer class you create. + // Calling it more than once for the same class will lead to memory + // corruption. + // + // Since peer classes are reference counted, this function will not + // remove the peer class if it's still assigned to torrents or peers. It + // will however remove it once the last peer and torrent drops their + // references to it. + // + // There is no need to call this function for custom peer classes. All + // peer classes will be properly destructed when the session object + // destructs. + // + // For more information on peer classes, see peer-classes_. + void delete_peer_class(peer_class_t cid); + + // These functions queries information from a peer class and updates the + // configuration of a peer class, respectively. + // + // ``cid`` must refer to an existing peer class. If it does not, the + // return value of ``get_peer_class()`` is undefined. + // + // ``set_peer_class()`` sets all the information in the + // peer_class_info object in the specified peer class. There is no + // option to only update a single property. + // + // A peer or torrent belonging to more than one class, the highest + // priority among any of its classes is the one that is taken into + // account. + // + // For more information, see peer-classes_. + peer_class_info get_peer_class(peer_class_t cid) const; + void set_peer_class(peer_class_t cid, peer_class_info const& pci); + +#if TORRENT_ABI_VERSION == 1 + // if the listen port failed in some way you can retry to listen on + // another port- range with this function. If the listener succeeded and + // is currently listening, a call to this function will shut down the + // listen port and reopen it using these new properties (the given + // interface and port range). As usual, if the interface is left as 0 + // this function will return false on failure. If it fails, it will also + // generate alerts describing the error. It will return true on success. + enum listen_on_flags_t + { + // this is always on starting with 0.16.2 + listen_reuse_address TORRENT_DEPRECATED_ENUM = 0x01, + listen_no_system_port TORRENT_DEPRECATED_ENUM = 0x02 + }; + + // deprecated in 0.16 + + // specify which interfaces to bind outgoing connections to + // This has been moved to a session setting + TORRENT_DEPRECATED + void use_interfaces(char const* interfaces); + + // instead of using this, specify listen interface and port in + // the settings_pack::listen_interfaces setting + TORRENT_DEPRECATED + void listen_on( + std::pair const& port_range + , error_code& ec + , const char* net_interface = nullptr + , int flags = 0); +#endif + + // delete the files belonging to the torrent from disk. + // including the part-file, if there is one + static constexpr remove_flags_t delete_files = 0_bit; + + // delete just the part-file associated with this torrent + static constexpr remove_flags_t delete_partfile = 1_bit; + +#if TORRENT_ABI_VERSION <= 2 + // this will add common extensions like ut_pex, ut_metadata, lt_tex + // smart_ban and possibly others. + TORRENT_DEPRECATED static constexpr session_flags_t add_default_plugins = 0_bit; +#endif + +#if TORRENT_ABI_VERSION == 1 + // this will start features like DHT, local service discovery, UPnP + // and NAT-PMP. + TORRENT_DEPRECATED static constexpr session_flags_t start_default_features = 1_bit; +#endif + + // when set, the session will start paused. Call + // session_handle::resume() to start + static constexpr session_flags_t paused = 2_bit; + + // ``remove_torrent()`` will close all peer connections associated with + // the torrent and tell the tracker that we've stopped participating in + // the swarm. This operation cannot fail. When it completes, you will + // receive a torrent_removed_alert. + // + // remove_torrent() is non-blocking, but will remove the torrent from the + // session synchronously. Calling session_handle::add_torrent() immediately + // afterward with the same torrent will succeed. Note that this creates a + // new handle which is not equal to the removed one. + // + // The optional second argument ``options`` can be used to delete all the + // files downloaded by this torrent. To do so, pass in the value + // ``session_handle::delete_files``. Once the torrent is deleted, a + // torrent_deleted_alert is posted. + // + // The torrent_handle remains valid for some time after remove_torrent() is + // called. It will become invalid only after all libtorrent tasks (such as + // I/O tasks) release their references to the torrent. Until this happens, + // torrent_handle::is_valid() will return true, and other calls such + // as torrent_handle::status() will succeed. Because of this, and because + // remove_torrent() is non-blocking, the following sequence usually + // succeeds (does not throw system_error): + // .. code:: c++ + // + // session.remove_handle(handle); + // handle.save_resume_data(); + // + // Note that when a queued or downloading torrent is removed, its position + // in the download queue is vacated and every subsequent torrent in the + // queue has their queue positions updated. This can potentially cause a + // large state_update to be posted. When removing all torrents, it is + // advised to remove them from the back of the queue, to minimize the + // shifting. + void remove_torrent(const torrent_handle&, remove_flags_t = {}); + + // Applies the settings specified by the settings_pack ``s``. This is an + // asynchronous operation that will return immediately and actually apply + // the settings to the main thread of libtorrent some time later. + void apply_settings(settings_pack const&); + void apply_settings(settings_pack&&); + settings_pack get_settings() const; + +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // deprecated in libtorrent 1.1. use settings_pack instead + TORRENT_DEPRECATED + void set_pe_settings(pe_settings const&); + TORRENT_DEPRECATED + pe_settings get_pe_settings() const; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // ``set_i2p_proxy`` sets the i2p_ proxy, and tries to open a persistent + // connection to it. The only used fields in the proxy settings structs + // are ``hostname`` and ``port``. + // + // ``i2p_proxy`` returns the current i2p proxy in use. + // + // .. _i2p: http://www.i2p2.de + + TORRENT_DEPRECATED + void set_i2p_proxy(proxy_settings const&); + TORRENT_DEPRECATED + proxy_settings i2p_proxy() const; + + // These functions sets and queries the proxy settings to be used for the + // session. + // + // For more information on what settings are available for proxies, see + // proxy_settings. If the session is not in anonymous mode, proxies that + // aren't working or fail, will automatically be disabled and packets + // will flow without using any proxy. If you want to enforce using a + // proxy, even when the proxy doesn't work, enable anonymous_mode in + // settings_pack. + TORRENT_DEPRECATED + void set_proxy(proxy_settings const& s); + TORRENT_DEPRECATED + proxy_settings proxy() const; + + // deprecated in 0.16 + // Get the number of uploads. + TORRENT_DEPRECATED + int num_uploads() const; + + // Get the number of connections. This number also contains the + // number of half open connections. + TORRENT_DEPRECATED + int num_connections() const; + + // deprecated in 0.15. + TORRENT_DEPRECATED + void set_peer_proxy(proxy_settings const& s); + TORRENT_DEPRECATED + void set_web_seed_proxy(proxy_settings const& s); + TORRENT_DEPRECATED + void set_tracker_proxy(proxy_settings const& s); + + TORRENT_DEPRECATED + proxy_settings peer_proxy() const; + TORRENT_DEPRECATED + proxy_settings web_seed_proxy() const; + TORRENT_DEPRECATED + proxy_settings tracker_proxy() const; + + TORRENT_DEPRECATED + void set_dht_proxy(proxy_settings const& s); + TORRENT_DEPRECATED + proxy_settings dht_proxy() const; + + // deprecated in 0.16 + TORRENT_DEPRECATED + int upload_rate_limit() const; + TORRENT_DEPRECATED + int download_rate_limit() const; + TORRENT_DEPRECATED + int local_upload_rate_limit() const; + TORRENT_DEPRECATED + int local_download_rate_limit() const; + TORRENT_DEPRECATED + int max_half_open_connections() const; + + TORRENT_DEPRECATED + void set_local_upload_rate_limit(int bytes_per_second); + TORRENT_DEPRECATED + void set_local_download_rate_limit(int bytes_per_second); + TORRENT_DEPRECATED + void set_upload_rate_limit(int bytes_per_second); + TORRENT_DEPRECATED + void set_download_rate_limit(int bytes_per_second); + TORRENT_DEPRECATED + void set_max_uploads(int limit); + TORRENT_DEPRECATED + void set_max_connections(int limit); + TORRENT_DEPRECATED + void set_max_half_open_connections(int limit); + + TORRENT_DEPRECATED + int max_connections() const; + TORRENT_DEPRECATED + int max_uploads() const; + +#endif + + // Alerts is the main mechanism for libtorrent to report errors and + // events. ``pop_alerts`` fills in the vector passed to it with pointers + // to new alerts. The session still owns these alerts and they will stay + // valid until the next time ``pop_alerts`` is called. You may not delete + // the alert objects. + // + // It is safe to call ``pop_alerts`` from multiple different threads, as + // long as the alerts themselves are not accessed once another thread + // calls ``pop_alerts``. Doing this requires manual synchronization + // between the popping threads. + // + // ``wait_for_alert`` will block the current thread for ``max_wait`` time + // duration, or until another alert is posted. If an alert is available + // at the time of the call, it returns immediately. The returned alert + // pointer is the head of the alert queue. ``wait_for_alert`` does not + // pop alerts from the queue, it merely peeks at it. The returned alert + // will stay valid until ``pop_alerts`` is called twice. The first time + // will pop it and the second will free it. + // + // If there is no alert in the queue and no alert arrives within the + // specified timeout, ``wait_for_alert`` returns nullptr. + // + // In the python binding, ``wait_for_alert`` takes the number of + // milliseconds to wait as an integer. + // + // The alert queue in the session will not grow indefinitely. Make sure + // to pop periodically to not miss notifications. To control the max + // number of alerts that's queued by the session, see + // ``settings_pack::alert_queue_size``. + // + // Some alerts are considered so important that they are posted even when + // the alert queue is full. Some alerts are considered mandatory and cannot + // be disabled by the ``alert_mask``. For instance, + // save_resume_data_alert and save_resume_data_failed_alert are always + // posted, regardless of the alert mask. + // + // To control which alerts are posted, set the alert_mask + // (settings_pack::alert_mask). + // + // If the alert queue fills up to the point where alerts are dropped, this + // will be indicated by a alerts_dropped_alert, which contains a bitmask + // of which types of alerts were dropped. Generally it is a good idea to + // make sure the alert queue is large enough, the alert_mask doesn't have + // unnecessary categories enabled and to call pop_alert() frequently, to + // avoid alerts being dropped. + // + // the ``set_alert_notify`` function lets the client set a function object + // to be invoked every time the alert queue goes from having 0 alerts to + // 1 alert. This function is called from within libtorrent, it may be the + // main thread, or it may be from within a user call. The intention of + // of the function is that the client wakes up its main thread, to poll + // for more alerts using ``pop_alerts()``. If the notify function fails + // to do so, it won't be called again, until ``pop_alerts`` is called for + // some other reason. For instance, it could signal an eventfd, post a + // message to an HWND or some other main message pump. The actual + // retrieval of alerts should not be done in the callback. In fact, the + // callback should not block. It should not perform any expensive work. + // It really should just notify the main application thread. + // + // The type of an alert is returned by the polymorphic function + // ``alert::type()`` but can also be queries from a concrete type via + // ``T::alert_type``, as a static constant. + void pop_alerts(std::vector* alerts); + alert* wait_for_alert(time_duration max_wait); + void set_alert_notify(std::function const& fun); + +#if TORRENT_ABI_VERSION == 1 + // use the setting instead + TORRENT_DEPRECATED + size_t set_alert_queue_size_limit(size_t queue_size_limit_); + + // Changes the mask of which alerts to receive. By default only errors + // are reported. ``m`` is a bitmask where each bit represents a category + // of alerts. + // + // ``get_alert_mask()`` returns the current mask; + // + // See category_t enum for options. + TORRENT_DEPRECATED + void set_alert_mask(std::uint32_t m); + TORRENT_DEPRECATED + std::uint32_t get_alert_mask() const; + + // Starts and stops Local Service Discovery. This service will broadcast + // the info-hashes of all the non-private torrents on the local network to + // look for peers on the same swarm within multicast reach. + // + // deprecated. use settings_pack::enable_lsd instead + TORRENT_DEPRECATED + void start_lsd(); + TORRENT_DEPRECATED + void stop_lsd(); + + // Starts and stops the UPnP service. When started, the listen port and + // the DHT port are attempted to be forwarded on local UPnP router + // devices. + // + // The upnp object returned by ``start_upnp()`` can be used to add and + // remove arbitrary port mappings. Mapping status is returned through the + // portmap_alert and the portmap_error_alert. The object will be valid + // until ``stop_upnp()`` is called. See upnp-and-nat-pmp_. + // + // deprecated. use settings_pack::enable_upnp instead + TORRENT_DEPRECATED + void start_upnp(); + TORRENT_DEPRECATED + void stop_upnp(); + + // Starts and stops the NAT-PMP service. When started, the listen port + // and the DHT port are attempted to be forwarded on the router through + // NAT-PMP. + // + // The natpmp object returned by ``start_natpmp()`` can be used to add + // and remove arbitrary port mappings. Mapping status is returned through + // the portmap_alert and the portmap_error_alert. The object will be + // valid until ``stop_natpmp()`` is called. See upnp-and-nat-pmp_. + // + // deprecated. use settings_pack::enable_natpmp instead + TORRENT_DEPRECATED + void start_natpmp(); + TORRENT_DEPRECATED + void stop_natpmp(); +#endif + + // protocols used by add_port_mapping() + static constexpr portmap_protocol udp = portmap_protocol::udp; + static constexpr portmap_protocol tcp = portmap_protocol::tcp; + + // add_port_mapping adds one or more port forwards on UPnP and/or NAT-PMP, + // whichever is enabled. A mapping is created for each listen socket + // in the session. The return values are all handles referring to the + // port mappings that were just created. Pass them to delete_port_mapping() + // to remove them. + std::vector add_port_mapping(portmap_protocol t, int external_port, int local_port); + void delete_port_mapping(port_mapping_t handle); + + // This option indicates if the ports are mapped using natpmp + // and upnp. If mapping was already made, they are deleted and added + // again. This only works if natpmp and/or upnp are configured to be + // enable. + static constexpr reopen_network_flags_t reopen_map_ports = 0_bit; + + // Instructs the session to reopen all listen and outgoing sockets. + // + // It's useful in the case your platform doesn't support the built in + // IP notifier mechanism, or if you have a better more reliable way to + // detect changes in the IP routing table. + void reopen_network_sockets(reopen_network_flags_t options = reopen_map_ports); + + // This function is intended only for use by plugins. This type does + // not have a stable API and should be relied on as little as possible. + std::shared_ptr native_handle() const + { return m_impl.lock(); } + + private: + + template + void async_call(Fun f, Args&&... a) const; + + template + void sync_call(Fun f, Args&&... a) const; + + template + Ret sync_call_ret(Fun f, Args&&... a) const; + + explicit session_handle(std::weak_ptr impl) + : m_impl(std::move(impl)) + {} + + std::weak_ptr m_impl; + }; + +} // namespace libtorrent + +#endif // TORRENT_SESSION_HANDLE_HPP_INCLUDED diff --git a/docs/include/libtorrent/session_params.hpp b/docs/include/libtorrent/session_params.hpp new file mode 100644 index 0000000..49b0eb2 --- /dev/null +++ b/docs/include/libtorrent/session_params.hpp @@ -0,0 +1,156 @@ +/* + +Copyright (c) 2015, Steven Siloti +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_PARAMS_HPP_INCLUDED +#define TORRENT_SESSION_PARAMS_HPP_INCLUDED + +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/kademlia/dht_state.hpp" +#include "libtorrent/session_types.hpp" +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/ip_filter.hpp" + +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/kademlia/dht_settings.hpp" +#endif + +namespace libtorrent { + +TORRENT_VERSION_NAMESPACE_3 +struct plugin; +TORRENT_VERSION_NAMESPACE_3_END + +struct disk_interface; +struct counters; + +using disk_io_constructor_type = std::function( + io_context&, settings_interface const&, counters&)>; + +TORRENT_VERSION_NAMESPACE_3 + +// The session_params is a parameters pack for configuring the session +// before it's started. +struct TORRENT_EXPORT session_params +{ + // This constructor can be used to start with the default plugins + // (ut_metadata, ut_pex and smart_ban). Pass a settings_pack to set the + // initial settings when the session starts. + session_params(settings_pack&& sp); // NOLINT + session_params(settings_pack const& sp); // NOLINT + session_params(); + + // hidden + ~session_params(); + + // This constructor helps to configure the set of initial plugins + // to be added to the session before it's started. + session_params(settings_pack&& sp + , std::vector> exts); + session_params(settings_pack const& sp + , std::vector> exts); + + // hidden + session_params(session_params const&); + session_params(session_params&&); + session_params& operator=(session_params const&) &; + session_params& operator=(session_params&&) &; + + // The settings to configure the session with + settings_pack settings; + + // the plugins to add to the session as it is constructed + std::vector> extensions; + +#if TORRENT_ABI_VERSION <= 2 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // this is deprecated. Use the dht_* settings instead. + dht::dht_settings dht_settings; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif + + // DHT node ID and node addresses to bootstrap the DHT with. + dht::dht_state dht_state; + + // function object to construct the storage object for DHT items. + dht::dht_storage_constructor_type dht_storage_constructor; + + // function object to create the disk I/O subsystem. Defaults to + // default_disk_io_constructor. + disk_io_constructor_type disk_io_constructor; + + // this container can be used by extensions/plugins to store settings. It's + // primarily here to make it convenient to save and restore state across + // sessions, using read_session_params() and write_session_params(). + std::map ext_state; + + // the IP filter to use for the session. This restricts which peers are allowed + // to connect. As if passed to set_ip_filter(). + libtorrent::ip_filter ip_filter; +}; + +TORRENT_VERSION_NAMESPACE_3_END + +// These functions serialize and de-serialize a ``session_params`` object to and +// from bencoded form. The session_params object is used to initialize a new +// session using the state from a previous one (or by programmatically configure +// the session up-front). +// The flags parameter can be used to only save and load certain aspects of the +// session's state. +// The ``_buf`` suffix indicates the function operates on buffer rather than the +// bencoded structure. +// The torrents in a session are not part of the session_params state, they have +// to be restored separately. +TORRENT_EXPORT session_params read_session_params(bdecode_node const& e + , save_state_flags_t flags = save_state_flags_t::all()); +TORRENT_EXPORT session_params read_session_params(span buf + , save_state_flags_t flags = save_state_flags_t::all()); +TORRENT_EXPORT entry write_session_params(session_params const& sp + , save_state_flags_t flags = save_state_flags_t::all()); +TORRENT_EXPORT std::vector write_session_params_buf(session_params const& sp + , save_state_flags_t flags = save_state_flags_t::all()); + +} + +#endif diff --git a/docs/include/libtorrent/session_settings.hpp b/docs/include/libtorrent/session_settings.hpp new file mode 100644 index 0000000..a4ae9d6 --- /dev/null +++ b/docs/include/libtorrent/session_settings.hpp @@ -0,0 +1,118 @@ +/* + +Copyright (c) 2006-2007, 2010, 2013-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_SETTINGS_HPP_INCLUDED +#define TORRENT_SESSION_SETTINGS_HPP_INCLUDED + +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/config.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/aux_/proxy_settings.hpp" +#include "libtorrent/kademlia/dht_settings.hpp" + +#include + +namespace libtorrent { + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + using dht_settings = dht::dht_settings; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + using aux::proxy_settings; + + // The ``pe_settings`` structure is used to control the settings related + // to peer protocol encryption. + struct TORRENT_DEPRECATED_EXPORT pe_settings + { + // initializes the encryption settings with the default values + pe_settings() + : out_enc_policy(enabled) + , in_enc_policy(enabled) + , allowed_enc_level(both) + , prefer_rc4(false) + {} + + // the encoding policy options for use with pe_settings::out_enc_policy + // and pe_settings::in_enc_policy. + enum enc_policy + { + // Only encrypted connections are allowed. Incoming connections that + // are not encrypted are closed and if the encrypted outgoing + // connection fails, a non-encrypted retry will not be made. + forced, + + // encrypted connections are enabled, but non-encrypted connections + // are allowed. An incoming non-encrypted connection will be accepted, + // and if an outgoing encrypted connection fails, a non- encrypted + // connection will be tried. + enabled, + + // only non-encrypted connections are allowed. + disabled + }; + + // the encryption levels, to be used with pe_settings::allowed_enc_level. + enum enc_level + { + // use only plaintext encryption + plaintext = 1, + // use only rc4 encryption + rc4 = 2, + // allow both + both = 3 + }; + + // control the settings for incoming + // and outgoing connections respectively. + // see enc_policy enum for the available options. + std::uint8_t out_enc_policy; + std::uint8_t in_enc_policy; + + // determines the encryption level of the + // connections. This setting will adjust which encryption scheme is + // offered to the other peer, as well as which encryption scheme is + // selected by the client. See enc_level enum for options. + std::uint8_t allowed_enc_level; + + // if the allowed encryption level is both, setting this to + // true will prefer rc4 if both methods are offered, plaintext + // otherwise + bool prefer_rc4; + }; +} + +#endif // TORRENT_ABI_VERSION +#endif diff --git a/docs/include/libtorrent/session_stats.hpp b/docs/include/libtorrent/session_stats.hpp new file mode 100644 index 0000000..d36fb86 --- /dev/null +++ b/docs/include/libtorrent/session_stats.hpp @@ -0,0 +1,79 @@ +/* + +Copyright (c) 2015, 2017-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_STATS_HPP_INCLUDED +#define TORRENT_SESSION_STATS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/string_view.hpp" + +#include + +namespace libtorrent { + + enum class metric_type_t + { + counter, gauge + }; + + // describes one statistics metric from the session. For more information, + // see the session-statistics_ section. + struct TORRENT_EXPORT stats_metric + { + // the name of the counter or gauge + char const* name; + + // the index into the session stats array, where the underlying value of + // this counter or gauge is found. The session stats array is part of the + // session_stats_alert object. + int value_index; +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED static constexpr metric_type_t type_counter = metric_type_t::counter; + TORRENT_DEPRECATED static constexpr metric_type_t type_gauge = metric_type_t::gauge; +#endif + metric_type_t type; + }; + + // This free function returns the list of available metrics exposed by + // libtorrent's statistics API. Each metric has a name and a *value index*. + // The value index is the index into the array in session_stats_alert where + // this metric's value can be found when the session stats is sampled (by + // calling post_session_stats()). + TORRENT_EXPORT std::vector session_stats_metrics(); + + // given a name of a metric, this function returns the counter index of it, + // or -1 if it could not be found. The counter index is the index into the + // values array returned by session_stats_alert. + TORRENT_EXPORT int find_metric_idx(string_view name); +} + +#endif diff --git a/docs/include/libtorrent/session_status.hpp b/docs/include/libtorrent/session_status.hpp new file mode 100644 index 0000000..34ec416 --- /dev/null +++ b/docs/include/libtorrent/session_status.hpp @@ -0,0 +1,238 @@ +/* + +Copyright (c) 2006, 2008-2011, 2013-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, Falco +Copyright (c) 2017, Falcosc +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_STATUS_HPP_INCLUDED +#define TORRENT_SESSION_STATUS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/export.hpp" +#include + +#if TORRENT_ABI_VERSION == 1 +// for dht_lookup and dht_routing_bucket +#include "libtorrent/alert_types.hpp" +#endif + +#if TORRENT_ABI_VERSION == 1 +namespace libtorrent { + + // holds counters and gauges for the uTP sockets + // deprecated in 1.1 in favor of session_stats counters, which is a more + // flexible, extensible and performant mechanism for stats. + struct TORRENT_DEPRECATED_EXPORT utp_status + { + // gauges. These are snapshots of the number of + // uTP sockets in each respective state + int num_idle; + int num_syn_sent; + int num_connected; + int num_fin_sent; + int num_close_wait; + + // These are monotonically increasing + // and cumulative counters for their respective event. + std::uint64_t packet_loss; + std::uint64_t timeout; + std::uint64_t packets_in; + std::uint64_t packets_out; + std::uint64_t fast_retransmit; + std::uint64_t packet_resend; + std::uint64_t samples_above_target; + std::uint64_t samples_below_target; + std::uint64_t payload_pkts_in; + std::uint64_t payload_pkts_out; + std::uint64_t invalid_pkts_in; + std::uint64_t redundant_pkts_in; + }; + + // contains session wide state and counters + // deprecated in 1.1 in favor of session_stats counters, which is a more + // flexible, extensible and performant mechanism for stats. + struct TORRENT_DEPRECATED_EXPORT session_status + { + // false as long as no incoming connections have been + // established on the listening socket. Every time you change the listen port, this will + // be reset to false. + bool has_incoming_connections; + + // the total download and upload rates accumulated + // from all torrents. This includes bittorrent protocol, DHT and an estimated TCP/IP + // protocol overhead. + // deprecated, use session_stats_metrics "net.recv_bytes" + "net.recv_ip_overhead_bytes" + // they does include payload + protocol + ip overhead bytes + int upload_rate; + int download_rate; + + // the total number of bytes downloaded and + // uploaded to and from all torrents. This also includes all the protocol overhead. + // deprecated, use session_stats_metrics "net.recv_bytes" + "net.recv_ip_overhead_bytes" + // they does include payload + protocol + ip overhead bytes + std::int64_t total_download; + std::int64_t total_upload; + + // the rate of the payload + // down- and upload only. + // deprecated, use session_stats_metrics "net.recv_payload_bytes" + int payload_upload_rate; + // deprecated, use session_stats_metrics "net.sent_payload_bytes" + int payload_download_rate; + + // the total transfers of payload + // only. The payload does not include the bittorrent protocol overhead, but only parts of the + // actual files to be downloaded. + // ``total_payload_download`` is deprecated, use session_stats_metrics + // "net.recv_payload_bytes" ``total_payload_upload`` is deprecated, use + // session_stats_metrics "net.sent_payload_bytes" + std::int64_t total_payload_download; + std::int64_t total_payload_upload; + + // the estimated TCP/IP overhead in each direction. + int ip_overhead_upload_rate; + int ip_overhead_download_rate; + std::int64_t total_ip_overhead_download; + std::int64_t total_ip_overhead_upload; + + // the upload and download rate used by DHT traffic. Also the total number + // of bytes sent and received to and from the DHT. + int dht_upload_rate; + int dht_download_rate; + std::int64_t total_dht_download; + std::int64_t total_dht_upload; + + // the upload and download rate used by tracker traffic. Also the total number + // of bytes sent and received to and from trackers. + int tracker_upload_rate; + int tracker_download_rate; + std::int64_t total_tracker_download; + std::int64_t total_tracker_upload; + + // the number of bytes that has been received more than once. + // This can happen if a request from a peer times out and is requested from a different + // peer, and then received again from the first one. To make this lower, increase the + // ``request_timeout`` and the ``piece_timeout`` in the session settings. + std::int64_t total_redundant_bytes; + + // the number of bytes that was downloaded which later failed + // the hash-check. + std::int64_t total_failed_bytes; + + // the total number of peer connections this session has. This includes + // incoming connections that still hasn't sent their handshake or outgoing connections + // that still hasn't completed the TCP connection. This number may be slightly higher + // than the sum of all peers of all torrents because the incoming connections may not + // be assigned a torrent yet. + int num_peers; + + int num_dead_peers; + + // the current number of unchoked peers. + int num_unchoked; + + // the current allowed number of unchoked peers. + int allowed_upload_slots; + + // the number of peers that are + // waiting for more bandwidth quota from the torrent rate limiter. + int up_bandwidth_queue; + int down_bandwidth_queue; + + // count the number of + // bytes the connections are waiting for to be able to send and receive. + int up_bandwidth_bytes_queue; + int down_bandwidth_bytes_queue; + + // tells the number of + // seconds until the next optimistic unchoke change and the start of the next + // unchoke interval. These numbers may be reset prematurely if a peer that is + // unchoked disconnects or becomes not interested. + int optimistic_unchoke_counter; + int unchoke_counter; + + // the number of peers currently + // waiting on a disk write or disk read to complete before it receives or sends + // any more data on the socket. It'a a metric of how disk bound you are. + int disk_write_queue; + int disk_read_queue; + + // only available when + // built with DHT support. They are all set to 0 if the DHT isn't running. When + // the DHT is running, ``dht_nodes`` is set to the number of nodes in the routing + // table. This number only includes *active* nodes, not cache nodes. The + // ``dht_node_cache`` is set to the number of nodes in the node cache. These nodes + // are used to replace the regular nodes in the routing table in case any of them + // becomes unresponsive. + // deprecated, use session_stats_metrics "dht.dht_nodes" and "dht.dht_nodes_cache" + int dht_nodes; + int dht_node_cache; + + // the number of torrents tracked by the DHT at the moment. + int dht_torrents; + + // an estimation of the total number of nodes in the DHT + // network. + std::int64_t dht_global_nodes; + + // a vector of the currently running DHT lookups. + std::vector active_requests; + + // contains information about every bucket in the DHT routing + // table. + std::vector dht_routing_table; + + // the number of nodes allocated dynamically for a + // particular DHT lookup. This represents roughly the amount of memory used + // by the DHT. + int dht_total_allocations; + +#include "libtorrent/aux_/disable_warnings_push.hpp" + + // statistics on the uTP sockets. + utp_status utp_stats; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // the number of known peers across all torrents. These are not necessarily + // connected peers, just peers we know of. + int peerlist_size; + + // the number of torrents in the + // session and the number of them that are currently paused, respectively. + int num_torrents; + int num_paused_torrents; + }; +} +#endif // TORRENT_ABI_VERSION + +#endif // TORRENT_SESSION_STATUS_HPP_INCLUDED diff --git a/docs/include/libtorrent/session_types.hpp b/docs/include/libtorrent/session_types.hpp new file mode 100644 index 0000000..f897095 --- /dev/null +++ b/docs/include/libtorrent/session_types.hpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2017, Alden Torres +Copyright (c) 2017-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_TYPES_HPP_INCLUDED +#define TORRENT_SESSION_TYPES_HPP_INCLUDED + +#include +#include "libtorrent/flags.hpp" + +namespace libtorrent { + + // hidden + using save_state_flags_t = flags::bitfield_flag; + + // hidden + using session_flags_t = flags::bitfield_flag; + + // The flags type used to specify options to removing files of torrents + using remove_flags_t = flags::bitfield_flag; + + // hidden + using reopen_network_flags_t = flags::bitfield_flag; +} + +#endif + diff --git a/docs/include/libtorrent/settings_pack.hpp b/docs/include/libtorrent/settings_pack.hpp new file mode 100644 index 0000000..b35729d --- /dev/null +++ b/docs/include/libtorrent/settings_pack.hpp @@ -0,0 +1,2263 @@ +/* + +Copyright (c) 2014-2022, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, TheOriginalWinCat +Copyright (c) 2019, Amir Abrams +Copyright (c) 2022, Kevin Bracey +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SETTINGS_PACK_HPP_INCLUDED +#define TORRENT_SETTINGS_PACK_HPP_INCLUDED + +#include "libtorrent/entry.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/flags.hpp" + +#include +#include + +// OVERVIEW +// +// You have some control over session configuration through the session::apply_settings() +// member function. To change one or more configuration options, create a settings_pack +// object and fill it with the settings to be set and pass it in to session::apply_settings(). +// +// The settings_pack object is a collection of settings updates that are applied +// to the session when passed to session::apply_settings(). It's empty when +// constructed. +// +// You have control over proxy and authorization settings and also the user-agent +// that will be sent to the tracker. The user-agent will also be used to identify the +// client with other peers. +// +// Each configuration option is named with an enum value inside the +// settings_pack class. These are the available settings: +namespace libtorrent { + +namespace aux { + struct session_impl; + struct session_settings; + struct session_settings_single_thread; +} + + struct settings_pack; + struct bdecode_node; + + TORRENT_EXTRA_EXPORT settings_pack load_pack_from_dict(bdecode_node const& settings); + + TORRENT_EXTRA_EXPORT void save_settings_to_dict(settings_pack const& sett, entry::dictionary_type& out); + TORRENT_EXTRA_EXPORT settings_pack non_default_settings(aux::session_settings const& sett); + TORRENT_EXTRA_EXPORT void apply_pack(settings_pack const* pack, aux::session_settings& sett + , aux::session_impl* ses = nullptr); + TORRENT_EXTRA_EXPORT void apply_pack_impl(settings_pack const* pack + , aux::session_settings_single_thread& sett + , std::vector* callbacks = nullptr); + TORRENT_EXTRA_EXPORT void run_all_updates(aux::session_impl& ses); + + // converts a setting integer (from the enums string_types, int_types or + // bool_types) to a string, and vice versa. + TORRENT_EXPORT int setting_by_name(string_view name); + TORRENT_EXPORT char const* name_for_setting(int s); + + // returns a settings_pack with every setting set to its default value + TORRENT_EXPORT settings_pack default_settings(); + + // the common interface to settings_pack and the internal representation of + // settings. + struct TORRENT_EXPORT settings_interface + { + virtual void set_str(int name, std::string val) = 0; + virtual void set_int(int name, int val) = 0; + virtual void set_bool(int name, bool val) = 0; + virtual bool has_val(int name) const = 0; + + virtual std::string const& get_str(int name) const = 0; + virtual int get_int(int name) const = 0; + virtual bool get_bool(int name) const = 0; + + template + // hidden + void set_int(int name, flags::bitfield_flag const val) + { set_int(name, static_cast(static_cast(val))); } + + // hidden + // these are here just to suppress the warning about virtual destructors + // internal + settings_interface() = default; + settings_interface(settings_interface const&) = default; + settings_interface(settings_interface&&) = default; + settings_interface& operator=(settings_interface const&) = default; + settings_interface& operator=(settings_interface&&) = default; + protected: + ~settings_interface() = default; + }; + + // The ``settings_pack`` struct, contains the names of all settings as + // enum values. These values are passed in to the ``set_str()``, + // ``set_int()``, ``set_bool()`` functions, to specify the setting to + // change. + // + // The ``settings_pack`` only stores values for settings that have been + // explicitly set on this object. However, it can still be queried for + // settings that have not been set and returns the default value for those + // settings. + // + // .. include:: settings-ref.rst + // + struct TORRENT_EXPORT settings_pack final : settings_interface + { + friend TORRENT_EXTRA_EXPORT void apply_pack_impl(settings_pack const* + , aux::session_settings_single_thread& + , std::vector*); + + // hidden + settings_pack() = default; + settings_pack(settings_pack const&) = default; + settings_pack(settings_pack&&) noexcept = default; + settings_pack& operator=(settings_pack const&) = default; + settings_pack& operator=(settings_pack&&) noexcept = default; + + // set a configuration option in the settings_pack. ``name`` is one of + // the enum values from string_types, int_types or bool_types. They must + // match the respective type of the set_* function. + void set_str(int name, std::string val) override; + void set_int(int name, int val) override; + void set_bool(int name, bool val) override; + template + void set_int(int name, flags::bitfield_flag const val) + { set_int(name, static_cast(static_cast(val))); } + + // queries whether the specified configuration option has a value set in + // this pack. ``name`` can be any enumeration value from string_types, + // int_types or bool_types. + bool has_val(int name) const override; + + // clear the settings pack from all settings + void clear(); + + // clear a specific setting from the pack + void clear(int name); + + // queries the current configuration option from the settings_pack. + // ``name`` is one of the enumeration values from string_types, int_types + // or bool_types. The enum value must match the type of the get_* + // function. If the specified setting field has not been set, the default + // value is returned. + std::string const& get_str(int name) const override; + int get_int(int name) const override; + bool get_bool(int name) const override; + + // setting names (indices) are 16 bits. The two most significant + // bits indicate what type the setting has. (string, int, bool) + enum type_bases + { + string_type_base = 0x0000, + int_type_base = 0x4000, + bool_type_base = 0x8000, + type_mask = 0xc000, + index_mask = 0x3fff + }; + + // internal + template + void for_each(Fun&& f) const + { + for (auto const& s : m_strings) f(s.first, s.second); + for (auto const& i : m_ints) f(i.first, i.second); + for (auto const& b : m_bools) f(b.first, b.second); + } + + // hidden + enum string_types + { + // this is the client identification to the tracker. The recommended + // format of this string is: "client-name/client-version + // libtorrent/libtorrent-version". This name will not only be used when + // making HTTP requests, but also when sending extended headers to + // peers that support that extension. It may not contain \r or \n + user_agent = string_type_base, + + // ``announce_ip`` is the ip address passed along to trackers as the + // ``&ip=`` parameter. If left as the default, that parameter is + // omitted. + // + // .. note:: + // This setting is only meant for very special cases where a seed is + // running on the same host as the tracker, and the tracker accepts + // the IP parameter (which normal trackers don't). Do not set this + // option unless you also control the tracker. + announce_ip, + +#if TORRENT_ABI_VERSION == 1 + // ``mmap_cache`` may be set to a filename where the disk cache will + // be mmapped to. This could be useful, for instance, to map the disk + // cache from regular rotating hard drives onto an SSD drive. Doing + // that effectively introduces a second layer of caching, allowing the + // disk cache to be as big as can fit on an SSD drive (probably about + // one order of magnitude more than the available RAM). The intention + // of this setting is to set it up once at the start up and not change + // it while running. The setting may not be changed as long as there + // are any disk buffers in use. This default to the empty string, + // which means use regular RAM allocations for the disk cache. The + // file specified will be created and truncated to the disk cache size + // (``cache_size``). Any existing file with the same name will be + // replaced. + // + // This feature requires the ``mmap`` system call, on systems that + // don't have ``mmap`` this setting is ignored. + mmap_cache TORRENT_DEPRECATED_ENUM, +#else + deprecated_mmap_cache, +#endif + + // this is the client name and version identifier sent to peers in the + // handshake message. If this is an empty string, the user_agent is + // used instead. This string must be a UTF-8 encoded unicode string. + handshake_client_version, + + // This controls which IP address outgoing TCP peer connections are bound + // to, in addition to controlling whether such connections are also + // bound to a specific network interface/adapter (*bind-to-device*). + // + // This string is a comma-separated list of IP addresses and + // interface names. An empty string will not bind TCP sockets to a + // device, and let the network stack assign the local address. + // + // A list of names will be used to bind outgoing TCP sockets in a + // round-robin fashion. An IP address will simply be used to `bind()` + // the socket. An interface name will attempt to bind the socket to + // that interface. If that fails, or is unsupported, one of the IP + // addresses configured for that interface is used to `bind()` the + // socket to. If the interface or adapter doesn't exist, the + // outgoing peer connection will fail with an error message suggesting + // the device cannot be found. Adapter names on Unix systems are of + // the form "eth0", "eth1", "tun0", etc. This may be useful for + // clients that are multi-homed. Binding an outgoing connection to a + // local IP does not necessarily make the connection via the + // associated NIC/Adapter. + // + // When outgoing interfaces are specified, incoming connections or + // packets sent to a local interface or IP that's *not* in this list + // will be rejected with a peer_blocked_alert with + // ``invalid_local_interface`` as the reason. + // + // Note that these are just interface/adapter names or IP addresses. + // There are no ports specified in this list. IPv6 addresses without + // port should be specified without enclosing ``[``, ``]``. + outgoing_interfaces, + + // a comma-separated list of (IP or device name, port) pairs. These are + // the listen ports that will be opened for accepting incoming uTP and + // TCP peer connections. These are also used for *outgoing* uTP and UDP + // tracker connections and DHT nodes. + // + // It is possible to listen on multiple interfaces and + // multiple ports. Binding to port 0 will make the operating system + // pick the port. + // + // .. note:: + // There are reasons to stick to the same port across sessions, + // which would mean only using port 0 on the first start, and + // recording the port that was picked for subsequent startups. + // Trackers, the DHT and other peers will remember the port they see + // you use and hand that port out to other peers trying to connect + // to you, as well as trying to connect to you themselves. + // + // A port that has an "s" suffix will accept SSL peer connections. (note + // that SSL sockets are only available in builds with SSL support) + // + // A port that has an "l" suffix will be considered a local network. + // i.e. it's assumed to only be able to reach hosts in the same local + // network as the IP address (based on the netmask associated with the + // IP, queried from the operating system). + // + // if binding fails, the listen_failed_alert is posted. Once a + // socket binding succeeds (if it does), the listen_succeeded_alert + // is posted. There may be multiple failures before a success. + // + // If a device name that does not exist is configured, no listen + // socket will be opened for that interface. If this is the only + // interface configured, it will be as if no listen ports are + // configured. + // + // If no listen ports are configured (e.g. listen_interfaces is an + // empty string), networking will be disabled. No DHT will start, no + // outgoing uTP or tracker connections will be made. No incoming TCP + // or uTP connections will be accepted. (outgoing TCP connections + // will still be possible, depending on + // settings_pack::outgoing_interfaces). + // + // For example: + // ``[::1]:8888`` - will only accept connections on the IPv6 loopback + // address on port 8888. + // + // ``eth0:4444,eth1:4444`` - will accept connections on port 4444 on + // any IP address bound to device ``eth0`` or ``eth1``. + // + // ``[::]:0s`` - will accept SSL connections on a port chosen by the + // OS. And not accept non-SSL connections at all. + // + // ``0.0.0.0:6881,[::]:6881`` - binds to all interfaces on port 6881. + // + // ``10.0.1.13:6881l`` - binds to the local IP address, port 6881, but + // only allow talking to peers on the same local network. The netmask + // is queried from the operating system. Interfaces marked ``l`` are + // not announced to trackers, unless the tracker is also on the same + // local network. + // + // Windows OS network adapter device name must be specified with GUID. + // It can be obtained from "netsh lan show interfaces" command output. + // GUID must be uppercased string embraced in curly brackets. + // ``{E4F0B674-0DFC-48BB-98A5-2AA730BDB6D6}:7777`` - will accept + // connections on port 7777 on adapter with this GUID. + // + // For more information, see the `Multi-homed hosts`_ section. + // + // .. _`Multi-homed hosts`: manual-ref.html#multi-homed-hosts + listen_interfaces, + + // when using a proxy, this is the hostname where the proxy is running + // see proxy_type. Note that when using a proxy, the + // settings_pack::listen_interfaces setting is overridden and only a + // single interface is created, just to contact the proxy. This + // means a proxy cannot be combined with SSL torrents or multiple + // listen interfaces. This proxy listen interface will not accept + // incoming TCP connections, will not map ports with any gateway and + // will not enable local service discovery. All traffic is supposed + // to be channeled through the proxy. + proxy_hostname, + + // when using a proxy, these are the credentials (if any) to use when + // connecting to it. see proxy_type + proxy_username, + proxy_password, + + // sets the i2p_ SAM bridge to connect to. set the port with the + // ``i2p_port`` setting. Unless this is set, i2p torrents are not + // supported. This setting is separate from the other proxy settings + // since i2p torrents and their peers are orthogonal. You can have + // i2p peers as well as regular peers via a proxy. + // + // .. _i2p: http://www.i2p2.de + i2p_hostname, + + // this is the fingerprint for the client. It will be used as the + // prefix to the peer_id. If this is 20 bytes (or longer) it will be + // truncated to 20 bytes and used as the entire peer-id + // + // There is a utility function, generate_fingerprint() that can be used + // to generate a standard client peer ID fingerprint prefix. + peer_fingerprint, + + // This is a comma-separated list of IP port-pairs. They will be added + // to the DHT node (if it's enabled) as back-up nodes in case we don't + // know of any. + // + // Changing these after the DHT has been started may not have any + // effect until the DHT is restarted. + // Here are some other bootstrap nodes that may work: + // ``router.bittorrent.com:6881``, + // ``dht.transmissionbt.com:6881`` + // ``router.bt.ouinet.work:6881``, + dht_bootstrap_nodes, + + max_string_setting_internal + }; + + // hidden + enum bool_types + { + // determines if connections from the same IP address as existing + // connections should be rejected or not. Rejecting multiple connections + // from the same IP address will prevent abusive + // behavior by peers. The logic for determining whether connections are + // to the same peer is more complicated with this enabled, and more + // likely to fail in some edge cases. It is not recommended to enable + // this feature. + allow_multiple_connections_per_ip = bool_type_base, + +#if TORRENT_ABI_VERSION == 1 + // if set to true, upload, download and unchoke limits are ignored for + // peers on the local network. This option is *DEPRECATED*, please use + // set_peer_class_filter() instead. + ignore_limits_on_local_network TORRENT_DEPRECATED_ENUM, +#else + deprecated_ignore_limits_on_local_network, +#endif + + // ``send_redundant_have`` controls if have messages will be sent to + // peers that already have the piece. This is typically not necessary, + // but it might be necessary for collecting statistics in some cases. + send_redundant_have, + +#if TORRENT_ABI_VERSION == 1 + // if this is true, outgoing bitfields will never be fuil. If the + // client is seed, a few bits will be set to 0, and later filled in + // with have messages. This is to prevent certain ISPs from stopping + // people from seeding. + lazy_bitfields TORRENT_DEPRECATED_ENUM, +#else + deprecated_lazy_bitfield, +#endif + + // ``use_dht_as_fallback`` determines how the DHT is used. If this is + // true, the DHT will only be used for torrents where all trackers in + // its tracker list has failed. Either by an explicit error message or + // a time out. If this is false, the DHT is used regardless of if the + // trackers fail or not. + use_dht_as_fallback, + + // ``upnp_ignore_nonrouters`` indicates whether or not the UPnP + // implementation should ignore any broadcast response from a device + // whose address is not on our subnet. i.e. + // it's a way to not talk to other people's routers by mistake. + upnp_ignore_nonrouters, + + // ``use_parole_mode`` specifies if parole mode should be used. Parole + // mode means that peers that participate in pieces that fail the hash + // check are put in a mode where they are only allowed to download + // whole pieces. If the whole piece a peer in parole mode fails the + // hash check, it is banned. If a peer participates in a piece that + // passes the hash check, it is taken out of parole mode. + use_parole_mode, + +#if TORRENT_ABI_VERSION == 1 + // enable and disable caching of blocks read from disk. the purpose of + // the read cache is partly read-ahead of requests but also to avoid + // reading blocks back from the disk multiple times for popular + // pieces. + use_read_cache TORRENT_DEPRECATED_ENUM, + use_write_cache TORRENT_DEPRECATED_ENUM, + + // this will make the disk cache never flush a write piece if it would + // cause is to have to re-read it once we want to calculate the piece + // hash + dont_flush_write_cache TORRENT_DEPRECATED_ENUM, + + // allocate separate, contiguous, buffers for read and write calls. + // Only used where writev/readv cannot be used will use more RAM but + // may improve performance + coalesce_reads TORRENT_DEPRECATED_ENUM, + coalesce_writes TORRENT_DEPRECATED_ENUM, +#else + deprecated_use_read_cache, + deprecated_use_write_cache, + deprecated_flush_write_cache, + deprecated_coalesce_reads, + deprecated_coalesce_writes, +#endif + + // if true, prefer seeding torrents when determining which torrents to give + // active slots to. If false, give preference to downloading torrents + auto_manage_prefer_seeds, + + // if ``dont_count_slow_torrents`` is true, torrents without any + // payload transfers are not subject to the ``active_seeds`` and + // ``active_downloads`` limits. This is intended to make it more + // likely to utilize all available bandwidth, and avoid having + // torrents that don't transfer anything block the active slots. + dont_count_slow_torrents, + + // ``close_redundant_connections`` specifies whether libtorrent should + // close connections where both ends have no utility in keeping the + // connection open. For instance if both ends have completed their + // downloads, there's no point in keeping it open. + close_redundant_connections, + + // If ``prioritize_partial_pieces`` is true, partial pieces are picked + // before pieces that are more rare. If false, rare pieces are always + // prioritized, unless the number of partial pieces is growing out of + // proportion. + prioritize_partial_pieces, + + // if set to true, the estimated TCP/IP overhead is drained from the + // rate limiters, to avoid exceeding the limits with the total traffic + rate_limit_ip_overhead, + + // ``announce_to_all_trackers`` controls how multi tracker torrents + // are treated. If this is set to true, all trackers in the same tier + // are announced to in parallel. If all trackers in tier 0 fails, all + // trackers in tier 1 are announced as well. If it's set to false, the + // behavior is as defined by the multi tracker specification. + // + // ``announce_to_all_tiers`` also controls how multi tracker torrents + // are treated. When this is set to true, one tracker from each tier + // is announced to. This is the uTorrent behavior. To be compliant + // with the Multi-tracker specification, set it to false. + announce_to_all_tiers, + announce_to_all_trackers, + + // ``prefer_udp_trackers``: true means that trackers + // may be rearranged in a way that udp trackers are always tried + // before http trackers for the same hostname. Setting this to false + // means that the tracker's tier is respected and there's no + // preference of one protocol over another. + prefer_udp_trackers, + +#if TORRENT_ABI_VERSION == 1 + // ``strict_super_seeding`` when this is set to true, a piece has to + // have been forwarded to a third peer before another one is handed + // out. This is the traditional definition of super seeding. + strict_super_seeding TORRENT_DEPRECATED_ENUM, +#else + deprecated_strict_super_seeding, +#endif + +#if TORRENT_ABI_VERSION == 1 + // if this is set to true, the memory allocated for the disk cache + // will be locked in physical RAM, never to be swapped out. Every time + // a disk buffer is allocated and freed, there will be the extra + // overhead of a system call. + lock_disk_cache TORRENT_DEPRECATED_ENUM, +#else + deprecated_lock_disk_cache, +#endif + + // when set to true, all data downloaded from peers will be assumed to + // be correct, and not tested to match the hashes in the torrent this + // is only useful for simulation and testing purposes (typically + // combined with disabled_storage) + disable_hash_checks, + + // if this is true, i2p torrents are allowed to also get peers from + // other sources than the tracker, and connect to regular IPs, not + // providing any anonymization. This may be useful if the user is not + // interested in the anonymization of i2p, but still wants to be able + // to connect to i2p peers. + allow_i2p_mixed, + +#if TORRENT_ABI_VERSION == 1 + // ``low_prio_disk`` determines if the disk I/O should use a normal or + // low priority policy. True, means that it's + // low priority by default. Other processes doing disk I/O will + // normally take priority in this mode. This is meant to improve the + // overall responsiveness of the system while downloading in the + // background. For high-performance server setups, this might not be + // desirable. + low_prio_disk TORRENT_DEPRECATED_ENUM, +#else + deprecated_low_prio_disk, +#endif + +#if TORRENT_ABI_VERSION <= 2 + // ``volatile_read_cache``, if this is set to true, read cache blocks + // that are hit by peer read requests are removed from the disk cache + // to free up more space. This is useful if you don't expect the disk + // cache to create any cache hits from other peers than the one who + // triggered the cache line to be read into the cache in the first + // place. + volatile_read_cache TORRENT_DEPRECATED_ENUM, +#else + deprecated_volatile_read_cache, +#endif + +#if TORRENT_ABI_VERSION == 1 + // ``guided_read_cache`` enables the disk cache to adjust the size of + // a cache line generated by peers to depend on the upload rate you + // are sending to that peer. The intention is to optimize the RAM + // usage of the cache, to read ahead further for peers that you're + // sending faster to. + guided_read_cache TORRENT_DEPRECATED_ENUM, +#else + deprecated_guided_read_cache, +#endif + + // ``no_atime_storage`` this is a Linux-only option and passes in the + // ``O_NOATIME`` to ``open()`` when opening files. This may lead to + // some disk performance improvements. + no_atime_storage, + + // ``incoming_starts_queued_torrents``. If a torrent + // has been paused by the auto managed feature in libtorrent, i.e. the + // torrent is paused and auto managed, this feature affects whether or + // not it is automatically started on an incoming connection. The main + // reason to queue torrents, is not to make them unavailable, but to + // save on the overhead of announcing to the trackers, the DHT and to + // avoid spreading one's unchoke slots too thin. If a peer managed to + // find us, even though we're no in the torrent anymore, this setting + // can make us start the torrent and serve it. + incoming_starts_queued_torrents, + + // when set to true, the downloaded counter sent to trackers will + // include the actual number of payload bytes downloaded including + // redundant bytes. If set to false, it will not include any redundancy + // bytes + report_true_downloaded, + + // ``strict_end_game_mode`` controls when a + // block may be requested twice. If this is ``true``, a block may only + // be requested twice when there's at least one request to every piece + // that's left to download in the torrent. This may slow down progress + // on some pieces sometimes, but it may also avoid downloading a lot + // of redundant bytes. If this is ``false``, libtorrent attempts to + // use each peer connection to its max, by always requesting + // something, even if it means requesting something that has been + // requested from another peer already. + strict_end_game_mode, + +#if TORRENT_ABI_VERSION == 1 + // if ``broadcast_lsd`` is set to true, the local peer discovery (or + // Local Service Discovery) will not only use IP multicast, but also + // broadcast its messages. This can be useful when running on networks + // that don't support multicast. Since broadcast messages might be + // expensive and disruptive on networks, only every 8th announce uses + // broadcast. + broadcast_lsd TORRENT_DEPRECATED_ENUM, +#else + deprecated_broadcast_lsd, +#endif + + // Enables incoming and outgoing, TCP and uTP peer connections. + // ``false`` is disabled and ``true`` is enabled. When outgoing + // connections are disabled, libtorrent will simply not make + // outgoing peer connections with the specific transport protocol. + // Disabled incoming peer connections will simply be rejected. + // These options only apply to peer connections, not tracker- or any + // other kinds of connections. + enable_outgoing_utp, + enable_incoming_utp, + enable_outgoing_tcp, + enable_incoming_tcp, + +#if TORRENT_ABI_VERSION == 1 + // ``ignore_resume_timestamps`` determines if the storage, when + // loading resume data files, should verify that the file modification + // time with the timestamps in the resume data. False, means timestamps + // are taken into account, and resume + // data is less likely to accepted (torrents are more likely to be + // fully checked when loaded). It might be useful to set this to true + // if your network is faster than your disk, and it would be faster to + // redownload potentially missed pieces than to go through the whole + // storage to look for them. + ignore_resume_timestamps TORRENT_DEPRECATED_ENUM, +#else + // hidden + deprecated_ignore_resume_timestamps, +#endif + + // ``no_recheck_incomplete_resume`` determines if the storage should + // check the whole files when resume data is incomplete or missing or + // whether it should simply assume we don't have any of the data. If + // false, any existing files will be checked. + // By setting this setting to true, the files won't be checked, but + // will go straight to download mode. + no_recheck_incomplete_resume, + + // ``anonymous_mode``: When set to true, the client tries to hide + // its identity to a certain degree. + // + // * A generic user-agent will be + // used for trackers (except for private torrents). + // * Your local IPv4 and IPv6 address won't be sent as query string + // parameters to private trackers. + // * If announce_ip is configured, it will not be sent to trackers + // * The client version will not be sent to peers in the extension + // handshake. + anonymous_mode, + + // specifies whether downloads from web seeds is reported to the + // tracker or not. Turning it off also excludes web + // seed traffic from other stats and download rate reporting via the + // libtorrent API. + report_web_seed_downloads, + +#if TORRENT_ABI_VERSION == 1 + // set to true if uTP connections should be rate limited This option + // is *DEPRECATED*, please use set_peer_class_filter() instead. + rate_limit_utp TORRENT_DEPRECATED_ENUM, +#else + deprecated_rate_limit_utp, +#endif + +#if TORRENT_ABI_VERSION == 1 + // if this is true, the ``&ip=`` argument in tracker requests (unless + // otherwise specified) will be set to the intermediate IP address if + // the user is double NATed. If the user is not double NATed, this + // option does not have an affect + announce_double_nat TORRENT_DEPRECATED_ENUM, +#else + deprecated_announce_double_nat, +#endif + + // ``seeding_outgoing_connections`` determines if seeding (and + // finished) torrents should attempt to make outgoing connections or + // not. It may be set to false in very + // specific applications where the cost of making outgoing connections + // is high, and there are no or small benefits of doing so. For + // instance, if no nodes are behind a firewall or a NAT, seeds don't + // need to make outgoing connections. + seeding_outgoing_connections, + + // when this is true, libtorrent will not attempt to make outgoing + // connections to peers whose port is < 1024. This is a safety + // precaution to avoid being part of a DDoS attack + no_connect_privileged_ports, + + // ``smooth_connects`` means the number of + // connection attempts per second may be limited to below the + // ``connection_speed``, in case we're close to bump up against the + // limit of number of connections. The intention of this setting is to + // more evenly distribute our connection attempts over time, instead + // of attempting to connect in batches, and timing them out in + // batches. + smooth_connects, + + // always send user-agent in every web seed request. If false, only + // the first request per http connection will include the user agent + always_send_user_agent, + + // ``apply_ip_filter_to_trackers`` determines + // whether the IP filter applies to trackers as well as peers. If this + // is set to false, trackers are exempt from the IP filter (if there + // is one). If no IP filter is set, this setting is irrelevant. + apply_ip_filter_to_trackers, + +#if TORRENT_ABI_VERSION == 1 + // ``use_disk_read_ahead`` if true will attempt to + // optimize disk reads by giving the operating system heads up of disk + // read requests as they are queued in the disk job queue. + use_disk_read_ahead TORRENT_DEPRECATED_ENUM, +#else + deprecated_use_disk_read_ahead, +#endif + +#if TORRENT_ABI_VERSION == 1 + // ``lock_files`` determines whether or not to lock files which + // libtorrent is downloading to or seeding from. This is implemented + // using ``fcntl(F_SETLK)`` on Unix systems and by not passing in + // ``SHARE_READ`` and ``SHARE_WRITE`` on windows. This might prevent + // 3rd party processes from corrupting the files under libtorrent's + // feet. + lock_files TORRENT_DEPRECATED_ENUM, +#else + deprecated_lock_files, +#endif + +#if TORRENT_ABI_VERSION == 1 + // ``contiguous_recv_buffer`` determines whether or not libtorrent + // should receive data from peers into a contiguous intermediate + // buffer, to then copy blocks into disk buffers from, or to make many + // smaller calls to ``read()``, each time passing in the specific + // buffer the data belongs in. When downloading at high rates, the + // latter may save some time copying data. When seeding at high rates, + // all incoming traffic consists of a very large number of tiny + // packets, and enabling ``contiguous_recv_buffer`` will provide + // higher performance. When this is enabled, it will only be used when + // seeding to peers, since that's when it provides performance + // improvements. + contiguous_recv_buffer TORRENT_DEPRECATED_ENUM, +#else + deprecated_contiguous_recv_buffer, +#endif + + // when true, web seeds sending bad data will be banned + ban_web_seeds, + +#if TORRENT_ABI_VERSION <= 2 + // when set to false, the ``write_cache_line_size`` will apply across + // piece boundaries. this is a bad idea unless the piece picker also + // is configured to have an affinity to pick pieces belonging to the + // same write cache line as is configured in the disk cache. + allow_partial_disk_writes TORRENT_DEPRECATED_ENUM, +#else + deprecated_allow_partial_disk_writes, +#endif + +#if TORRENT_ABI_VERSION == 1 + // If true, disables any communication that's not going over a proxy. + // Enabling this requires a proxy to be configured as well, see + // proxy_type and proxy_hostname settings. The listen sockets are + // closed, and incoming connections will only be accepted through a + // SOCKS5 or I2P proxy (if a peer proxy is set up and is run on the + // same machine as the tracker proxy). + force_proxy TORRENT_DEPRECATED_ENUM, +#else + deprecated_force_proxy, +#endif + + // if false, prevents libtorrent to advertise share-mode support + support_share_mode, + +#if TORRENT_ABI_VERSION <= 2 + // support for BEP 30 merkle torrents has been removed + + // if this is false, don't advertise support for the Tribler merkle + // tree piece message + support_merkle_torrents TORRENT_DEPRECATED_ENUM, +#else + deprecated_support_merkle_torrents, +#endif + + // if this is true, the number of redundant bytes is sent to the + // tracker + report_redundant_bytes, + + // if this is true, libtorrent will fall back to listening on a port + // chosen by the operating system (i.e. binding to port 0). If a + // failure is preferred, set this to false. + listen_system_port_fallback, + +#if TORRENT_ABI_VERSION == 1 + // ``use_disk_cache_pool`` enables using a pool allocator for disk + // cache blocks. Enabling it makes the cache perform better at high + // throughput. It also makes the cache less likely and slower at + // returning memory back to the system, once allocated. + use_disk_cache_pool TORRENT_DEPRECATED_ENUM, +#else + deprecated_use_disk_cache_pool, +#endif + + // when this is true, and incoming encrypted connections are enabled, + // &supportcrypt=1 is included in http tracker announces + announce_crypto_support, + + // Starts and stops the UPnP service. When started, the listen port + // and the DHT port are attempted to be forwarded on local UPnP router + // devices. + // + // The upnp object returned by ``start_upnp()`` can be used to add and + // remove arbitrary port mappings. Mapping status is returned through + // the portmap_alert and the portmap_error_alert. The object will be + // valid until ``stop_upnp()`` is called. See upnp-and-nat-pmp_. + enable_upnp, + + // Starts and stops the NAT-PMP service. When started, the listen port + // and the DHT port are attempted to be forwarded on the router + // through NAT-PMP. + // + // The natpmp object returned by ``start_natpmp()`` can be used to add + // and remove arbitrary port mappings. Mapping status is returned + // through the portmap_alert and the portmap_error_alert. The object + // will be valid until ``stop_natpmp()`` is called. See + // upnp-and-nat-pmp_. + enable_natpmp, + + // Starts and stops Local Service Discovery. This service will + // broadcast the info-hashes of all the non-private torrents on the + // local network to look for peers on the same swarm within multicast + // reach. + enable_lsd, + + // starts the dht node and makes the trackerless service available to + // torrents. + enable_dht, + + // if the allowed encryption level is both, setting this to true will + // prefer RC4 if both methods are offered, plain text otherwise + prefer_rc4, + + // if true, hostname lookups are done via the configured proxy (if + // any). This is only supported by SOCKS5 and HTTP. + proxy_hostnames, + + // if true, peer connections are made (and accepted) over the + // configured proxy, if any. Web seeds as well as regular bittorrent + // peer connections are considered "peer connections". Anything + // transporting actual torrent payload (trackers and DHT traffic are + // not considered peer connections). + proxy_peer_connections, + + // if this setting is true, torrents with a very high availability of + // pieces (and seeds) are downloaded sequentially. This is more + // efficient for the disk I/O. With many seeds, the download order is + // unlikely to matter anyway + auto_sequential, + + // if true, tracker connections are made over the configured proxy, if + // any. + proxy_tracker_connections, + + // Starts and stops the internal IP table route changes notifier. + // + // The current implementation supports multiple platforms, and it is + // recommended to have it enable, but you may want to disable it if + // it's supported but unreliable, or if you have a better way to + // detect the changes. In the later case, you should manually call + // ``session_handle::reopen_network_sockets`` to ensure network + // changes are taken in consideration. + enable_ip_notifier, + + // when this is true, nodes whose IDs are derived from their source + // IP according to `BEP 42`_ are preferred in the routing table. + dht_prefer_verified_node_ids, + + // determines if the routing table entries should restrict entries to one + // per IP. This defaults to true, which helps mitigate some attacks on + // the DHT. It prevents adding multiple nodes with IPs with a very close + // CIDR distance. + // + // when set, nodes whose IP address that's in the same /24 (or /64 for + // IPv6) range in the same routing table bucket. This is an attempt to + // mitigate node ID spoofing attacks also restrict any IP to only have a + // single entry in the whole routing table + dht_restrict_routing_ips, + + // determines if DHT searches should prevent adding nodes with IPs with + // very close CIDR distance. This also defaults to true and helps + // mitigate certain attacks on the DHT. + dht_restrict_search_ips, + + // makes the first buckets in the DHT routing table fit 128, 64, 32 and + // 16 nodes respectively, as opposed to the standard size of 8. All other + // buckets have size 8 still. + dht_extended_routing_table, + + // slightly changes the lookup behavior in terms of how many outstanding + // requests we keep. Instead of having branch factor be a hard limit, we + // always keep *branch factor* outstanding requests to the closest nodes. + // i.e. every time we get results back with closer nodes, we query them + // right away. It lowers the lookup times at the cost of more outstanding + // queries. + dht_aggressive_lookups, + + // when set, perform lookups in a way that is slightly more expensive, + // but which minimizes the amount of information leaked about you. + dht_privacy_lookups, + + // when set, node's whose IDs that are not correctly generated based on + // its external IP are ignored. When a query arrives from such node, an + // error message is returned with a message saying "invalid node ID". + dht_enforce_node_id, + + // ignore DHT messages from parts of the internet we wouldn't expect to + // see any traffic from + dht_ignore_dark_internet, + + // when set, the other nodes won't keep this node in their routing + // tables, it's meant for low-power and/or ephemeral devices that + // cannot support the DHT, it is also useful for mobile devices which + // are sensitive to network traffic and battery life. + // this node no longer responds to 'query' messages, and will place a + // 'ro' key (value = 1) in the top-level message dictionary of outgoing + // query messages. + dht_read_only, + + // when this is true, create an affinity for downloading 4 MiB extents + // of adjacent pieces. This is an attempt to achieve better disk I/O + // throughput by downloading larger extents of bytes, for torrents with + // small piece sizes + piece_extent_affinity, + + // when set to true, the certificate of HTTPS trackers and HTTPS web + // seeds will be validated against the system's certificate store + // (as defined by OpenSSL). If the system does not have a + // certificate store, this option may have to be disabled in order + // to get trackers and web seeds to work). + validate_https_trackers, + + // when enabled, tracker and web seed requests are subject to + // certain restrictions. + // + // An HTTP(s) tracker requests to localhost (loopback) + // must have the request path start with "/announce". This is the + // conventional bittorrent tracker request. Any other HTTP(S) + // tracker request to loopback will be rejected. This applies to + // trackers that redirect to loopback as well. + // + // Web seeds that end up on the client's local network (i.e. in a + // private IP address range) may not include query string arguments. + // This applies to web seeds redirecting to the local network as + // well. + // + // Web seeds on global IPs (i.e. not local network) may not redirect + // to a local network address + ssrf_mitigation, + + // when disabled, any tracker or web seed with an IDNA hostname + // (internationalized domain name) is ignored. This is a security + // precaution to avoid various unicode encoding attacks that might + // happen at the application level. + allow_idna, + + // when set to true, enables the attempt to use SetFileValidData() + // to pre-allocate disk space. This system call will only work when + // running with Administrator privileges on Windows, and so this + // setting is only relevant in that scenario. Using + // SetFileValidData() poses a security risk, as it may reveal + // previously deleted information from the disk. + enable_set_file_valid_data, + + // When using a SOCKS5 proxy, UDP traffic is routed through the + // proxy by sending a UDP ASSOCIATE command. If this option is true, + // the UDP ASSOCIATE command will include the IP address and + // listen port to the local UDP socket. This indicates to the proxy + // which source endpoint to expect our packets from. The benefit is + // that incoming packets can be forwarded correctly, before any + // outgoing packets are sent. The risk is that if there's a NAT + // between the client and the proxy, the IP address specified in the + // protocol may not be valid from the proxy's point of view. + socks5_udp_send_local_ep, + + max_bool_setting_internal + }; + + // hidden + enum int_types + { + // ``tracker_completion_timeout`` is the number of seconds the tracker + // connection will wait from when it sent the request until it + // considers the tracker to have timed-out. + tracker_completion_timeout = int_type_base, + + // ``tracker_receive_timeout`` is the number of seconds to wait to + // receive any data from the tracker. If no data is received for this + // number of seconds, the tracker will be considered as having timed + // out. If a tracker is down, this is the kind of timeout that will + // occur. + tracker_receive_timeout, + + // ``stop_tracker_timeout`` is the number of seconds to wait when + // sending a stopped message before considering a tracker to have + // timed out. This is usually shorter, to make the client quit faster. + // If the value is set to 0, the connections to trackers with the + // stopped event are suppressed. + stop_tracker_timeout, + + // this is the maximum number of bytes in a tracker response. If a + // response size passes this number of bytes it will be rejected and + // the connection will be closed. On gzipped responses this size is + // measured on the uncompressed data. So, if you get 20 bytes of gzip + // response that'll expand to 2 megabytes, it will be interrupted + // before the entire response has been uncompressed (assuming the + // limit is lower than 2 MiB). + tracker_maximum_response_length, + + // the number of seconds from a request is sent until it times out if + // no piece response is returned. + piece_timeout, + + // the number of seconds one block (16 kiB) is expected to be received + // within. If it's not, the block is requested from a different peer + request_timeout, + + // the length of the request queue given in the number of seconds it + // should take for the other end to send all the pieces. i.e. the + // actual number of requests depends on the download rate and this + // number. + request_queue_time, + + // the number of outstanding block requests a peer is allowed to queue + // up in the client. If a peer sends more requests than this (before + // the first one has been sent) the last request will be dropped. the + // higher this is, the faster upload speeds the client can get to a + // single peer. + max_allowed_in_request_queue, + + // ``max_out_request_queue`` is the maximum number of outstanding + // requests to send to a peer. This limit takes precedence over + // ``request_queue_time``. i.e. no matter the download speed, the + // number of outstanding requests will never exceed this limit. + max_out_request_queue, + + // if a whole piece can be downloaded in this number of seconds, or + // less, the peer_connection will prefer to request whole pieces at a + // time from this peer. The benefit of this is to better utilize disk + // caches by doing localized accesses and also to make it easier to + // identify bad peers if a piece fails the hash check. + whole_pieces_threshold, + + // ``peer_timeout`` is the number of seconds the peer connection + // should wait (for any activity on the peer connection) before + // closing it due to time out. 120 seconds is + // specified in the protocol specification. After half + // the time out, a keep alive message is sent. + peer_timeout, + + // same as peer_timeout, but only applies to url-seeds. this is + // usually set lower, because web servers are expected to be more + // reliable. + urlseed_timeout, + + // controls the pipelining size of url and http seeds. i.e. the number of HTTP + // request to keep outstanding before waiting for the first one to + // complete. It's common for web servers to limit this to a relatively + // low number, like 5 + urlseed_pipeline_size, + + // number of seconds until a new retry of a url-seed takes place. + // Default retry value for http-seeds that don't provide + // a valid ``retry-after`` header. + urlseed_wait_retry, + + // sets the upper limit on the total number of files this session will + // keep open. The reason why files are left open at all is that some + // anti virus software hooks on every file close, and scans the file + // for viruses. deferring the closing of the files will be the + // difference between a usable system and a completely hogged down + // system. Most operating systems also has a limit on the total number + // of file descriptors a process may have open. + file_pool_size, + + // ``max_failcount`` is the maximum times we try to + // connect to a peer before stop connecting again. If a + // peer succeeds, the failure counter is reset. If a + // peer is retrieved from a peer source (other than DHT) + // the failcount is decremented by one, allowing another + // try. + max_failcount, + + // the number of seconds to wait to reconnect to a peer. this time is + // multiplied with the failcount. + min_reconnect_time, + + // ``peer_connect_timeout`` the number of seconds to wait after a + // connection attempt is initiated to a peer until it is considered as + // having timed out. This setting is especially important in case the + // number of half-open connections are limited, since stale half-open + // connection may delay the connection of other peers considerably. + peer_connect_timeout, + + // ``connection_speed`` is the number of connection attempts that are + // made per second. If a number < 0 is specified, it will default to + // 200 connections per second. If 0 is specified, it means don't make + // outgoing connections at all. + connection_speed, + + // if a peer is uninteresting and uninterested for longer than this + // number of seconds, it will be disconnected. + inactivity_timeout, + + // ``unchoke_interval`` is the number of seconds between + // chokes/unchokes. On this interval, peers are re-evaluated for being + // choked/unchoked. This is defined as 30 seconds in the protocol, and + // it should be significantly longer than what it takes for TCP to + // ramp up to it's max rate. + unchoke_interval, + + // ``optimistic_unchoke_interval`` is the number of seconds between + // each *optimistic* unchoke. On this timer, the currently + // optimistically unchoked peer will change. + optimistic_unchoke_interval, + + // ``num_want`` is the number of peers we want from each tracker + // request. It defines what is sent as the ``&num_want=`` parameter to + // the tracker. + num_want, + + // ``initial_picker_threshold`` specifies the number of pieces we need + // before we switch to rarest first picking. The first + // ``initial_picker_threshold`` pieces in any torrent are picked at random + // , the following pieces are picked in rarest first order. + initial_picker_threshold, + + // the number of allowed pieces to send to peers that supports the + // fast extensions + allowed_fast_set_size, + + // ``suggest_mode`` controls whether or not libtorrent will send out + // suggest messages to create a bias of its peers to request certain + // pieces. The modes are: + // + // * ``no_piece_suggestions`` which will not send out suggest messages. + // * ``suggest_read_cache`` which will send out suggest messages for + // the most recent pieces that are in the read cache. + suggest_mode, + + // ``max_queued_disk_bytes`` is the maximum number of bytes, to + // be written to disk, that can wait in the disk I/O thread queue. + // This queue is only for waiting for the disk I/O thread to receive + // the job and either write it to disk or insert it in the write + // cache. When this limit is reached, the peer connections will stop + // reading data from their sockets, until the disk thread catches up. + // Setting this too low will severely limit your download rate. + max_queued_disk_bytes, + + // the number of seconds to wait for a handshake response from a peer. + // If no response is received within this time, the peer is + // disconnected. + handshake_timeout, + + // ``send_buffer_low_watermark`` the minimum send buffer target size + // (send buffer includes bytes pending being read from disk). For good + // and snappy seeding performance, set this fairly high, to at least + // fit a few blocks. This is essentially the initial window size which + // will determine how fast we can ramp up the send rate + // + // if the send buffer has fewer bytes than ``send_buffer_watermark``, + // we'll read another 16 kiB block onto it. If set too small, upload + // rate capacity will suffer. If set too high, memory will be wasted. + // The actual watermark may be lower than this in case the upload rate + // is low, this is the upper limit. + // + // the current upload rate to a peer is multiplied by this factor to + // get the send buffer watermark. The factor is specified as a + // percentage. i.e. 50 -> 0.5 This product is clamped to the + // ``send_buffer_watermark`` setting to not exceed the max. For high + // speed upload, this should be set to a greater value than 100. For + // high capacity connections, setting this higher can improve upload + // performance and disk throughput. Setting it too high may waste RAM + // and create a bias towards read jobs over write jobs. + send_buffer_low_watermark, + send_buffer_watermark, + send_buffer_watermark_factor, + + // ``choking_algorithm`` specifies which algorithm to use to determine + // how many peers to unchoke. The unchoking algorithm for + // downloading torrents is always "tit-for-tat", i.e. the peers we + // download the fastest from are unchoked. + // + // The options for choking algorithms are defined in the + // choking_algorithm_t enum. + // + // ``seed_choking_algorithm`` controls the seeding unchoke behavior. + // i.e. How we select which peers to unchoke for seeding torrents. + // Since a seeding torrent isn't downloading anything, the + // tit-for-tat mechanism cannot be used. The available options are + // defined in the seed_choking_algorithm_t enum. + choking_algorithm, + seed_choking_algorithm, + +#if TORRENT_ABI_VERSION == 1 + // ``cache_size`` is the disk write and read cache. It is specified + // in units of 16 kiB blocks. Buffers that are part of a peer's send + // or receive buffer also count against this limit. Send and receive + // buffers will never be denied to be allocated, but they will cause + // the actual cached blocks to be flushed or evicted. If this is set + // to -1, the cache size is automatically set based on the amount of + // physical RAM on the machine. If the amount of physical RAM cannot + // be determined, it's set to 1024 (= 16 MiB). + // + // On 32 bit builds, the effective cache size will be limited to 3/4 of + // 2 GiB to avoid exceeding the virtual address space limit. + cache_size TORRENT_DEPRECATED_ENUM, + + // Disk buffers are allocated using a pool allocator, the number of + // blocks that are allocated at a time when the pool needs to grow can + // be specified in ``cache_buffer_chunk_size``. Lower numbers saves + // memory at the expense of more heap allocations. If it is set to 0, + // the effective chunk size is proportional to the total cache size, + // attempting to strike a good balance between performance and memory + // usage. It defaults to 0. + cache_buffer_chunk_size TORRENT_DEPRECATED_ENUM, + + // ``cache_expiry`` is the number of seconds + // from the last cached write to a piece in the write cache, to when + // it's forcefully flushed to disk. + cache_expiry TORRENT_DEPRECATED_ENUM, +#else + deprecated_cache_size, + deprecated_cache_buffer_chunk_size, + deprecated_cache_expiry, +#endif + + // determines how files are opened when they're in read only mode + // versus read and write mode. The options are: + // + // enable_os_cache + // Files are opened normally, with the OS caching reads and writes. + // disable_os_cache + // This opens all files in no-cache mode. This corresponds to the + // OS not letting blocks for the files linger in the cache. This + // makes sense in order to avoid the bittorrent client to + // potentially evict all other processes' cache by simply handling + // high throughput and large files. If libtorrent's read cache is + // disabled, enabling this may reduce performance. + // write_through + // flush pieces to disk as they complete validation. + // + // One reason to disable caching is that it may help the operating + // system from growing its file cache indefinitely. + disk_io_write_mode, + disk_io_read_mode, + + // this is the first port to use for binding outgoing connections to. + // This is useful for users that have routers that allow QoS settings + // based on local port. when binding outgoing connections to specific + // ports, ``num_outgoing_ports`` is the size of the range. It should + // be more than a few + // + // .. warning:: setting outgoing ports will limit the ability to keep + // multiple connections to the same client, even for different + // torrents. It is not recommended to change this setting. Its main + // purpose is to use as an escape hatch for cheap routers with QoS + // capability but can only classify flows based on port numbers. + // + // It is a range instead of a single port because of the problems with + // failing to reconnect to peers if a previous socket to that peer and + // port is in ``TIME_WAIT`` state. + outgoing_port, + num_outgoing_ports, + + // ``peer_dscp`` determines the DSCP field in the IP header of every + // packet sent to peers (including web seeds). ``0x0`` means no marking, + // ``0x04`` represents Lower Effort. For more details see `RFC 8622`_. + // + // .. _`RFC 8622`: http://www.faqs.org/rfcs/rfc8622.html + // + // ``peer_tos`` is the backwards compatible name for this setting. + peer_dscp, + + // hidden + peer_tos = peer_dscp, + + // for auto managed torrents, these are the limits they are subject + // to. If there are too many torrents some of the auto managed ones + // will be paused until some slots free up. ``active_downloads`` and + // ``active_seeds`` controls how many active seeding and downloading + // torrents the queuing mechanism allows. The target number of active + // torrents is ``min(active_downloads + active_seeds, active_limit)``. + // ``active_downloads`` and ``active_seeds`` are upper limits on the + // number of downloading torrents and seeding torrents respectively. + // Setting the value to -1 means unlimited. + // + // For example if there are 10 seeding torrents and 10 downloading + // torrents, and ``active_downloads`` is 4 and ``active_seeds`` is 4, + // there will be 4 seeds active and 4 downloading torrents. If the + // settings are ``active_downloads`` = 2 and ``active_seeds`` = 4, + // then there will be 2 downloading torrents and 4 seeding torrents + // active. Torrents that are not auto managed are not counted against + // these limits. + // + // ``active_checking`` is the limit of number of simultaneous checking + // torrents. + // + // ``active_limit`` is a hard limit on the number of active (auto + // managed) torrents. This limit also applies to slow torrents. + // + // ``active_dht_limit`` is the max number of torrents to announce to + // the DHT. + // + // ``active_tracker_limit`` is the max number of torrents to announce + // to their trackers. + // + // ``active_lsd_limit`` is the max number of torrents to announce to + // the local network over the local service discovery protocol. + // + // You can have more torrents *active*, even though they are not + // announced to the DHT, lsd or their tracker. If some peer knows + // about you for any reason and tries to connect, it will still be + // accepted, unless the torrent is paused, which means it won't accept + // any connections. + active_downloads, + active_seeds, + active_checking, + active_dht_limit, + active_tracker_limit, + active_lsd_limit, + active_limit, + +#if TORRENT_ABI_VERSION == 1 + // ``active_loaded_limit`` is the number of torrents that are allowed + // to be *loaded* at any given time. Note that a torrent can be active + // even though it's not loaded. If an unloaded torrents finds a peer + // that wants to access it, the torrent will be loaded on demand, + // using a user-supplied callback function. If the feature of + // unloading torrents is not enabled, this setting have no effect. If + // this limit is set to 0, it means unlimited. For more information, + // see dynamic-loading-of-torrent-files_. + active_loaded_limit TORRENT_DEPRECATED_ENUM, +#else + deprecated_active_loaded_limit, +#endif + + // ``auto_manage_interval`` is the number of seconds between the + // torrent queue is updated, and rotated. + auto_manage_interval, + + // this is the limit on the time a torrent has been an active seed + // (specified in seconds) before it is considered having met the seed + // limit criteria. See queuing_. + seed_time_limit, + + // ``auto_scrape_interval`` is the number of seconds between scrapes + // of queued torrents (auto managed and paused torrents). Auto managed + // torrents that are paused, are scraped regularly in order to keep + // track of their downloader/seed ratio. This ratio is used to + // determine which torrents to seed and which to pause. + // + // ``auto_scrape_min_interval`` is the minimum number of seconds + // between any automatic scrape (regardless of torrent). In case there + // are a large number of paused auto managed torrents, this puts a + // limit on how often a scrape request is sent. + auto_scrape_interval, + auto_scrape_min_interval, + + // ``max_peerlist_size`` is the maximum number of peers in the list of + // known peers. These peers are not necessarily connected, so this + // number should be much greater than the maximum number of connected + // peers. Peers are evicted from the cache when the list grows passed + // 90% of this limit, and once the size hits the limit, peers are no + // longer added to the list. If this limit is set to 0, there is no + // limit on how many peers we'll keep in the peer list. + // + // ``max_paused_peerlist_size`` is the max peer list size used for + // torrents that are paused. This can be used to save memory for paused + // torrents, since it's not as important for them to keep a large peer + // list. + max_peerlist_size, + max_paused_peerlist_size, + + // this is the minimum allowed announce interval for a tracker. This + // is specified in seconds and is used as a sanity check on what is + // returned from a tracker. It mitigates hammering mis-configured + // trackers. + min_announce_interval, + + // this is the number of seconds a torrent is considered active after + // it was started, regardless of upload and download speed. This is so + // that newly started torrents are not considered inactive until they + // have a fair chance to start downloading. + auto_manage_startup, + + // ``seeding_piece_quota`` is the number of pieces to send to a peer, + // when seeding, before rotating in another peer to the unchoke set. + seeding_piece_quota, + + // ``max_rejects`` is the number of piece requests we will reject in a + // row while a peer is choked before the peer is considered abusive + // and is disconnected. + max_rejects, + + // specifies the buffer sizes set on peer sockets. 0 means the OS + // default (i.e. don't change the buffer sizes). + // The socket buffer sizes are changed using setsockopt() with + // SOL_SOCKET/SO_RCVBUF and SO_SNDBUFFER. + // + // Note that uTP peers share a single UDP socket buffer for each of the + // ``listen_interfaces``, along with DHT and UDP tracker traffic. + // If the buffer size is too small for the combined traffic through the + // socket, packets may be dropped. + recv_socket_buffer_size, + send_socket_buffer_size, + + // the max number of bytes a single peer connection's receive buffer is + // allowed to grow to. + max_peer_recv_buffer_size, + +#if TORRENT_ABI_VERSION == 1 + // ``file_checks_delay_per_block`` is the number of milliseconds to + // sleep in between disk read operations when checking torrents. + // This can be set to higher numbers to slow down the + // rate at which data is read from the disk while checking. This may + // be useful for background tasks that doesn't matter if they take a + // bit longer, as long as they leave disk I/O time for other + // processes. + file_checks_delay_per_block TORRENT_DEPRECATED_ENUM, +#else + deprecated_file_checks_delay_per_block, +#endif + +#if TORRENT_ABI_VERSION <= 2 + // ``read_cache_line_size`` is the number of blocks to read into the + // read cache when a read cache miss occurs. Setting this to 0 is + // essentially the same thing as disabling read cache. The number of + // blocks read into the read cache is always capped by the piece + // boundary. + // + // When a piece in the write cache has ``write_cache_line_size`` + // contiguous blocks in it, they will be flushed. Setting this to 1 + // effectively disables the write cache. + read_cache_line_size, + write_cache_line_size, +#else + deprecated_read_cache_line_size, + deprecated_write_cache_line_size, +#endif + + // ``optimistic_disk_retry`` is the number of seconds from a disk + // write errors occur on a torrent until libtorrent will take it out + // of the upload mode, to test if the error condition has been fixed. + // + // libtorrent will only do this automatically for auto managed + // torrents. + // + // You can explicitly take a torrent out of upload only mode using + // set_upload_mode(). + optimistic_disk_retry, + + // ``max_suggest_pieces`` is the max number of suggested piece indices + // received from a peer that's remembered. If a peer floods suggest + // messages, this limit prevents libtorrent from using too much RAM. + max_suggest_pieces, + + // ``local_service_announce_interval`` is the time between local + // network announces for a torrent. + // This interval is specified in seconds. + local_service_announce_interval, + + // ``dht_announce_interval`` is the number of seconds between + // announcing torrents to the distributed hash table (DHT). + dht_announce_interval, + + // ``udp_tracker_token_expiry`` is the number of seconds libtorrent + // will keep UDP tracker connection tokens around for. This is + // specified to be 60 seconds. The higher this + // value is, the fewer packets have to be sent to the UDP tracker. In + // order for higher values to work, the tracker needs to be configured + // to match the expiration time for tokens. + udp_tracker_token_expiry, + +#if TORRENT_ABI_VERSION == 1 + // ``default_cache_min_age`` is the minimum number of seconds any read + // cache line is kept in the cache. This + // may be greater if ``guided_read_cache`` is enabled. Having a lower + // bound on the time a cache line stays in the cache is an attempt + // to avoid swapping the same pieces in and out of the cache in case + // there is a shortage of spare cache space. + default_cache_min_age TORRENT_DEPRECATED_ENUM, +#else + deprecated_default_cache_min_age, +#endif + + // ``num_optimistic_unchoke_slots`` is the number of optimistic + // unchoke slots to use. + // Having a higher number of optimistic unchoke slots mean you will + // find the good peers faster but with the trade-off to use up more + // bandwidth. 0 means automatic, where libtorrent opens up 20% of your + // allowed upload slots as optimistic unchoke slots. + num_optimistic_unchoke_slots, + +#if TORRENT_ABI_VERSION == 1 + // ``default_est_reciprocation_rate`` is the assumed reciprocation + // rate from peers when using the BitTyrant choker. If set too high, + // you will over-estimate your peers and be + // more altruistic while finding the true reciprocation rate, if it's + // set too low, you'll be too stingy and waste finding the true + // reciprocation rate. + // + // ``increase_est_reciprocation_rate`` specifies how many percent the + // estimated reciprocation rate should be increased by each unchoke + // interval a peer is still choking us back. + // This only applies to the BitTyrant choker. + // + // ``decrease_est_reciprocation_rate`` specifies how many percent the + // estimated reciprocation rate should be decreased by each unchoke + // interval a peer unchokes us. This only applies + // to the BitTyrant choker. + default_est_reciprocation_rate TORRENT_DEPRECATED_ENUM, + increase_est_reciprocation_rate TORRENT_DEPRECATED_ENUM, + decrease_est_reciprocation_rate TORRENT_DEPRECATED_ENUM, +#else + deprecated_default_est_reciprocation_rate, + deprecated_increase_est_reciprocation_rate, + deprecated_decrease_est_reciprocation_rate, +#endif + + // the max number of peers we accept from pex messages from a single + // peer. this limits the number of concurrent peers any of our peers + // claims to be connected to. If they claim to be connected to more + // than this, we'll ignore any peer that exceeds this limit + max_pex_peers, + + // ``tick_interval`` specifies the number of milliseconds between + // internal ticks. This is the frequency with which bandwidth quota is + // distributed to peers. It should not be more than one second (i.e. + // 1000 ms). Setting this to a low value (around 100) means higher + // resolution bandwidth quota distribution, setting it to a higher + // value saves CPU cycles. + tick_interval, + + // ``share_mode_target`` specifies the target share ratio for share + // mode torrents. If set to 3, we'll try to upload 3 + // times as much as we download. Setting this very high, will make it + // very conservative and you might end up not downloading anything + // ever (and not affecting your share ratio). It does not make any + // sense to set this any lower than 2. For instance, if only 3 peers + // need to download the rarest piece, it's impossible to download a + // single piece and upload it more than 3 times. If the + // share_mode_target is set to more than 3, nothing is downloaded. + share_mode_target, + + // ``upload_rate_limit`` and ``download_rate_limit`` sets + // the session-global limits of upload and download rate limits, in + // bytes per second. By default peers on the local network are not rate + // limited. + // + // A value of 0 means unlimited. + // + // For fine grained control over rate limits, including making them apply + // to local peers, see peer-classes_. + upload_rate_limit, + download_rate_limit, +#if TORRENT_ABI_VERSION == 1 + local_upload_rate_limit TORRENT_DEPRECATED_ENUM, + local_download_rate_limit TORRENT_DEPRECATED_ENUM, +#else + deprecated_local_upload_rate_limit, + deprecated_local_download_rate_limit, +#endif + + // the number of bytes per second (on average) the DHT is allowed to send. + // If the incoming requests causes to many bytes to be sent in responses, + // incoming requests will be dropped until the quota has been replenished. + dht_upload_rate_limit, + + // ``unchoke_slots_limit`` is the max number of unchoked peers in the + // session. The number of unchoke slots may be ignored depending on + // what ``choking_algorithm`` is set to. Setting this limit to -1 + // means unlimited, i.e. all peers will always be unchoked. + unchoke_slots_limit, + +#if TORRENT_ABI_VERSION == 1 + // ``half_open_limit`` sets the maximum number of half-open + // connections libtorrent will have when connecting to peers. A + // half-open connection is one where connect() has been called, but + // the connection still hasn't been established (nor failed). Windows + // XP Service Pack 2 sets a default, system wide, limit of the number + // of half-open connections to 10. So, this limit can be used to work + // nicer together with other network applications on that system. The + // default is to have no limit, and passing -1 as the limit, means to + // have no limit. When limiting the number of simultaneous connection + // attempts, peers will be put in a queue waiting for their turn to + // get connected. + half_open_limit TORRENT_DEPRECATED_ENUM, +#else + deprecated_half_open_limit, +#endif + + // ``connections_limit`` sets a global limit on the number of + // connections opened. The number of connections is set to a hard + // minimum of at least two per torrent, so if you set a too low + // connections limit, and open too many torrents, the limit will not + // be met. + connections_limit, + + // ``connections_slack`` is the number of incoming connections + // exceeding the connection limit to accept in order to potentially + // replace existing ones. + connections_slack, + + // ``utp_target_delay`` is the target delay for uTP sockets in + // milliseconds. A high value will make uTP connections more + // aggressive and cause longer queues in the upload bottleneck. It + // cannot be too low, since the noise in the measurements would cause + // it to send too slow. + // ``utp_gain_factor`` is the number of bytes the uTP congestion + // window can increase at the most in one RTT. + // If this is set too high, the congestion controller reacts + // too hard to noise and will not be stable, if it's set too low, it + // will react slow to congestion and not back off as fast. + // + // ``utp_min_timeout`` is the shortest allowed uTP socket timeout, + // specified in milliseconds. The + // timeout depends on the RTT of the connection, but is never smaller + // than this value. A connection times out when every packet in a + // window is lost, or when a packet is lost twice in a row (i.e. the + // resent packet is lost as well). + // + // The shorter the timeout is, the faster the connection will recover + // from this situation, assuming the RTT is low enough. + // ``utp_syn_resends`` is the number of SYN packets that are sent (and + // timed out) before giving up and closing the socket. + // ``utp_num_resends`` is the number of times a packet is sent (and + // lost or timed out) before giving up and closing the connection. + // ``utp_connect_timeout`` is the number of milliseconds of timeout + // for the initial SYN packet for uTP connections. For each timed out + // packet (in a row), the timeout is doubled. ``utp_loss_multiplier`` + // controls how the congestion window is changed when a packet loss is + // experienced. It's specified as a percentage multiplier for + // ``cwnd``. Do not change this value unless you know what you're doing. + // Never set it higher than 100. + utp_target_delay, + utp_gain_factor, + utp_min_timeout, + utp_syn_resends, + utp_fin_resends, + utp_num_resends, + utp_connect_timeout, +#if TORRENT_ABI_VERSION == 1 + utp_delayed_ack TORRENT_DEPRECATED_ENUM, +#else + deprecated_utp_delayed_ack, +#endif + utp_loss_multiplier, + + // The ``mixed_mode_algorithm`` determines how to treat TCP + // connections when there are uTP connections. Since uTP is designed + // to yield to TCP, there's an inherent problem when using swarms that + // have both TCP and uTP connections. If nothing is done, uTP + // connections would often be starved out for bandwidth by the TCP + // connections. This mode is ``prefer_tcp``. The ``peer_proportional`` + // mode simply looks at the current throughput and rate limits all TCP + // connections to their proportional share based on how many of the + // connections are TCP. This works best if uTP connections are not + // rate limited by the global rate limiter (which they aren't by + // default). + mixed_mode_algorithm, + + // ``listen_queue_size`` is the value passed in to listen() for the + // listen socket. It is the number of outstanding incoming connections + // to queue up while we're not actively waiting for a connection to be + // accepted. 5 should be sufficient for any + // normal client. If this is a high performance server which expects + // to receive a lot of connections, or used in a simulator or test, it + // might make sense to raise this number. It will not take affect + // until the ``listen_interfaces`` settings is updated. + listen_queue_size, + + // ``torrent_connect_boost`` is the number of peers to try to connect + // to immediately when the first tracker response is received for a + // torrent. This is a boost to given to new torrents to accelerate + // them starting up. The normal connect scheduler is run once every + // second, this allows peers to be connected immediately instead of + // waiting for the session tick to trigger connections. + // This may not be set higher than 255. + torrent_connect_boost, + + // ``alert_queue_size`` is the maximum number of alerts queued up + // internally. If alerts are not popped, the queue will eventually + // fill up to this level. Once the alert queue is full, additional + // alerts will be dropped, and not delivered to the client. Once the + // client drains the queue, new alerts may be delivered again. In order + // to know that alerts have been dropped, see + // session_handle::dropped_alerts(). + alert_queue_size, + + // ``max_metadata_size`` is the maximum allowed size (in bytes) to be + // received by the metadata extension, i.e. magnet links. + max_metadata_size, + + // ``hashing_threads`` is the number of disk I/O threads to use for + // piece hash verification. These threads are *in addition* to the + // regular disk I/O threads specified by settings_pack::aio_threads. + // These threads are only used for full checking of torrents. The + // hash checking done while downloading are done by the regular disk + // I/O threads. + // The hasher threads do not only compute hashes, but also perform + // the read from disk. On storage optimal for sequential access, + // such as hard drives, this setting should be set to 1, which is + // also the default. + hashing_threads, + + // the number of blocks to keep outstanding at any given time when + // checking torrents. Higher numbers give faster re-checks but uses + // more memory. Specified in number of 16 kiB blocks + checking_mem_usage, + + // if set to > 0, pieces will be announced to other peers before they + // are fully downloaded (and before they are hash checked). The + // intention is to gain 1.5 potential round trip times per downloaded + // piece. When non-zero, this indicates how many milliseconds in + // advance pieces should be announced, before they are expected to be + // completed. + predictive_piece_announce, + + // for some aio back-ends, ``aio_threads`` specifies the number of + // io-threads to use. + aio_threads, + +#if TORRENT_ABI_VERSION == 1 + // for some aio back-ends, ``aio_max`` specifies the max number of + // outstanding jobs. + aio_max TORRENT_DEPRECATED_ENUM, + + // .. note:: This is not implemented + // + // ``network_threads`` is the number of threads to use to call + // ``async_write_some`` (i.e. send) on peer connection sockets. When + // seeding at extremely high rates, this may become a bottleneck, and + // setting this to 2 or more may parallelize that cost. When using SSL + // torrents, all encryption for outgoing traffic is done within the + // socket send functions, and this will help parallelizing the cost of + // SSL encryption as well. + network_threads TORRENT_DEPRECATED_ENUM, + + // ``ssl_listen`` sets the listen port for SSL connections. If this is + // set to 0, no SSL listen port is opened. Otherwise a socket is + // opened on this port. This setting is only taken into account when + // opening the regular listen port, and won't re-open the listen + // socket simply by changing this setting. + ssl_listen TORRENT_DEPRECATED_ENUM, +#else + // hidden + deprecated_aio_max, + deprecated_network_threads, + deprecated_ssl_listen, +#endif + + // ``tracker_backoff`` determines how aggressively to back off from + // retrying failing trackers. This value determines *x* in the + // following formula, determining the number of seconds to wait until + // the next retry: + // + // delay = 5 + 5 * x / 100 * fails^2 + // + // This setting may be useful to make libtorrent more or less + // aggressive in hitting trackers. + tracker_backoff, + + // when a seeding torrent reaches either the share ratio (bytes up / + // bytes down) or the seed time ratio (seconds as seed / seconds as + // downloader) or the seed time limit (seconds as seed) it is + // considered done, and it will leave room for other torrents. These + // are specified as percentages. Torrents that are considered done will + // still be allowed to be seeded, they just won't have priority anymore. + // For more, see queuing_. + share_ratio_limit, + seed_time_ratio_limit, + + // peer_turnover is the percentage of peers to disconnect every + // turnover peer_turnover_interval (if we're at the peer limit), this + // is specified in percent when we are connected to more than limit * + // peer_turnover_cutoff peers disconnect peer_turnover fraction of the + // peers. It is specified in percent peer_turnover_interval is the + // interval (in seconds) between optimistic disconnects if the + // disconnects happen and how many peers are disconnected is + // controlled by peer_turnover and peer_turnover_cutoff + peer_turnover, + peer_turnover_cutoff, + peer_turnover_interval, + + // this setting controls the priority of downloading torrents over + // seeding or finished torrents when it comes to making peer + // connections. Peer connections are throttled by the connection_speed + // and the half-open connection limit. This makes peer connections a + // limited resource. Torrents that still have pieces to download are + // prioritized by default, to avoid having many seeding torrents use + // most of the connection attempts and only give one peer every now + // and then to the downloading torrent. libtorrent will loop over the + // downloading torrents to connect a peer each, and every n:th + // connection attempt, a finished torrent is picked to be allowed to + // connect to a peer. This setting controls n. + connect_seed_every_n_download, + + // the max number of bytes to allow an HTTP response to be when + // announcing to trackers or downloading .torrent files via the + // ``url`` provided in ``add_torrent_params``. + max_http_recv_buffer_size, + + // if binding to a specific port fails, should the port be incremented + // by one and tried again? This setting specifies how many times to + // retry a failed port bind + max_retry_port_bind, + + // a bitmask combining flags from alert_category_t defining which + // kinds of alerts to receive + alert_mask, + + // control the settings for incoming and outgoing connections + // respectively. see enc_policy enum for the available options. + // Keep in mind that protocol encryption degrades performance in + // several respects: + // + // 1. It prevents "zero copy" disk buffers being sent to peers, since + // each peer needs to mutate the data (i.e. encrypt it) the data + // must be copied per peer connection rather than sending the same + // buffer to multiple peers. + // 2. The encryption itself requires more CPU than plain bittorrent + // protocol. The highest cost is the Diffie Hellman exchange on + // connection setup. + // 3. The encryption handshake adds several round-trips to the + // connection setup, and delays transferring data. + out_enc_policy, + in_enc_policy, + + // determines the encryption level of the connections. This setting + // will adjust which encryption scheme is offered to the other peer, + // as well as which encryption scheme is selected by the client. See + // enc_level enum for options. + allowed_enc_level, + + // the download and upload rate limits for a torrent to be considered + // active by the queuing mechanism. A torrent whose download rate is + // less than ``inactive_down_rate`` and whose upload rate is less than + // ``inactive_up_rate`` for ``auto_manage_startup`` seconds, is + // considered inactive, and another queued torrent may be started. + // This logic is disabled if ``dont_count_slow_torrents`` is false. + inactive_down_rate, + inactive_up_rate, + + // proxy to use. see proxy_type_t. + proxy_type, + + // the port of the proxy server + proxy_port, + + // sets the i2p_ SAM bridge port to connect to. set the hostname with + // the ``i2p_hostname`` setting. + // + // .. _i2p: http://www.i2p2.de + i2p_port, + +#if TORRENT_ABI_VERSION == 1 + // this determines the max number of volatile disk cache blocks. If the + // number of volatile blocks exceed this limit, other volatile blocks + // will start to be evicted. A disk cache block is volatile if it has + // low priority, and should be one of the first blocks to be evicted + // under pressure. For instance, blocks pulled into the cache as the + // result of calculating a piece hash are volatile. These blocks don't + // represent potential interest among peers, so the value of keeping + // them in the cache is limited. + cache_size_volatile, +#else + deprecated_cache_size_volatile, +#endif + + // The maximum request range of an url seed in bytes. This value + // defines the largest possible sequential web seed request. Lower values + // are possible but will be ignored if they are lower then piece size. + // This value should be related to your download speed to prevent + // libtorrent from creating too many expensive http requests per + // second. You can select a value as high as you want but keep in mind + // that libtorrent can't create parallel requests if the first request + // did already select the whole file. + // If you combine bittorrent seeds with web seeds and pick strategies + // like rarest first you may find your web seed requests split into + // smaller parts because we don't download already picked pieces + // twice. + urlseed_max_request_bytes, + + // time to wait until a new retry of a web seed name lookup + web_seed_name_lookup_retry, + + // the number of seconds between closing the file opened the longest + // ago. 0 means to disable the feature. The purpose of this is to + // periodically close files to trigger the operating system flushing + // disk cache. Specifically it has been observed to be required on + // windows to not have the disk cache grow indefinitely. + // This defaults to 240 seconds on windows, and disabled on other + // systems. + close_file_interval, + + // When uTP experiences packet loss, it will reduce the congestion + // window, and not reduce it again for this many milliseconds, even if + // experiencing another lost packet. + utp_cwnd_reduce_timer, + + // the max number of web seeds to have connected per torrent at any + // given time. + max_web_seed_connections, + + // the number of seconds before the internal host name resolver + // considers a cache value timed out, negative values are interpreted + // as zero. + resolver_cache_timeout, + + // specify the not-sent low watermark for socket send buffers. This + // corresponds to the, Linux-specific, ``TCP_NOTSENT_LOWAT`` TCP socket + // option. + send_not_sent_low_watermark, + + // the rate based choker compares the upload rate to peers against a + // threshold that increases proportionally by its size for every + // peer it visits, visiting peers in decreasing upload rate. The + // number of upload slots is determined by the number of peers whose + // upload rate exceeds the threshold. This option sets the start + // value for this threshold. A higher value leads to fewer unchoke + // slots, a lower value leads to more. + rate_choker_initial_threshold, + + // The expiration time of UPnP port-mappings, specified in seconds. 0 + // means permanent lease. Some routers do not support expiration times + // on port-maps (nor correctly returning an error indicating lack of + // support). In those cases, set this to 0. Otherwise, don't set it any + // lower than 5 minutes. + upnp_lease_duration, + + // limits the number of concurrent HTTP tracker announces. Once the + // limit is hit, tracker requests are queued and issued when an + // outstanding announce completes. + max_concurrent_http_announces, + + // the maximum number of peers to send in a reply to ``get_peers`` + dht_max_peers_reply, + + // the number of concurrent search request the node will send when + // announcing and refreshing the routing table. This parameter is called + // alpha in the kademlia paper + dht_search_branching, + + // the maximum number of failed tries to contact a node before it is + // removed from the routing table. If there are known working nodes that + // are ready to replace a failing node, it will be replaced immediately, + // this limit is only used to clear out nodes that don't have any node + // that can replace them. + dht_max_fail_count, + + // the total number of torrents to track from the DHT. This is simply an + // upper limit to make sure malicious DHT nodes cannot make us allocate + // an unbounded amount of memory. + dht_max_torrents, + + // max number of items the DHT will store + dht_max_dht_items, + + // the max number of peers to store per torrent (for the DHT) + dht_max_peers, + + // the max number of torrents to return in a torrent search query to the + // DHT + dht_max_torrent_search_reply, + + // the number of seconds a DHT node is banned if it exceeds the rate + // limit. The rate limit is averaged over 10 seconds to allow for bursts + // above the limit. + dht_block_timeout, + + // the max number of packets per second a DHT node is allowed to send + // without getting banned. + dht_block_ratelimit, + + // the number of seconds a immutable/mutable item will be expired. + // default is 0, means never expires. + dht_item_lifetime, + + // the info-hashes sample recomputation interval (in seconds). + // The node will precompute a subset of the tracked info-hashes and return + // that instead of calculating it upon each request. The permissible range + // is between 0 and 21600 seconds (inclusive). + dht_sample_infohashes_interval, + + // the maximum number of elements in the sampled subset of info-hashes. + // If this number is too big, expect the DHT storage implementations + // to clamp it in order to allow UDP packets go through + dht_max_infohashes_sample_count, + + // ``max_piece_count`` is the maximum allowed number of pieces in + // metadata received via magnet links. Loading large torrents (with + // more pieces than the default limit) may also require passing in + // a higher limit to read_resume_data() and + // torrent_info::parse_info_section(), if those are used. + max_piece_count, + + // when receiving metadata (torrent file) from peers, this is the + // max number of bencoded tokens we're willing to parse. This limit + // is meant to prevent DoS attacks on peers. For very large + // torrents, this limit may have to be raised. + metadata_token_limit, + + // controls whether disk writes will be made through a memory mapped + // file or via normal write calls. This only affects the + // mmap_disk_io. When saving to a non-local drive (network share, + // NFS or NAS) using memory mapped files is most likely inferior. + // When writing to a local SSD (especially in DAX mode) using memory + // mapped files likely gives the best performance. + // The values for this setting are specified as mmap_write_mode_t. + disk_write_mode, + + // when using mmap_disk_io, files smaller than this number of blocks + // will not be memory mapped, but will use normal pread/pwrite + // operations. This file size limit is specified in 16 kiB blocks. + mmap_file_size_cutoff, + + // Configures the SAM session + // quantity of I2P inbound and outbound tunnels [1..16]. + // number of hops for I2P inbound and outbound tunnels [0..7] + // Changing these will not trigger a reconnect to the SAM bridge, + // they will take effect the next time the SAM connection is + // re-established (by restarting or changing i2p_hostname or + // i2p_port). + i2p_inbound_quantity, + i2p_outbound_quantity, + i2p_inbound_length, + i2p_outbound_length, + + // ``announce_port`` is the port passed along as the ``port`` parameter + // to remote trackers such as HTTP or DHT. This setting does not affect + // the effective listening port nor local service discovery announcements. + // If left as zero (default), the listening port value is used. + // + // .. note:: + // This setting is only meant for very special cases where a + // seed's listening port differs from the external port. As an + // example, if a local proxy is used and that the proxy supports + // reverse tunnels through NAT-PMP, the tracker must connect to + // the external NAT-PMP port (configured using ``announce_port``) + // instead of the actual local listening port. + announce_port, + + max_int_setting_internal + }; + + // hidden + constexpr static int num_string_settings = int(max_string_setting_internal) - int(string_type_base); + constexpr static int num_bool_settings = int(max_bool_setting_internal) - int(bool_type_base); + constexpr static int num_int_settings = int(max_int_setting_internal) - int(int_type_base); + + enum mmap_write_mode_t : std::uint8_t + { + // disable writing to disk via mmap, always use normal write calls + always_pwrite = 0, + + // prefer using memory mapped files for disk writes (at least for + // large files where it might make sense) + always_mmap_write, + + // determine whether to use pwrite or memory mapped files for disk + // writes depending on the kind of storage behind the save path + auto_mmap_write, + }; + + enum suggest_mode_t : std::uint8_t { no_piece_suggestions = 0, suggest_read_cache = 1 }; + + enum choking_algorithm_t : std::uint8_t + { + // This is the traditional choker with a fixed number of unchoke + // slots (as specified by settings_pack::unchoke_slots_limit). + fixed_slots_choker = 0, + + // This opens up unchoke slots based on the upload rate achieved to + // peers. The more slots that are opened, the marginal upload rate + // required to open up another slot increases. Configure the initial + // threshold with settings_pack::rate_choker_initial_threshold. + // + // For more information, see `rate based choking`_. + rate_based_choker = 2, +#if TORRENT_ABI_VERSION == 1 + bittyrant_choker TORRENT_DEPRECATED_ENUM = 3 +#else + deprecated_bittyrant_choker = 3 +#endif + }; + + enum seed_choking_algorithm_t : std::uint8_t + { + // which round-robins the peers that are unchoked + // when seeding. This distributes the upload bandwidth uniformly and + // fairly. It minimizes the ability for a peer to download everything + // without redistributing it. + round_robin, + + // unchokes the peers we can send to the fastest. This might be a + // bit more reliable in utilizing all available capacity. + fastest_upload, + + // prioritizes peers who have just started or are + // just about to finish the download. The intention is to force + // peers in the middle of the download to trade with each other. + // This does not just take into account the pieces a peer is + // reporting having downloaded, but also the pieces we have sent + // to it. + anti_leech + }; + + enum io_buffer_mode_t : std::uint8_t + { + enable_os_cache = 0, +#if TORRENT_ABI_VERSION == 1 + disable_os_cache_for_aligned_files TORRENT_DEPRECATED_ENUM = 2, +#else + deprecated_disable_os_cache_for_aligned_files = 1, +#endif + disable_os_cache = 2, + + write_through = 3, + }; + + enum bandwidth_mixed_algo_t : std::uint8_t + { + // disables the mixed mode bandwidth balancing + prefer_tcp = 0, + + // does not throttle uTP, throttles TCP to the same proportion + // of throughput as there are TCP connections + peer_proportional = 1 + }; + + // the encoding policy options for use with + // settings_pack::out_enc_policy and settings_pack::in_enc_policy. + enum enc_policy : std::uint8_t + { + // Only encrypted connections are allowed. Incoming connections that + // are not encrypted are closed and if the encrypted outgoing + // connection fails, a non-encrypted retry will not be made. + pe_forced, + + // encrypted connections are enabled, but non-encrypted connections + // are allowed. An incoming non-encrypted connection will be accepted, + // and if an outgoing encrypted connection fails, a non- encrypted + // connection will be tried. + pe_enabled, + + // only non-encrypted connections are allowed. + pe_disabled + }; + + // the encryption levels, to be used with + // settings_pack::allowed_enc_level. + enum enc_level : std::uint8_t + { + // use only plain text encryption + pe_plaintext = 1, + // use only RC4 encryption + pe_rc4 = 2, + // allow both + pe_both = 3 + }; + + enum proxy_type_t : std::uint8_t + { + // No proxy server is used and all other fields are ignored. + none, + + // The server is assumed to be a `SOCKS4 server`_ that requires a + // username. + // + // .. _`SOCKS4 server`: http://www.ufasoft.com/doc/socks4_protocol.htm + socks4, + + // The server is assumed to be a SOCKS5 server (`RFC 1928`_) that does + // not require any authentication. The username and password are + // ignored. + // + // .. _`RFC 1928`: http://www.faqs.org/rfcs/rfc1928.html + socks5, + + // The server is assumed to be a SOCKS5 server that supports plain + // text username and password authentication (`RFC 1929`_). The + // username and password specified may be sent to the proxy if it + // requires. + // + // .. _`RFC 1929`: http://www.faqs.org/rfcs/rfc1929.html + socks5_pw, + + // The server is assumed to be an HTTP proxy. If the transport used + // for the connection is non-HTTP, the server is assumed to support + // the CONNECT_ method. i.e. for web seeds and HTTP trackers, a plain + // proxy will suffice. The proxy is assumed to not require + // authorization. The username and password will not be used. + // + // .. _CONNECT: http://tools.ietf.org/html/draft-luotonen-web-proxy-tunneling-01 + http, + + // The server is assumed to be an HTTP proxy that requires user + // authorization. The username and password will be sent to the proxy. + http_pw, + +#if TORRENT_USE_I2P + // internal + // This is used internally to communicate with the + // http_tracker_connection. To configure an i2p SAM bridge, set + // i2p_hostname and i2p_port. + i2p_proxy +#endif + }; + private: + + std::vector> m_strings; + std::vector> m_ints; + std::vector> m_bools; + }; +} + +#endif diff --git a/docs/include/libtorrent/sha1.hpp b/docs/include/libtorrent/sha1.hpp new file mode 100644 index 0000000..185d0ee --- /dev/null +++ b/docs/include/libtorrent/sha1.hpp @@ -0,0 +1,43 @@ +/* +SHA-1 C++ conversion + +original version: + +SHA-1 in C +By Steve Reid +100% Public Domain + +changelog at the end of sha1.cpp +*/ + +#ifndef TORRENT_SHA1_HPP_INCLUDED +#define TORRENT_SHA1_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if !defined TORRENT_USE_LIBGCRYPT \ + && !TORRENT_USE_COMMONCRYPTO \ + && !TORRENT_USE_CNG \ + && !TORRENT_USE_CRYPTOAPI \ + && !defined TORRENT_USE_LIBCRYPTO + +#include + +namespace libtorrent { + + struct sha1_ctx + { + std::uint32_t state[5]; + std::uint32_t count[2]; + std::uint8_t buffer[64]; + }; + + // we don't want these to clash with openssl's libcrypto + TORRENT_EXTRA_EXPORT void SHA1_init(sha1_ctx* context); + TORRENT_EXTRA_EXPORT void SHA1_update(sha1_ctx* context + , std::uint8_t const* data, size_t len); + TORRENT_EXTRA_EXPORT void SHA1_final(std::uint8_t* digest, sha1_ctx* context); +} + +#endif +#endif diff --git a/docs/include/libtorrent/sha1_hash.hpp b/docs/include/libtorrent/sha1_hash.hpp new file mode 100644 index 0000000..2c1bb83 --- /dev/null +++ b/docs/include/libtorrent/sha1_hash.hpp @@ -0,0 +1,316 @@ +/* + +Copyright (c) 2013-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2020, Mike Tzou +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SHA1_HASH_HPP_INCLUDED +#define TORRENT_SHA1_HASH_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/byteswap.hpp" +#include "libtorrent/aux_/ffs.hpp" +#include "libtorrent/span.hpp" + +#if TORRENT_USE_IOSTREAM +#include +#endif // TORRENT_USE_IOSTREAM + +namespace libtorrent { + + // This type holds an N digest or any other kind of N bits + // sequence. It implements a number of convenience functions, such + // as bit operations, comparison operators etc. + // + // This data structure is 32 bits aligned, like it's the case for + // each SHA-N specification. + template + class digest32 + { + static_assert(N % 32 == 0, "N must be a multiple of 32"); + static constexpr std::ptrdiff_t number_size = N / 32; + static constexpr int bits_in_byte = 8; + public: + + using difference_type = std::ptrdiff_t; + using index_type = std::ptrdiff_t; + + // the size of the hash in bytes + static constexpr difference_type size() noexcept { return N / bits_in_byte; } + + // constructs an all-zero digest + digest32() noexcept { clear(); } + + digest32(digest32 const&) noexcept = default; + digest32& operator=(digest32 const&) noexcept = default; + + // returns an all-F digest. i.e. the maximum value + // representable by an N bit number (N/8 bytes). This is + // a static member function. + static digest32 (max)() noexcept + { + digest32 ret; + ret.m_number.fill(0xffffffff); + return ret; + } + + // returns an all-zero digest. i.e. the minimum value + // representable by an N bit number (N/8 bytes). This is + // a static member function. + static digest32 (min)() noexcept + { + digest32 ret; + // all bits are already 0 + return ret; + } + + // copies N/8 bytes from the pointer provided, into the digest. + // The passed in string MUST be at least N/8 bytes. 0-terminators + // are ignored, ``s`` is treated like a raw memory buffer. + explicit digest32(char const* s) noexcept + { + if (s == nullptr) clear(); + else std::memcpy(m_number.data(), s, size()); + } +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + explicit digest32(std::string const& s) + { + assign(s.data()); + } +#endif + explicit digest32(span s) noexcept + { + assign(s); + } + void assign(span s) noexcept + { + TORRENT_ASSERT(s.size() >= N / bits_in_byte); + auto const sl = s.size() < size() ? s.size() : size(); + std::memcpy(m_number.data(), s.data(), static_cast(sl)); + } + void assign(char const* str) noexcept { std::memcpy(m_number.data(), str, size()); } + + char const* data() const noexcept { return reinterpret_cast(m_number.data()); } + char* data() noexcept { return reinterpret_cast(m_number.data()); } + + // set the digest to all zeros. + void clear() noexcept { m_number.fill(0); } + + // return true if the digest is all zero. + bool is_all_zeros() const noexcept + { + return std::all_of(m_number.begin(), m_number.end() + , [](std::uint32_t v) { return v == 0; }); + } + + // shift left or right ``n`` bits. + digest32& operator<<=(int n) & noexcept; + digest32& operator>>=(int n) & noexcept; + + // standard comparison operators + bool operator==(digest32 const& n) const noexcept + { + return std::equal(n.m_number.begin(), n.m_number.end(), m_number.begin()); + } + bool operator!=(digest32 const& n) const noexcept + { + return !std::equal(n.m_number.begin(), n.m_number.end(), m_number.begin()); + } + bool operator<(digest32 const& n) const noexcept + { + for (auto const v : boost::combine(m_number, n.m_number)) + { + std::uint32_t const lhs = aux::network_to_host(boost::get<0>(v)); + std::uint32_t const rhs = aux::network_to_host(boost::get<1>(v)); + if (lhs < rhs) return true; + if (lhs > rhs) return false; + } + return false; + } + + int count_leading_zeroes() const noexcept + { + return aux::count_leading_zeros(m_number); + } + + // returns a bit-wise negated copy of the digest + digest32 operator~() const noexcept + { + digest32 ret; + for (auto const v : boost::combine(m_number, ret.m_number)) + boost::get<1>(v) = ~boost::get<0>(v); + return ret; + } + + // returns the bit-wise XOR of the two digests. + digest32 operator^(digest32 const& n) const noexcept + { + digest32 ret = *this; + ret ^= n; + return ret; + } + + // in-place bit-wise XOR with the passed in digest. + digest32& operator^=(digest32 const& n) & noexcept + { + for (auto const v : boost::combine(m_number, n.m_number)) + boost::get<0>(v) ^= boost::get<1>(v); + return *this; + } + + // returns the bit-wise AND of the two digests. + digest32 operator&(digest32 const& n) const noexcept + { + digest32 ret = *this; + ret &= n; + return ret; + } + + // in-place bit-wise AND of the passed in digest + digest32& operator&=(digest32 const& n) & noexcept + { + for (auto const v : boost::combine(m_number, n.m_number)) + boost::get<0>(v) &= boost::get<1>(v); + return *this; + } + + // in-place bit-wise OR of the two digests. + digest32& operator|=(digest32 const& n) & noexcept + { + for (auto const v : boost::combine(m_number, n.m_number)) + boost::get<0>(v) |= boost::get<1>(v); + return *this; + } + + // accessors for specific bytes + std::uint8_t& operator[](index_type i) noexcept + { + TORRENT_ASSERT(i < size()); + return reinterpret_cast(m_number.data())[i]; + } + std::uint8_t const& operator[](index_type i) const noexcept + { + TORRENT_ASSERT(i < size()); + return reinterpret_cast(m_number.data())[i]; + } + + using const_iterator = std::uint8_t const*; + using iterator = std::uint8_t*; + + // start and end iterators for the hash. The value type + // of these iterators is ``std::uint8_t``. + const_iterator begin() const + { return reinterpret_cast(m_number.data()); } + const_iterator end() const + { return reinterpret_cast(m_number.data()) + size(); } + iterator begin() + { return reinterpret_cast(m_number.data()); } + iterator end() + { return reinterpret_cast(m_number.data()) + size(); } + + // return a copy of the N/8 bytes representing the digest as a std::string. + // It's still a binary string with N/8 binary characters. + std::string to_string() const + { + return std::string(reinterpret_cast(m_number.data()), size()); + } + +#if TORRENT_USE_IOSTREAM + // print a sha1_hash object to an ostream as 40 hexadecimal digits + friend std::ostream& operator<<(std::ostream& os, digest32 const& val) + { val.stream_out(os); return os; } + + // read 40 hexadecimal digits from an istream into a sha1_hash + friend std::istream& operator>>(std::istream& is, digest32& val) + { val.stream_in(is); return is; } +#endif // TORRENT_USE_IOSTREAM + + private: + +#if TORRENT_USE_IOSTREAM + void stream_in(std::istream& is); + void stream_out(std::ostream& os) const; +#endif // TORRENT_USE_IOSTREAM + + std::array m_number; + }; + + // This type holds a SHA-1 digest or any other kind of 20 byte + // sequence. It implements a number of convenience functions, such + // as bit operations, comparison operators etc. + // + // In libtorrent it is primarily used to hold info-hashes, piece-hashes, + // peer IDs, node IDs etc. + using sha1_hash = digest32<160>; + using sha256_hash = digest32<256>; + +#if TORRENT_USE_IOSTREAM + extern template void digest32<160>::stream_out(std::ostream&) const; + extern template void digest32<256>::stream_out(std::ostream&) const; + extern template void digest32<160>::stream_in(std::istream&); + extern template void digest32<256>::stream_in(std::istream&); +#endif // TORRENT_USE_IOSTREAM + + extern template digest32<160>& digest32<160>::operator<<=(int) & noexcept; + extern template digest32<256>& digest32<256>::operator<<=(int) & noexcept; + extern template digest32<160>& digest32<160>::operator>>=(int) & noexcept; + extern template digest32<256>& digest32<256>::operator>>=(int) & noexcept; +} + +namespace std { + template + struct hash> + { + std::size_t operator()(libtorrent::digest32 const& k) const + { + std::size_t ret; + static_assert(N >= sizeof(ret) * 8, "hash is not defined for small digests"); + // this is OK because digest32 is already a hash + std::memcpy(&ret, k.data(), sizeof(ret)); + return ret; + } + }; +} + +#endif // TORRENT_SHA1_HASH_HPP_INCLUDED diff --git a/docs/include/libtorrent/sha256.hpp b/docs/include/libtorrent/sha256.hpp new file mode 100644 index 0000000..df96d26 --- /dev/null +++ b/docs/include/libtorrent/sha256.hpp @@ -0,0 +1,33 @@ +// SHA-256. Adapted from LibTomCrypt. This code is Public Domain + +#ifndef TORRENT_SHA256_HPP_INCLUDED +#define TORRENT_SHA256_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if !defined TORRENT_USE_LIBGCRYPT \ + && !TORRENT_USE_COMMONCRYPTO \ + && !TORRENT_USE_CNG \ + && !TORRENT_USE_CRYPTOAPI_SHA_512 \ + && !defined TORRENT_USE_LIBCRYPTO + +#include + +namespace libtorrent { + + struct sha256_ctx + { + std::uint64_t length; + std::uint32_t state[8]; + std::uint32_t curlen; + std::uint8_t buf[64]; + }; + + TORRENT_EXTRA_EXPORT void SHA256_init(sha256_ctx& md); + TORRENT_EXTRA_EXPORT void SHA256_update(sha256_ctx& md + , std::uint8_t const* in, size_t len); + TORRENT_EXTRA_EXPORT void SHA256_final(std::uint8_t* digest, sha256_ctx& md); +} + +#endif +#endif diff --git a/docs/include/libtorrent/sliding_average.hpp b/docs/include/libtorrent/sliding_average.hpp new file mode 100644 index 0000000..b2c813f --- /dev/null +++ b/docs/include/libtorrent/sliding_average.hpp @@ -0,0 +1,93 @@ +/* + +Copyright (c) 2010, 2014, 2016-2019, Arvid Norberg +Copyright (c) 2019, Amir Abrams +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SLIDING_AVERAGE_HPP_INCLUDED +#define TORRENT_SLIDING_AVERAGE_HPP_INCLUDED + +#include +#include // for std::abs +#include +#include // for is_integral + +#include "libtorrent/assert.hpp" + +namespace libtorrent { + +// an exponential moving average accumulator. Add samples to it and it keeps +// track of a moving mean value and an average deviation +template +struct sliding_average +{ + static_assert(std::is_integral::value, "template argument must be integral"); + + sliding_average(): m_mean(0), m_average_deviation(0), m_num_samples(0) {} + sliding_average(sliding_average const&) = default; + sliding_average& operator=(sliding_average const&) = default; + + void add_sample(Int s) + { + TORRENT_ASSERT(s < std::numeric_limits::max() / 64); + // fixed point + s *= 64; + Int const deviation = (m_num_samples > 0) ? std::abs(m_mean - s) : 0; + + if (m_num_samples < inverted_gain) + ++m_num_samples; + + m_mean += (s - m_mean) / m_num_samples; + + if (m_num_samples > 1) { + // the exact same thing for deviation off the mean except -1 on + // the samples, because the number of deviation samples always lags + // behind by 1 (you need to actual samples to have a single deviation + // sample). + m_average_deviation += (deviation - m_average_deviation) / (m_num_samples - 1); + } + } + + Int mean() const { return m_num_samples > 0 ? (m_mean + 32) / 64 : 0; } + Int avg_deviation() const { return m_num_samples > 1 ? (m_average_deviation + 32) / 64 : 0; } + int num_samples() const { return m_num_samples; } + +private: + // both of these are fixed point values (* 64) + Int m_mean = 0; + Int m_average_deviation = 0; + // the number of samples we have received, but no more than inverted_gain + // this is the effective inverted_gain + int m_num_samples = 0; +}; + +} + +#endif diff --git a/docs/include/libtorrent/socket.hpp b/docs/include/libtorrent/socket.hpp new file mode 100644 index 0000000..a4e046b --- /dev/null +++ b/docs/include/libtorrent/socket.hpp @@ -0,0 +1,298 @@ +/* + +Copyright (c) 2003-2004, 2006-2010, 2012, 2014-2022, Arvid Norberg +Copyright (c) 2017, Alden Torres +Copyright (c) 2018, Alexandre Janniaux +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SOCKET_HPP_INCLUDED +#define TORRENT_SOCKET_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +// if building as Objective C++, asio's template +// parameters Protocol has to be renamed to avoid +// colliding with keywords + +#ifdef __OBJC__ +#define Protocol Protocol_ +#endif + +#if defined TORRENT_WINDOWS || defined TORRENT_CYGWIN +// asio assumes that the windows error codes are defined already +#include +#endif + +#include +#include +#include +#include + +#ifdef __OBJC__ +#undef Protocol +#endif + +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#endif + +#if TORRENT_USE_NETLINK +#include +#ifndef SOL_NETLINK +#define SOL_NETLINK 270 +#endif + +// NETLINK_NO_ENOBUFS exists at least since android 2.3, but is not exposed +#if defined TORRENT_ANDROID && !defined NETLINK_NO_ENOBUFS +#define NETLINK_NO_ENOBUFS 5 +#endif +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + +#if defined TORRENT_BUILD_SIMULATOR +struct tcp : sim::asio::ip::tcp { + tcp(sim::asio::ip::tcp const& p) : sim::asio::ip::tcp(p) {} // NOLINT + using socket = aux::noexcept_move_only; +}; +struct udp : sim::asio::ip::udp { + udp(sim::asio::ip::udp const& p) : sim::asio::ip::udp(p) {} // NOLINT + using socket = aux::noexcept_move_only; +}; + using sim::asio::async_write; + using sim::asio::async_read; + using true_tcp_socket = sim::asio::ip::tcp::socket; +#else +struct tcp : boost::asio::ip::tcp { + tcp(boost::asio::ip::tcp const& p) : boost::asio::ip::tcp(p) {} // NOLINT + using socket = aux::noexcept_move_only; +}; +struct udp : boost::asio::ip::udp { + udp(boost::asio::ip::udp const& p) : boost::asio::ip::udp(p) {} // NOLINT + using socket = aux::noexcept_move_only; +}; + using boost::asio::async_write; + using boost::asio::async_read; + using true_tcp_socket = boost::asio::ip::tcp::socket; +#endif + + // internal + inline udp::endpoint make_udp(tcp::endpoint const ep) + { return {ep.address(), ep.port()}; } + + // internal + inline tcp::endpoint make_tcp(udp::endpoint const ep) + { return {ep.address(), ep.port()}; } + +#ifdef TORRENT_WINDOWS + +#ifndef PROTECTION_LEVEL_UNRESTRICTED +#define PROTECTION_LEVEL_UNRESTRICTED 10 +#endif + +#ifndef IPV6_PROTECTION_LEVEL +#define IPV6_PROTECTION_LEVEL 30 +#endif + + struct v6_protection_level + { + explicit v6_protection_level(int level): m_value(level) {} + template + int level(Protocol const&) const { return IPPROTO_IPV6; } + template + int name(Protocol const&) const { return IPV6_PROTECTION_LEVEL; } + template + int const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; + + struct exclusive_address_use + { + explicit exclusive_address_use(int enable): m_value(enable) {} + template + int level(Protocol const&) const { return SOL_SOCKET; } + template + int name(Protocol const&) const { return SO_EXCLUSIVEADDRUSE; } + template + int const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; +#endif // TORRENT_WINDOWS + +#ifdef IPV6_TCLASS + struct traffic_class + { + explicit traffic_class(int val): m_value(val) {} + template + int level(Protocol const&) const { return IPPROTO_IPV6; } + template + int name(Protocol const&) const { return IPV6_TCLASS; } + template + int const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; +#endif + + struct type_of_service + { +#ifdef _WIN32 + using tos_t = DWORD; +#else + using tos_t = int; +#endif + explicit type_of_service(tos_t const val) : m_value(tos_t(val)) {} + template + int level(Protocol const&) const { return IPPROTO_IP; } + template + int name(Protocol const&) const { return IP_TOS; } + template + tos_t const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + tos_t m_value; + }; + +#ifdef IP_DSCP_TRAFFIC_TYPE + struct dscp_traffic_type + { + explicit dscp_traffic_type(DWORD val) : m_value(val) {} + template + int level(Protocol const&) const { return IP_DSCP_TRAFFIC_TYPE; } + template + int name(Protocol const&) const { return DSCP_TRAFFIC_TYPE; } + template + DWORD const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + DWORD m_value; + }; +#endif + +#if defined IP_DONTFRAG || defined IP_MTU_DISCOVER || defined IP_DONTFRAGMENT +#define TORRENT_HAS_DONT_FRAGMENT +#endif + +#ifdef TORRENT_HAS_DONT_FRAGMENT + + // the order of these preprocessor tests matters. Windows defines both + // IP_DONTFRAGMENT and IP_MTU_DISCOVER, but the latter is not supported + // in general, the simple option of just setting the DF bit is preferred, if + // it's available +#if defined IP_DONTFRAG || defined IP_DONTFRAGMENT + + struct dont_fragment + { + explicit dont_fragment(bool val) : m_value(val) {} + template + int level(Protocol const&) const { return IPPROTO_IP; } + template + int name(Protocol const&) const +#if defined IP_DONTFRAG + { return IP_DONTFRAG; } +#else // defined IP_DONTFRAGMENT + { return IP_DONTFRAGMENT; } +#endif + template + int const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; + +#else + + // this is the fallback mechanism using the IP_MTU_DISCOVER option, which + // does a little bit more than we want, it makes the kernel track an estimate + // of the MTU and rejects packets immediately if they are believed to exceed + // it. + struct dont_fragment + { + explicit dont_fragment(bool val) + : m_value(val ? IP_PMTUDISC_PROBE : IP_PMTUDISC_DONT) {} + template + int level(Protocol const&) const { return IPPROTO_IP; } + template + int name(Protocol const&) const { return IP_MTU_DISCOVER; } + template + int const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; + +#endif // IP_DONTFRAG vs. IP_MTU_DISCOVER + +#endif // TORRENT_HAS_DONT_FRAGMENT + +#if TORRENT_USE_NETLINK + struct no_enobufs + { + explicit no_enobufs(bool val) : m_value(val) {} + template + int level(Protocol const&) const { return SOL_NETLINK; } + template + int name(Protocol const&) const { return NETLINK_NO_ENOBUFS; } + template + int const* data(Protocol const&) const { return &m_value; } + template + std::size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; +#endif // TORRENT_USE_NETLINK + +#ifdef TCP_NOTSENT_LOWAT + struct tcp_notsent_lowat + { + explicit tcp_notsent_lowat(int val) : m_value(val) {} + template + int level(Protocol const&) const { return IPPROTO_TCP; } + template + int name(Protocol const&) const { return TCP_NOTSENT_LOWAT; } + template + int const* data(Protocol const&) const { return &m_value; } + template + std::size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; +#endif +} + +#endif // TORRENT_SOCKET_HPP_INCLUDED diff --git a/docs/include/libtorrent/socket_io.hpp b/docs/include/libtorrent/socket_io.hpp new file mode 100644 index 0000000..85474f8 --- /dev/null +++ b/docs/include/libtorrent/socket_io.hpp @@ -0,0 +1,146 @@ +/* + +Copyright (c) 2009, 2013-2019, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2016, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SOCKET_IO_HPP_INCLUDED +#define TORRENT_SOCKET_IO_HPP_INCLUDED + +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/string_view.hpp" +#include + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT std::string print_address(address const& addr); + TORRENT_EXTRA_EXPORT std::string print_endpoint(address const& addr, int port); + TORRENT_EXTRA_EXPORT std::string print_endpoint(tcp::endpoint const& ep); + TORRENT_EXTRA_EXPORT std::string print_endpoint(udp::endpoint const& ep); + TORRENT_EXTRA_EXPORT tcp::endpoint parse_endpoint(string_view str, error_code& ec); + + TORRENT_EXTRA_EXPORT std::string address_to_bytes(address const& a); + TORRENT_EXTRA_EXPORT std::string endpoint_to_bytes(udp::endpoint const& ep); + TORRENT_EXTRA_EXPORT sha1_hash hash_address(address const& ip); + +namespace aux { + + template + std::size_t address_size(Proto p) + { + if (p == Proto::v6()) + return std::tuple_size::value; + else + return std::tuple_size::value; + } + + template + void write_address(address const& a, OutIt&& out) + { + if (a.is_v4()) + { + write_uint32(a.to_v4().to_uint(), out); + } + else if (a.is_v6()) + { + for (auto b : a.to_v6().to_bytes()) + write_uint8(b, out); + } + } + + template + address_v4 read_v4_address(InIt&& in) + { + std::uint32_t const ip = read_uint32(in); + return address_v4(ip); + } + + template + address_v6 read_v6_address(InIt&& in) + { + address_v6::bytes_type bytes; + for (auto& b : bytes) + b = read_uint8(in); + return address_v6(bytes); + } + + template + void write_endpoint(Endpoint const& e, OutIt&& out) + { + write_address(e.address(), out); + write_uint16(e.port(), out); + } + + template + Endpoint read_v4_endpoint(InIt&& in) + { + address addr = read_v4_address(in); + std::uint16_t port = read_uint16(in); + return Endpoint(addr, port); + } + + template + Endpoint read_v6_endpoint(InIt&& in) + { + address addr = read_v6_address(in); + std::uint16_t port = read_uint16(in); + return Endpoint(addr, port); + } + + template + std::vector read_endpoint_list(libtorrent::bdecode_node const& n) + { + std::vector ret; + if (n.type() != bdecode_node::list_t) return ret; + for (int i = 0; i < n.list_size(); ++i) + { + bdecode_node e = n.list_at(i); + if (e.type() != bdecode_node::string_t) return ret; + if (e.string_length() < 6) continue; + char const* in = e.string_ptr(); + if (e.string_length() == 6) + ret.push_back(read_v4_endpoint(in)); + else if (e.string_length() == 18) + ret.push_back(read_v6_endpoint(in)); + } + return ret; + } +} // namespace aux + +} + +#endif diff --git a/docs/include/libtorrent/socket_type.hpp b/docs/include/libtorrent/socket_type.hpp new file mode 100644 index 0000000..ae15d31 --- /dev/null +++ b/docs/include/libtorrent/socket_type.hpp @@ -0,0 +1,65 @@ +/* + +Copyright (c) 2019-2020, Arvid Norberg +Copyright (c) 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SOCKET_TYPE_HPP +#define TORRENT_SOCKET_TYPE_HPP + +#include "libtorrent/config.hpp" +#include + +namespace libtorrent { + +// A type describing kinds of sockets involved in various operations or events. +enum class socket_type_t : std::uint8_t { + tcp, + socks5, + http, + utp, + i2p, + tcp_ssl, + socks5_ssl, + http_ssl, + utp_ssl, + +#if TORRENT_ABI_VERSION <= 2 + udp TORRENT_DEPRECATED_ENUM = utp, +#endif +}; + +// return a short human readable name for types of socket +// TODO: move to aux +char const* socket_type_name(socket_type_t); + +} + +#endif diff --git a/docs/include/libtorrent/socks5_stream.hpp b/docs/include/libtorrent/socks5_stream.hpp new file mode 100644 index 0000000..0459331 --- /dev/null +++ b/docs/include/libtorrent/socks5_stream.hpp @@ -0,0 +1,561 @@ +/* + +Copyright (c) 2007, 2009-2010, 2013-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SOCKS5_STREAM_HPP_INCLUDED +#define TORRENT_SOCKS5_STREAM_HPP_INCLUDED + +#include + +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/aux_/ip_helpers.hpp" // for is_ip_address +#include "libtorrent/assert.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/string_util.hpp" // for to_string +#include "libtorrent/socket_io.hpp" + +namespace libtorrent { + +namespace socks_error { + + // SOCKS5 error values. If an error_code has the + // socks error category (get_socks_category()), these + // are the error values. + enum socks_error_code + { + no_error = 0, + unsupported_version, + unsupported_authentication_method, + unsupported_authentication_version, + authentication_error, + username_required, + general_failure, + command_not_supported, + no_identd, + identd_error, + + num_errors + }; + + // internal + TORRENT_EXPORT boost::system::error_code make_error_code(socks_error_code e); + +} // namespace socks_error +} + +namespace boost { +namespace system { + + template<> struct is_error_code_enum + { static const bool value = true; }; + +} +} + +namespace libtorrent { + +// returns the error_category for SOCKS5 errors +TORRENT_EXPORT boost::system::error_category& socks_category(); + +#if TORRENT_ABI_VERSION == 1 +TORRENT_DEPRECATED +inline boost::system::error_category& get_socks_category() +{ return socks_category(); } +#endif + +class socks5_stream : public proxy_base +{ +public: + + // commands + enum { + socks5_connect = 1, + socks5_udp_associate = 3 + }; + + socks5_stream(socks5_stream&&) = default; + explicit socks5_stream(io_context& io_context) + : proxy_base(io_context) + , m_version(5) + , m_command(socks5_connect) + {} + + void set_version(int v) { m_version = v; } + + void set_command(int c) + { + TORRENT_ASSERT(c == socks5_connect || c == socks5_udp_associate); + m_command = c; + } + + void set_username(std::string const& user + , std::string const& password) + { + m_user = user; + m_password = password; + } + + void set_dst_name(std::string const& host) + { + // if this assert trips, set_dst_name() is called with an IP address rather + // than a hostname. Instead, resolve the IP into an address and pass it to + // async_connect instead + TORRENT_ASSERT(!aux::is_ip_address(host)); + m_dst_name = host; + if (m_dst_name.size() > 255) + m_dst_name.resize(255); + } + + void close(error_code& ec) + { + m_dst_name.clear(); + proxy_base::close(ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void close() + { + m_dst_name.clear(); + proxy_base::close(); + } +#endif + + // TODO: 2 add async_connect() that takes a hostname and port as well + template + void async_connect(endpoint_type const& endpoint, Handler handler) + { + // make sure we don't try to connect to INADDR_ANY. binding is fine, + // and using a hostname is fine on SOCKS version 5. + TORRENT_ASSERT(endpoint.address() != address() + || (!m_dst_name.empty() && m_version == 5)); + + m_remote_endpoint = endpoint; + + // the connect is split up in the following steps: + // 1. resolve name of proxy server + // 2. connect to proxy server + // 3. if version == 5: + // 3.1 send SOCKS5 authentication method message + // 3.2 read SOCKS5 authentication response + // 3.3 send username+password + // 4. send SOCKS command message + + ADD_OUTSTANDING_ASYNC("socks5_stream::name_lookup"); + m_resolver.async_resolve(m_hostname, to_string(m_port).data(), wrap_allocator( + [this](error_code const& ec, tcp::resolver::results_type ips, Handler hn) { + name_lookup(ec, std::move(ips), std::move(hn)); + }, std::move(handler))); + } + +private: + + template + void name_lookup(error_code const& e, tcp::resolver::results_type ips + , Handler h) + { + COMPLETE_ASYNC("socks5_stream::name_lookup"); + if (handle_error(e, std::move(h))) return; + + auto i = ips.begin(); + if (!m_sock.is_open()) + { + error_code ec; + m_sock.open(i->endpoint().protocol(), ec); + if (handle_error(ec, std::move(h))) return; + } + + // TODO: we could bind the socket here, since we know what the + // target endpoint is of the proxy + ADD_OUTSTANDING_ASYNC("socks5_stream::connected"); + m_sock.async_connect(i->endpoint(), wrap_allocator( + [this](error_code const& ec, Handler hn) + { connected(ec, std::move(hn)); }, std::move(h))); + } + + template + void connected(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::connected"); + if (handle_error(e, std::move(h))) return; + + using namespace libtorrent::aux; + if (m_version == 5) + { + // send SOCKS5 authentication methods + m_buffer.resize(m_user.empty()?3:4); + char* p = &m_buffer[0]; + write_uint8(5, p); // SOCKS VERSION 5 + if (m_user.empty()) + { + write_uint8(1, p); // 1 authentication method (no auth) + write_uint8(0, p); // no authentication + } + else + { + write_uint8(2, p); // 2 authentication methods + write_uint8(0, p); // no authentication + write_uint8(2, p); // username/password + } + ADD_OUTSTANDING_ASYNC("socks5_stream::handshake1"); + async_write(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake1(ec, std::move(hn)); + }, std::move(h))); + } + else if (m_version == 4) + { + socks_connect(std::move(h)); + } + else + { + std::move(h)(error_code(socks_error::unsupported_version)); + } + } + + template + void handshake1(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::handshake1"); + if (handle_error(e, std::move(h))) return; + + ADD_OUTSTANDING_ASYNC("socks5_stream::handshake2"); + m_buffer.resize(2); + async_read(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake2(ec, std::move(hn)); + }, std::move(h))); + } + + template + void handshake2(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::handshake2"); + if (handle_error(e, std::move(h))) return; + + using namespace libtorrent::aux; + + char* p = &m_buffer[0]; + int version = read_uint8(p); + int method = read_uint8(p); + + if (version < m_version) + { + std::move(h)(error_code(socks_error::unsupported_version)); + return; + } + + if (method == 0) + { + socks_connect(std::move(h)); + } + else if (method == 2) + { + if (m_user.empty()) + { + std::move(h)(error_code(socks_error::username_required)); + return; + } + + // start sub-negotiation + m_buffer.resize(m_user.size() + m_password.size() + 3); + p = &m_buffer[0]; + write_uint8(1, p); + TORRENT_ASSERT(m_user.size() < 0x100); + write_uint8(uint8_t(m_user.size()), p); + write_string(m_user, p); + TORRENT_ASSERT(m_password.size() < 0x100); + write_uint8(uint8_t(m_password.size()), p); + write_string(m_password, p); + + ADD_OUTSTANDING_ASYNC("socks5_stream::handshake3"); + async_write(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t const, Handler hn) { + handshake3(ec, std::move(hn)); + }, std::move(h))); + } + else + { + std::move(h)(error_code(socks_error::unsupported_authentication_method)); + return; + } + } + + template + void handshake3(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::handshake3"); + if (handle_error(e, std::move(h))) return; + + ADD_OUTSTANDING_ASYNC("socks5_stream::handshake4"); + m_buffer.resize(2); + async_read(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake4(ec, std::move(hn)); + }, std::move(h))); + } + + template + void handshake4(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::handshake4"); + if (handle_error(e, std::move(h))) return; + + using namespace libtorrent::aux; + + char* p = &m_buffer[0]; + int version = read_uint8(p); + int status = read_uint8(p); + + if (version != 1) + { + std::move(h)(error_code(socks_error::unsupported_authentication_version)); + return; + } + + if (status != 0) + { + std::move(h)(error_code(socks_error::authentication_error)); + return; + } + + std::vector().swap(m_buffer); + socks_connect(std::move(h)); + } + + template + void socks_connect(Handler h) + { + using namespace libtorrent::aux; + + if (m_version == 5) + { + // send SOCKS5 connect command + m_buffer.resize(6 + (!m_dst_name.empty() + ? m_dst_name.size() + 1 + :(aux::is_v4(m_remote_endpoint) ? 4 : 16))); + char* p = &m_buffer[0]; + write_uint8(5, p); // SOCKS VERSION 5 + write_uint8(std::uint8_t(m_command), p); // CONNECT command + write_uint8(0, p); // reserved + if (!m_dst_name.empty()) + { + write_uint8(3, p); // address type + TORRENT_ASSERT(m_dst_name.size() < 0x100); + write_uint8(uint8_t(m_dst_name.size()), p); + std::copy(m_dst_name.begin(), m_dst_name.end(), p); + p += m_dst_name.size(); + } + else + { + // we either need a hostname or a valid endpoint + TORRENT_ASSERT(m_remote_endpoint.address() != address()); + + write_uint8(aux::is_v4(m_remote_endpoint) ? 1 : 4, p); // address type + write_address(m_remote_endpoint.address(), p); + } + write_uint16(m_remote_endpoint.port(), p); + } + else if (m_version == 4) + { + // SOCKS4 only supports IPv4 + if (!aux::is_v4(m_remote_endpoint)) + { + std::move(h)(error_code(boost::asio::error::address_family_not_supported)); + return; + } + m_buffer.resize(m_user.size() + 9); + char* p = &m_buffer[0]; + write_uint8(4, p); // SOCKS VERSION 4 + write_uint8(std::uint8_t(m_command), p); // CONNECT command + write_uint16(m_remote_endpoint.port(), p); + write_uint32(m_remote_endpoint.address().to_v4().to_uint(), p); + std::copy(m_user.begin(), m_user.end(), p); + p += m_user.size(); + write_uint8(0, p); // 0-terminator + } + else + { + std::move(h)(error_code(socks_error::unsupported_version)); + return; + } + + ADD_OUTSTANDING_ASYNC("socks5_stream::connect1"); + async_write(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + connect1(ec, std::move(hn)); + }, std::move(h))); + } + + template + void connect1(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::connect1"); + if (handle_error(e, std::move(h))) return; + + if (m_version == 5) + m_buffer.resize(6 + 4); // assume an IPv4 address + else if (m_version == 4) + m_buffer.resize(8); + + ADD_OUTSTANDING_ASYNC("socks5_stream::connect2"); + async_read(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + connect2(ec, std::move(hn)); + }, std::move(h))); + } + + template + void connect2(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::connect2"); + if (handle_error(e, std::move(h))) return; + + using namespace libtorrent::aux; + + char const* p = &m_buffer[0]; + int const version = read_uint8(p); + int const response = read_uint8(p); + + if (m_version == 5) + { + if (version < m_version) + { + std::move(h)(error_code(socks_error::unsupported_version)); + return; + } + if (response != 0) + { + error_code ec(socks_error::general_failure); + switch (response) + { + case 2: ec = boost::asio::error::no_permission; break; + case 3: ec = boost::asio::error::network_unreachable; break; + case 4: ec = boost::asio::error::host_unreachable; break; + case 5: ec = boost::asio::error::connection_refused; break; + case 6: ec = boost::asio::error::timed_out; break; + case 7: ec = socks_error::command_not_supported; break; + case 8: ec = boost::asio::error::address_family_not_supported; break; + } + std::move(h)(ec); + return; + } + p += 1; // reserved + int const atyp = read_uint8(p); + // read the proxy IP it was bound to (this is variable length depending + // on address type) + if (atyp == 1) + { + std::vector().swap(m_buffer); + std::move(h)(e); + return; + } + std::size_t extra_bytes = 0; + if (atyp == 4) + { + // IPv6 + extra_bytes = 12; + } + else if (atyp == 3) + { + // hostname with length prefix + extra_bytes = read_uint8(p) - 3; + } + else + { + std::move(h)(error_code(boost::asio::error::address_family_not_supported)); + return; + } + m_buffer.resize(m_buffer.size() + extra_bytes); + + ADD_OUTSTANDING_ASYNC("socks5_stream::connect3"); + TORRENT_ASSERT(extra_bytes > 0); + async_read(m_sock, boost::asio::buffer(&m_buffer[m_buffer.size() - extra_bytes], extra_bytes) + , wrap_allocator([this](error_code const& ec, std::size_t, Handler hn) { + connect3(ec, std::move(hn)); + }, std::move(h))); + } + else if (m_version == 4) + { + if (version != 0) + { + std::move(h)(error_code(socks_error::general_failure)); + return; + } + + // access granted + if (response == 90) + { + std::vector().swap(m_buffer); + std::move(h)(e); + return; + } + + error_code ec(socks_error::general_failure); + switch (response) + { + case 91: ec = boost::asio::error::connection_refused; break; + case 92: ec = socks_error::no_identd; break; + case 93: ec = socks_error::identd_error; break; + } + std::move(h)(ec); + } + } + + template + void connect3(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::connect3"); + using namespace libtorrent::aux; + + if (handle_error(e, std::move(h))) return; + + std::vector().swap(m_buffer); + std::move(h)(e); + } + + // send and receive buffer + std::vector m_buffer; + // proxy authentication + std::string m_user; + std::string m_password; + std::string m_dst_name; + + int m_version; + + // the socks command to send for this connection (connect or udp associate) + int m_command; +}; + +} + +#endif diff --git a/docs/include/libtorrent/span.hpp b/docs/include/libtorrent/span.hpp new file mode 100644 index 0000000..82f5c77 --- /dev/null +++ b/docs/include/libtorrent/span.hpp @@ -0,0 +1,194 @@ +/* + +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2016-2020, Arvid Norberg +Copyright (c) 2017, Steven Siloti +Copyright (c) 2019, Michael Cronenworth +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SPAN_HPP_INCLUDED +#define TORRENT_SPAN_HPP_INCLUDED + +#include +#include +#include +#include "libtorrent/assert.hpp" + +namespace libtorrent { + +namespace aux { + + template + struct compatible_type + { + // conversions that are OK + // int -> int + // int const -> int const + // int -> int const + // int -> int volatile + // int -> int const volatile + // int volatile -> int const volatile + // int const -> int const volatile + + static const bool value = std::is_same::value + || std::is_same::type>::value + || std::is_same::type>::value + || std::is_same::type>::value + ; + }; +} + + template + struct span + { + using difference_type = std::ptrdiff_t; + using index_type = std::ptrdiff_t; + + span() noexcept : m_ptr(nullptr), m_len(0) {} + + template ::value>::type> + span(span const& v) noexcept // NOLINT + : m_ptr(v.data()), m_len(v.size()) {} + + span(T& p) noexcept : m_ptr(&p), m_len(1) {} // NOLINT + span(T* p, difference_type const l) noexcept : m_ptr(p), m_len(l) // NOLINT + { TORRENT_ASSERT(l >= 0); } + + template + span(std::array& arr) noexcept // NOLINT + : m_ptr(arr.data()), m_len(static_cast(arr.size())) {} + + // this is necessary until C++17, where data() returns a non-const pointer + template + span(std::basic_string& str) noexcept // NOLINT + : m_ptr(&str[0]), m_len(static_cast(str.size())) {} + + template + span(U (&arr)[N]) noexcept // NOLINT + : m_ptr(&arr[0]), m_len(N) {} + + // anything with a .data() member function is considered a container + // but only if the value type is compatible with T + template ().data())>::type + , typename = typename std::enable_if::value>::type> + span(Cont& c) // NOLINT + : m_ptr(c.data()), m_len(static_cast(c.size())) {} + + // allow construction from const containers if T is const + // this allows const spans to be constructed from a temporary container + template ().data())>::type + , typename = typename std::enable_if::value + && std::is_const::value>::type> + span(Cont const& c) // NOLINT + : m_ptr(c.data()), m_len(static_cast(c.size())) {} + + template ::value>::type> + span& operator=(span const& rhs) noexcept + { + m_ptr = rhs.data(); + m_len = rhs.size(); + return *this; + } + + index_type size() const noexcept { return m_len; } + bool empty() const noexcept { return m_len == 0; } + T* data() const noexcept { return m_ptr; } + + using const_iterator = T const*; + using const_reverse_iterator = std::reverse_iterator; + using iterator = T*; + using reverse_iterator = std::reverse_iterator; + + T* begin() const noexcept { return m_ptr; } + T* end() const noexcept { return m_ptr + m_len; } + reverse_iterator rbegin() const noexcept { return reverse_iterator(end()); } + reverse_iterator rend() const noexcept { return reverse_iterator(begin()); } + + T& front() const noexcept { TORRENT_ASSERT(m_len > 0); return m_ptr[0]; } + T& back() const noexcept { TORRENT_ASSERT(m_len > 0); return m_ptr[m_len - 1]; } + + span first(difference_type const n) const + { + TORRENT_ASSERT(size() >= n); + return { data(), n }; + } + + span last(difference_type const n) const + { + TORRENT_ASSERT(size() >= n); + return { data() + size() - n, n }; + } + + span subspan(index_type const offset) const + { + TORRENT_ASSERT(size() >= offset); + return { data() + offset, size() - offset }; + } + + span subspan(index_type const offset, difference_type const count) const + { + TORRENT_ASSERT(count >= 0); + TORRENT_ASSERT(size() >= offset); + TORRENT_ASSERT(size() >= offset + count); + return { data() + offset, count }; + } + + T& operator[](index_type const idx) const + { + TORRENT_ASSERT(idx < m_len); + TORRENT_ASSERT(idx >= 0); + return m_ptr[idx]; + } + + private: + T* m_ptr; + difference_type m_len; + }; + + template + inline bool operator==(span const& lhs, span const& rhs) + { + return lhs.size() == rhs.size() + && (lhs.data() == rhs.data() || std::equal(lhs.begin(), lhs.end(), rhs.begin())); + } + + template + inline bool operator!=(span const& lhs, span const& rhs) + { + return lhs.size() != rhs.size() + || (lhs.data() != rhs.data() && !std::equal(lhs.begin(), lhs.end(), rhs.begin())); + } +} + +#endif // TORRENT_SPAN_HPP_INCLUDED diff --git a/docs/include/libtorrent/ssl.hpp b/docs/include/libtorrent/ssl.hpp new file mode 100644 index 0000000..044ee8c --- /dev/null +++ b/docs/include/libtorrent/ssl.hpp @@ -0,0 +1,186 @@ +/* + +Copyright (c) 2020, Paul-Louis Ageneau +Copyright (c) 2018, Alexandre Janniaux +Copyright (c) 2020, Arvid Norberg +Copyright (c) 2020, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SSL_HPP_INCLUDED +#define TORRENT_SSL_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/aux_/export.hpp" + +#if TORRENT_USE_SSL + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#ifndef BOOST_NO_EXCEPTIONS +#include +#endif + +#ifdef TORRENT_USE_OPENSSL +#include // for OPENSSL_VERSION_NUMBER +#if OPENSSL_VERSION_NUMBER < 0x1000000fL +#error OpenSSL too old, use a recent version with SNI support +#endif +#ifdef TORRENT_WINDOWS +// because openssl includes winsock.h, we must include winsock2.h first +#include +#endif +#include +#include +#include +#include +#endif + +#ifdef TORRENT_USE_GNUTLS +#include +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#ifdef TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#endif + +#include +#include +#include +#include + +namespace libtorrent { +namespace ssl { + +using error_code = boost::system::error_code; + +#if defined TORRENT_USE_OPENSSL +#if defined TORRENT_BUILD_SIMULATOR + using sim::asio::ssl::context; + using sim::asio::ssl::stream_base; + using sim::asio::ssl::stream; +#else + using boost::asio::ssl::context; + using boost::asio::ssl::stream_base; + using boost::asio::ssl::stream; +#endif +using boost::asio::ssl::verify_context; +#if BOOST_VERSION >= 107300 +using boost::asio::ssl::host_name_verification; +#else +using host_name_verification = boost::asio::ssl::rfc2818_verification; +#endif + +using native_context_type = SSL_CTX*; +using native_stream_type = SSL*; +using context_handle_type = native_context_type; +using stream_handle_type = native_stream_type; + +typedef int (*server_name_callback_type)(SSL* s, int*, void* arg); + +#elif defined TORRENT_USE_GNUTLS +using boost::asio::gnutls::context; +using boost::asio::gnutls::stream_base; +using boost::asio::gnutls::stream; +using boost::asio::gnutls::verify_context; +using boost::asio::gnutls::host_name_verification; + +using native_context_type = context::native_handle_type; +using native_stream_type = stream_base::native_handle_type; +using context_handle_type = context*; +using stream_handle_type = stream_base*; + +typedef bool (*server_name_callback_type)(stream_handle_type handle, std::string const& name, void* arg); + +#endif + +namespace error { + +#if defined TORRENT_USE_OPENSSL +using boost::asio::error::get_ssl_category; +using boost::asio::ssl::error::get_stream_category; +#elif defined TORRENT_USE_GNUTLS +using boost::asio::gnutls::error::get_ssl_category; +using boost::asio::gnutls::error::get_stream_category; +#endif + +} + +inline context_handle_type get_handle(context &c) +{ +#if defined TORRENT_USE_OPENSSL + return c.native_handle(); +#elif defined TORRENT_USE_GNUTLS + return &c; +#endif +} + +template +stream_handle_type get_handle(stream& s) +{ +#if defined TORRENT_USE_OPENSSL + return s.native_handle(); +#elif defined TORRENT_USE_GNUTLS + return &s; +#endif +} + +template +context_handle_type get_context_handle(stream& s) +{ +#if defined TORRENT_USE_OPENSSL + return SSL_get_SSL_CTX(s.native_handle()); +#elif defined TORRENT_USE_GNUTLS + return &s.get_context(); +#endif +} + +TORRENT_EXTRA_EXPORT void set_trust_certificate(native_context_type nc, string_view pem, error_code &ec); + +TORRENT_EXTRA_EXPORT void set_server_name_callback(context_handle_type c, server_name_callback_type cb, void* arg, error_code& ec); +TORRENT_EXTRA_EXPORT void set_host_name(stream_handle_type s, std::string const& name, error_code& ec); + +TORRENT_EXTRA_EXPORT void set_context(stream_handle_type s, context_handle_type c); +TORRENT_EXTRA_EXPORT bool has_context(stream_handle_type s, context_handle_type c); +TORRENT_EXTRA_EXPORT context_handle_type get_context(stream_handle_type s); + +} // ssl +} // libtorrent + +#endif // TORRENT_USE_SSL + +#endif diff --git a/docs/include/libtorrent/ssl_stream.hpp b/docs/include/libtorrent/ssl_stream.hpp new file mode 100644 index 0000000..4e7fadd --- /dev/null +++ b/docs/include/libtorrent/ssl_stream.hpp @@ -0,0 +1,355 @@ +/* + +Copyright (c) 2008, 2010-2020, Arvid Norberg +Copyright (c) 2018, Alden Torres +Copyright (c) 2018, Alexandre Janniaux +Copyright (c) 2020, Paul-Louis Ageneau +Copyright (c) 2021, YenForYang +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SSL_STREAM_HPP_INCLUDED +#define TORRENT_SSL_STREAM_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_SSL + +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/ssl.hpp" + +#include + +#include + +namespace libtorrent { + +template +struct ssl_stream +{ + ssl_stream(io_context& io_context, ssl::context& ctx) + : m_sock(new ssl::stream(io_context, ctx)) + {} + + template + ssl_stream(S&& s, ssl::context& ctx) + : m_sock(new ssl::stream(std::forward(s), ctx)) + {} + + ssl_stream(ssl_stream&&) = default; + + using sock_type = typename ssl::stream; + using next_layer_type = typename sock_type::next_layer_type; + using lowest_layer_type = typename Stream::lowest_layer_type; + using endpoint_type = typename Stream::endpoint_type; + using protocol_type = typename Stream::protocol_type; +#if BOOST_VERSION >= 106600 + using executor_type = typename sock_type::executor_type; + executor_type get_executor() { return m_sock->get_executor(); } +#endif + + ssl::stream_handle_type handle() + { + return ssl::get_handle(*m_sock); + } + + ssl::context_handle_type context_handle() + { + return ssl::get_context_handle(*m_sock); + } + + void set_host_name(std::string const& name) + { + error_code ec; + set_host_name(name, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } + + void set_host_name(std::string const& name, error_code& ec) + { + ssl::set_host_name(handle(), name, ec); + } + + template + void set_verify_callback(T const& fun, error_code& ec) + { + m_sock->set_verify_callback(fun, ec); + } + + template + void async_connect(endpoint_type const& endpoint, Handler const& h) + { + // the connect is split up in the following steps: + // 1. connect to peer + // 2. perform SSL client handshake + + m_sock->next_layer().async_connect(endpoint, wrap_allocator( + [this](error_code const& ec, Handler hn) { + connected(ec, std::move(hn)); + }, std::move(h))); + } + + template + void async_accept_handshake(Handler const& h) + { + // this is used for accepting SSL connections + m_sock->async_handshake(ssl::stream_base::server, wrap_allocator( + [this](error_code const& ec, Handler hn) { + handshake(ec, std::move(hn)); + }, std::move(h))); + } + + void accept_handshake(error_code& ec) + { + // this is used for accepting SSL connections + m_sock->handshake(ssl::stream_base::server, ec); + } + + template + void async_shutdown(Handler handler) + { + error_code ec; + m_sock->next_layer().cancel(ec); + m_sock->async_shutdown(std::move(handler)); + } + + void shutdown(error_code& ec) + { + m_sock->shutdown(ec); + } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler handler) + { + m_sock->async_read_some(buffers, std::move(handler)); + } + + template + std::size_t read_some(Mutable_Buffers const& buffers, error_code& ec) + { + return m_sock->read_some(buffers, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void set_option(SettableSocketOption const& opt) + { + m_sock->next_layer().set_option(opt); + } +#endif + + template + void set_option(SettableSocketOption const& opt, error_code& ec) + { + m_sock->next_layer().set_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void get_option(GettableSocketOption& opt) + { + m_sock->next_layer().get_option(opt); + } +#endif + + template + void get_option(GettableSocketOption& opt, error_code& ec) + { + m_sock->next_layer().get_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + std::size_t read_some(Mutable_Buffers const& buffers) + { + return m_sock->read_some(buffers); + } + + template + void io_control(IO_Control_Command& ioc) + { + m_sock->next_layer().io_control(ioc); + } +#endif + + template + void io_control(IO_Control_Command& ioc, error_code& ec) + { + m_sock->next_layer().io_control(ioc, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void non_blocking(bool b) { m_sock->next_layer().non_blocking(b); } +#endif + + void non_blocking(bool b, error_code& ec) + { m_sock->next_layer().non_blocking(b, ec); } + + template + void async_write_some(Const_Buffers const& buffers, Handler handler) + { + m_sock->async_write_some(buffers, std::move(handler)); + } + + template + std::size_t write_some(Const_Buffers const& buffers, error_code& ec) + { + return m_sock->write_some(buffers, ec); + } + + // the SSL stream may cache 17 kiB internally, and there's no way of + // asking how large its buffer is. 17 kiB isn't very much though, so it + // seems fine to potentially over-estimate the number of bytes available. +#ifndef BOOST_NO_EXCEPTIONS + std::size_t available() const + { return 17 * 1024 + const_cast(*m_sock).next_layer().available(); } +#endif + + std::size_t available(error_code& ec) const + { return 17 * 1024 + const_cast(*m_sock).next_layer().available(ec); } + +#ifndef BOOST_NO_EXCEPTIONS + void bind(endpoint_type const& endpoint) + { + m_sock->next_layer().bind(endpoint); + } +#endif + + void bind(endpoint_type const& endpoint, error_code& ec) + { + m_sock->next_layer().bind(endpoint, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void open(protocol_type const& p) + { + m_sock->next_layer().open(p); + } +#endif + + void open(protocol_type const& p, error_code& ec) + { + m_sock->next_layer().open(p, ec); + } + + bool is_open() const + { + return m_sock->next_layer().is_open(); + } + +#ifndef BOOST_NO_EXCEPTIONS + void close() + { + m_sock->next_layer().close(); + } +#endif + + void close(error_code& ec) + { + m_sock->next_layer().close(ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type remote_endpoint() const + { + return m_sock->next_layer().remote_endpoint(); + } +#endif + + endpoint_type remote_endpoint(error_code& ec) const + { + return m_sock->next_layer().remote_endpoint(ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type local_endpoint() const + { + return m_sock->next_layer().local_endpoint(); + } +#endif + + endpoint_type local_endpoint(error_code& ec) const + { + return m_sock->next_layer().local_endpoint(ec); + } + + lowest_layer_type& lowest_layer() + { + return m_sock->lowest_layer(); + } + + lowest_layer_type const& lowest_layer() const + { + return m_sock->lowest_layer(); + } + + next_layer_type& next_layer() + { + return m_sock->next_layer(); + } + + next_layer_type const& next_layer() const + { + return m_sock->next_layer(); + } + +private: + + template + void connected(error_code const& e, Handler h) + { + if (e) + { + h(e); + return; + } + + m_sock->async_handshake(ssl::stream_base::client, wrap_allocator( + [this](error_code const& ec, Handler hn) { + handshake(ec, std::move(hn)); + }, std::move(h))); + } + + template + void handshake(error_code const& e, Handler h) + { + h(e); + } + + // to make us movable + std::unique_ptr> m_sock; +}; + +} + +#endif // TORRENT_USE_SSL + +#endif diff --git a/docs/include/libtorrent/stack_allocator.hpp b/docs/include/libtorrent/stack_allocator.hpp new file mode 100644 index 0000000..ec94f46 --- /dev/null +++ b/docs/include/libtorrent/stack_allocator.hpp @@ -0,0 +1,96 @@ +/* + +Copyright (c) 2012, 2015-2019, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STACK_ALLOCATOR +#define TORRENT_STACK_ALLOCATOR + +#include "libtorrent/assert.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" + +#include // for va_list +#include // for vsnprintf +#include + +namespace libtorrent { +namespace aux { + + struct allocation_slot + { + allocation_slot() noexcept : m_idx(-1) {} + allocation_slot(allocation_slot const&) noexcept = default; + allocation_slot(allocation_slot&&) noexcept = default; + allocation_slot& operator=(allocation_slot const&) & = default; + allocation_slot& operator=(allocation_slot&&) & noexcept = default; + bool operator==(allocation_slot const& s) const { return m_idx == s.m_idx; } + bool operator!=(allocation_slot const& s) const { return m_idx != s.m_idx; } + friend struct stack_allocator; + private: + explicit allocation_slot(int idx) noexcept : m_idx(idx) {} + int val() const { return m_idx; } + int m_idx; + }; + + struct TORRENT_EXTRA_EXPORT stack_allocator + { + stack_allocator() {} + + // non-copyable + stack_allocator(stack_allocator const&) = delete; + stack_allocator& operator=(stack_allocator const&) = delete; + stack_allocator(stack_allocator&&) = default; + stack_allocator& operator=(stack_allocator&&) & = default; + + allocation_slot copy_string(string_view str); + allocation_slot copy_string(char const* str); + + allocation_slot format_string(char const* fmt, va_list v); + + allocation_slot copy_buffer(span buf); + allocation_slot allocate(int bytes); + char* ptr(allocation_slot idx); + char const* ptr(allocation_slot idx) const; + void swap(stack_allocator& rhs); + void reset(); + + private: + + vector m_storage; + }; + +} +} + +#endif diff --git a/docs/include/libtorrent/stat.hpp b/docs/include/libtorrent/stat.hpp new file mode 100644 index 0000000..5bdd1f3 --- /dev/null +++ b/docs/include/libtorrent/stat.hpp @@ -0,0 +1,287 @@ +/* + +Copyright (c) 2003-2005, 2007-2012, 2014-2017, 2019, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STAT_HPP_INCLUDED +#define TORRENT_STAT_HPP_INCLUDED + +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent { + + class TORRENT_EXTRA_EXPORT stat_channel + { + public: + + stat_channel() + : m_total_counter(0) + , m_counter(0) + , m_5_sec_average(0) + {} + + void operator+=(stat_channel const& s) + { + TORRENT_ASSERT(m_counter < (std::numeric_limits::max)() - s.m_counter); + m_counter += s.m_counter; + TORRENT_ASSERT(m_total_counter < (std::numeric_limits::max)() - s.m_counter); + m_total_counter += s.m_counter; + } + + void add(int count) + { + TORRENT_ASSERT(count >= 0); + + TORRENT_ASSERT(m_counter < (std::numeric_limits::max)() - count); + m_counter += count; + TORRENT_ASSERT(m_total_counter < (std::numeric_limits::max)() - count); + m_total_counter += count; + } + + // should be called once every second + void second_tick(int tick_interval_ms); + std::int32_t rate() const { return m_5_sec_average; } + std::int32_t low_pass_rate() const { return m_5_sec_average; } + + std::int64_t total() const { return m_total_counter; } + + void offset(std::int64_t c) + { + TORRENT_ASSERT(m_total_counter < (std::numeric_limits::max)() - c); + m_total_counter += c; + } + + std::int32_t counter() const { return m_counter; } + + void clear() + { + m_counter = 0; + m_5_sec_average = 0; + m_total_counter = 0; + } + + private: + + // total counters + std::int64_t m_total_counter; + + // the accumulator for this second. + std::int32_t m_counter; + + // sliding average + std::int32_t m_5_sec_average; + }; + + class TORRENT_EXTRA_EXPORT stat + { + public: + void operator+=(const stat& s) + { + for (int i = 0; i < num_channels; ++i) + m_stat[i] += s.m_stat[i]; + } + + void sent_syn(bool ipv6) + { + m_stat[upload_ip_protocol].add(ipv6 ? 60 : 40); + } + + void received_synack(bool ipv6) + { + // we received SYN-ACK and also sent ACK back + m_stat[download_ip_protocol].add(ipv6 ? 60 : 40); + m_stat[upload_ip_protocol].add(ipv6 ? 60 : 40); + } + + void received_bytes(int bytes_payload, int bytes_protocol) + { + TORRENT_ASSERT(bytes_payload >= 0); + TORRENT_ASSERT(bytes_protocol >= 0); + + m_stat[download_payload].add(bytes_payload); + m_stat[download_protocol].add(bytes_protocol); + } + + void sent_bytes(int bytes_payload, int bytes_protocol) + { + TORRENT_ASSERT(bytes_payload >= 0); + TORRENT_ASSERT(bytes_protocol >= 0); + + m_stat[upload_payload].add(bytes_payload); + m_stat[upload_protocol].add(bytes_protocol); + } + + // and IP packet was received or sent + // account for the overhead caused by it + void trancieve_ip_packet(int bytes_transferred, bool ipv6) + { + // one TCP/IP packet header for the packet + // sent or received, and one for the ACK + // The IPv4 header is 20 bytes + // and IPv6 header is 40 bytes + const int header = (ipv6 ? 40 : 20) + 20; + const int mtu = 1500; + const int packet_size = mtu - header; + const int overhead = (std::max)(1, (bytes_transferred + packet_size - 1) / packet_size) * header; + m_stat[download_ip_protocol].add(overhead); + m_stat[upload_ip_protocol].add(overhead); + } + + int upload_ip_overhead() const { return m_stat[upload_ip_protocol].counter(); } + int download_ip_overhead() const { return m_stat[download_ip_protocol].counter(); } + + // should be called once every second + void second_tick(int tick_interval_ms) + { + for (int i = 0; i < num_channels; ++i) + m_stat[i].second_tick(tick_interval_ms); + } + + int low_pass_upload_rate() const + { + return m_stat[upload_payload].low_pass_rate() + + m_stat[upload_protocol].low_pass_rate() + + m_stat[upload_ip_protocol].low_pass_rate(); + } + + int low_pass_download_rate() const + { + return m_stat[download_payload].low_pass_rate() + + m_stat[download_protocol].low_pass_rate() + + m_stat[download_ip_protocol].low_pass_rate(); + } + + int upload_rate() const + { + return m_stat[upload_payload].rate() + + m_stat[upload_protocol].rate() + + m_stat[upload_ip_protocol].rate(); + } + + int download_rate() const + { + return m_stat[download_payload].rate() + + m_stat[download_protocol].rate() + + m_stat[download_ip_protocol].rate(); + } + + std::int64_t total_upload() const + { + return m_stat[upload_payload].total() + + m_stat[upload_protocol].total() + + m_stat[upload_ip_protocol].total(); + } + + std::int64_t total_download() const + { + return m_stat[download_payload].total() + + m_stat[download_protocol].total() + + m_stat[download_ip_protocol].total(); + } + + int upload_payload_rate() const + { return m_stat[upload_payload].rate(); } + int download_payload_rate() const + { return m_stat[download_payload].rate(); } + + std::int64_t total_payload_upload() const + { return m_stat[upload_payload].total(); } + std::int64_t total_payload_download() const + { return m_stat[download_payload].total(); } + + std::int64_t total_protocol_upload() const + { return m_stat[upload_protocol].total(); } + std::int64_t total_protocol_download() const + { return m_stat[download_protocol].total(); } + + std::int64_t total_transfer(int channel) const + { return m_stat[channel].total(); } + int transfer_rate(int channel) const + { return m_stat[channel].rate(); } + + // this is used to offset the statistics when a + // peer_connection is opened and have some previous + // transfers from earlier connections. + void add_stat(std::int64_t downloaded, std::int64_t uploaded) + { + m_stat[download_payload].offset(downloaded); + m_stat[upload_payload].offset(uploaded); + } + + int last_payload_downloaded() const + { return m_stat[download_payload].counter(); } + int last_payload_uploaded() const + { return m_stat[upload_payload].counter(); } + int last_protocol_downloaded() const + { return m_stat[download_protocol].counter(); } + int last_protocol_uploaded() const + { return m_stat[upload_protocol].counter(); } + + // these are the channels we keep stats for + enum + { + // TODO: 3 everything but payload counters and rates could probably be + // removed from here + upload_payload, + upload_protocol, + download_payload, + download_protocol, + upload_ip_protocol, + download_ip_protocol, + num_channels + }; + + void clear() + { + for (int i = 0; i < num_channels; ++i) + m_stat[i].clear(); + } + + stat_channel const& operator[](int i) const + { + TORRENT_ASSERT(i >= 0 && i < num_channels); + return m_stat[i]; + } + + private: + + stat_channel m_stat[num_channels]; + }; + +} + +#endif // TORRENT_STAT_HPP_INCLUDED diff --git a/docs/include/libtorrent/stat_cache.hpp b/docs/include/libtorrent/stat_cache.hpp new file mode 100644 index 0000000..c24e7ce --- /dev/null +++ b/docs/include/libtorrent/stat_cache.hpp @@ -0,0 +1,108 @@ +/* + +Copyright (c) 2010, 2013-2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STAT_CACHE_HPP +#define TORRENT_STAT_CACHE_HPP + +#include +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/vector.hpp" + +namespace libtorrent { + + struct TORRENT_EXTRA_EXPORT stat_cache + { + stat_cache(); + ~stat_cache(); + + void reserve(int num_files); + + // returns the size of the file unless an error occurs, in which case ec + // is set to indicate the error + std::int64_t get_filesize(file_index_t i, file_storage const& fs + , std::string const& save_path, error_code& ec); + + void set_dirty(file_index_t i); + + void clear(); + + // internal + enum + { + not_in_cache = -1, + file_error = -2 // (first index in m_errors) + }; + + // internal + void set_cache(file_index_t i, std::int64_t size); + void set_error(file_index_t i, error_code const& ec); + + private: + + void set_cache_impl(file_index_t i, std::int64_t size); + void set_error_impl(file_index_t i, error_code const& ec); + + // returns the index to the specified error. Either an existing one or a + // newly added entry + int add_error(error_code const& ec); + + struct stat_cache_t + { + explicit stat_cache_t(std::int64_t s): file_size(s) {} + + // the size of the file. Negative values have special meaning. -1 means + // not-in-cache (i.e. there's no data for this file in the cache). + // lower values (larger negative values) indicate that an error + // occurred while stat()ing the file. The positive value is an index + // into m_errors, that recorded the actual error. + std::int64_t file_size; + }; + + mutable std::mutex m_mutex; + + // one entry per file + aux::vector m_stat_cache; + + // These are the errors that have happened when stating files. Each entry + // that had an error, refers to an index into this vector. + std::vector m_errors; + }; +} + +#endif // TORRENT_STAT_CACHE_HPP diff --git a/docs/include/libtorrent/storage.hpp b/docs/include/libtorrent/storage.hpp new file mode 100644 index 0000000..fd593aa --- /dev/null +++ b/docs/include/libtorrent/storage.hpp @@ -0,0 +1,7 @@ + +#ifndef TORRENT_STORAGE_HPP_INCLUDED +#define TORRENT_STORAGE_HPP_INCLUDED + +#error "the disk I/O subsystem has been overhauled in libtorrent 2.0. storage_interface is no longer a customization point, customize disk_interface instead" + +#endif diff --git a/docs/include/libtorrent/storage_defs.hpp b/docs/include/libtorrent/storage_defs.hpp new file mode 100644 index 0000000..45a090f --- /dev/null +++ b/docs/include/libtorrent/storage_defs.hpp @@ -0,0 +1,159 @@ +/* + +Copyright (c) 2023, Vladimir Golovnev +Copyright (c) 2006-2007, 2009, 2013-2014, 2016-2020, 2022, Arvid Norberg +Copyright (c) 2016, 2021, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STORAGE_DEFS_HPP_INCLUDE +#define TORRENT_STORAGE_DEFS_HPP_INCLUDE + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/download_priority.hpp" +#include +#include + +namespace libtorrent { + + using storage_index_t = aux::strong_typedef; + + // types of storage allocation used for add_torrent_params::storage_mode. + enum storage_mode_t + { + // All pieces will be written to their final position, all files will be + // allocated in full when the torrent is first started. This mode minimizes + // fragmentation but could be a costly operation. + storage_mode_allocate, + + // All pieces will be written to the place where they belong and sparse files + // will be used. This is the recommended, and default mode. + storage_mode_sparse + }; + + // return values from check_fastresume, and move_storage + enum class status_t : std::uint8_t + { + no_error, + fatal_disk_error, + need_full_check, + file_exist, + + // hidden + mask = 0xf, + + // this is not an enum value, but a flag that can be set in the return + // from async_check_files, in case an existing file was found larger than + // specified in the torrent. i.e. it has garbage at the end + // the status_t field is used for this to preserve ABI. + oversized_file = 0x10, + }; + + // internal + inline status_t operator|(status_t lhs, status_t rhs) + { + return status_t(static_cast(lhs) | static_cast(rhs)); + } + inline status_t operator&(status_t lhs, status_t rhs) + { + return status_t(static_cast(lhs) & static_cast(rhs)); + } + inline status_t operator~(status_t lhs) + { + return status_t(~static_cast(lhs)); + } + + // flags for async_move_storage + enum class move_flags_t : std::uint8_t + { + // replace any files in the destination when copying + // or moving the storage + always_replace_files, + + // if any files that we want to copy exist in the destination + // exist, fail the whole operation and don't perform + // any copy or move. There is an inherent race condition + // in this mode. The files are checked for existence before + // the operation starts. In between the check and performing + // the copy, the destination files may be created, in which + // case they are replaced. + fail_if_exist, + + // if any file exist in the target, take those files instead + // of the ones we may have in the source. + dont_replace, + + // don't move any source files, just forget about them + // and begin checking files at new save path + reset_save_path, + + // don't move any source files, just change save path + // and continue working without any checks + reset_save_path_unchecked + }; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + enum deprecated_move_flags_t + { + always_replace_files TORRENT_DEPRECATED_ENUM, + fail_if_exist TORRENT_DEPRECATED_ENUM, + dont_replace TORRENT_DEPRECATED_ENUM + }; +#endif + + // a parameter pack used to construct the storage for a torrent, used in + // disk_interface + struct TORRENT_EXPORT storage_params + { + storage_params(file_storage const& f, file_storage const* mf + , std::string const& sp, storage_mode_t const sm + , aux::vector const& prio + , sha1_hash const& ih) + : files(f) + , mapped_files(mf) + , path(sp) + , mode(sm) + , priorities(prio) + , info_hash(ih) + {} + file_storage const& files; + file_storage const* mapped_files = nullptr; // optional + std::string const& path; + storage_mode_t mode{storage_mode_sparse}; + aux::vector const& priorities; + sha1_hash info_hash; + }; +} + +#endif diff --git a/docs/include/libtorrent/string_util.hpp b/docs/include/libtorrent/string_util.hpp new file mode 100644 index 0000000..0a1765f --- /dev/null +++ b/docs/include/libtorrent/string_util.hpp @@ -0,0 +1,153 @@ +/* + +Copyright (c) 2012, 2014-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2016, Steven Siloti +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STRING_UTIL_HPP_INCLUDED +#define TORRENT_STRING_UTIL_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/error_code.hpp" + +#include +#include +#include +#include +#include // for std::array + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT bool is_alpha(char c); + + TORRENT_EXTRA_EXPORT + std::array::digits10> + to_string(std::int64_t n); + + // internal + inline bool is_digit(char c) + { return c >= '0' && c <= '9'; } + inline void ensure_trailing_slash(std::string& url) + { + if (url.empty() || url[url.size() - 1] != '/') + url += '/'; + } + + // internal + TORRENT_EXTRA_EXPORT string_view strip_string(string_view in); + + TORRENT_EXTRA_EXPORT bool is_print(char c); + TORRENT_EXTRA_EXPORT bool is_space(char c); + TORRENT_EXTRA_EXPORT char to_lower(char c); + + TORRENT_EXTRA_EXPORT bool string_begins_no_case(char const* s1, char const* s2); + TORRENT_EXTRA_EXPORT bool string_equal_no_case(string_view s1, string_view s2); + + TORRENT_EXTRA_EXPORT void url_random(span dest); + + TORRENT_EXTRA_EXPORT bool string_ends_with(string_view s1, string_view s2); + + // Returns offset at which src matches target. + // If no sync found, return -1 + TORRENT_EXTRA_EXPORT int search(span src, span target); + + struct listen_interface_t + { + std::string device; + int port; + bool ssl; + bool local; + friend bool operator==(listen_interface_t const& lhs, listen_interface_t const& rhs) + { + return lhs.device == rhs.device + && lhs.port == rhs.port + && lhs.ssl == rhs.ssl + && lhs.local == rhs.local; + } + }; + + // this parses the string that's used as the listen_interfaces setting. + // it is a comma-separated list of IP or device names with ports. For + // example: "eth0:6881,eth1:6881" or "127.0.0.1:6881" + TORRENT_EXTRA_EXPORT std::vector parse_listen_interfaces( + std::string const& in, std::vector& errors); + +#if TORRENT_ABI_VERSION == 1 \ + || !defined TORRENT_DISABLE_LOGGING + TORRENT_EXTRA_EXPORT std::string print_listen_interfaces( + std::vector const& in); +#endif + + // this parses the string that's used as the listen_interfaces setting. + // it is a comma-separated list of IP or device names with ports. For + // example: "eth0:6881,eth1:6881" or "127.0.0.1:6881" + TORRENT_EXTRA_EXPORT void parse_comma_separated_string_port( + std::string const& in, std::vector>& out); + + // this parses the string that's used as the outgoing_interfaces setting. + // it is a comma separated list of IPs and device names. For example: + // "eth0, eth1, 127.0.0.1" + TORRENT_EXTRA_EXPORT void parse_comma_separated_string( + std::string const& in, std::vector& out); + + // strdup is not part of the C standard. Some systems + // don't have it and it won't be available when building + // in strict ANSI mode + TORRENT_EXTRA_EXPORT char* allocate_string_copy(string_view str); + + // searches for separator ('sep') in the string 'last'. + // if found, returns the string_view representing the range from the start of + // `last` up to (but not including) the separator. The second return value is + // the remainder of the string, starting one character after the separator. + // if no separator is found, the whole string is returned and the second + // return value is an empty string_view. + TORRENT_EXTRA_EXPORT std::pair split_string(string_view last, char sep); + + // same as split_string, but if one sub-string starts with a double quote + // (") separators are ignored until the end double-quote. Unless if the + // separator itself is a double quote. + TORRENT_EXTRA_EXPORT std::pair split_string_quotes( + string_view last, char const sep); + + // removes whitespaces at the beginning of the string, in-place + TORRENT_EXTRA_EXPORT void ltrim(std::string& s); + +#if TORRENT_USE_I2P + + TORRENT_EXTRA_EXPORT bool is_i2p_url(std::string const& url); + +#endif +} + +#endif diff --git a/docs/include/libtorrent/string_view.hpp b/docs/include/libtorrent/string_view.hpp new file mode 100644 index 0000000..5460134 --- /dev/null +++ b/docs/include/libtorrent/string_view.hpp @@ -0,0 +1,109 @@ +/* + +Copyright (c) 2016-2017, 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STRING_VIEW_HPP_INCLUDED +#define TORRENT_STRING_VIEW_HPP_INCLUDED + +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +// TODO: replace this by the standard string_view in C++17 + +#if BOOST_VERSION < 106100 +#include +#include // for strchr +namespace libtorrent { + +using string_view = boost::string_ref; +using wstring_view = boost::wstring_ref; + +// internal +inline string_view::size_type find_first_of(string_view const v, char const c + , string_view::size_type pos) +{ + while (pos < v.size()) + { + if (v[pos] == c) return pos; + ++pos; + } + return string_view::npos; +} + +// internal +inline string_view::size_type find_first_of(string_view const v, char const* c + , string_view::size_type pos) +{ + while (pos < v.size()) + { + if (std::strchr(c, v[pos]) != nullptr) return pos; + ++pos; + } + return string_view::npos; +} +} +#else +#include +namespace libtorrent { + +using string_view = boost::string_view; +using wstring_view = boost::wstring_view; + +// internal +inline string_view::size_type find_first_of(string_view const v, char const c + , string_view::size_type pos) +{ + return v.find_first_of(c, pos); +} + +// internal +inline string_view::size_type find_first_of(string_view const v, char const* c + , string_view::size_type pos) +{ + return v.find_first_of(c, pos); +} +} +#endif + +namespace libtorrent { + +inline namespace literals { + + constexpr string_view operator "" _sv(char const* str, std::size_t len) + { return string_view(str, len); } +} +} + + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif diff --git a/docs/include/libtorrent/tailqueue.hpp b/docs/include/libtorrent/tailqueue.hpp new file mode 100644 index 0000000..39e4cd2 --- /dev/null +++ b/docs/include/libtorrent/tailqueue.hpp @@ -0,0 +1,214 @@ +/* + +Copyright (c) 2010, 2013-2017, 2019, 2022, Arvid Norberg +Copyright (c) 2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TAILQUEUE_HPP +#define TORRENT_TAILQUEUE_HPP + +#include "libtorrent/assert.hpp" +#include // for std::move + +namespace libtorrent { + + template + struct tailqueue_node + { + tailqueue_node() : next(nullptr) {} + T* next; + }; + + template + inline N* postinc(N*& e) + { + N* ret = e; + e = static_cast(ret->next); + return ret; + } + + template + struct tailqueue_iterator + { + template friend struct tailqueue; + + T* get() const { return m_current; } + void next() { m_current = m_current->next; } + + private: + explicit tailqueue_iterator(T* cur) + : m_current(cur) {} + // the current element + T* m_current; + }; + + template + struct tailqueue + { + tailqueue(): m_first(nullptr), m_last(nullptr), m_size(0) {} + + tailqueue(tailqueue const&) = delete; + tailqueue(tailqueue&& t): m_first(t.m_first), m_last(t.m_last), m_size(t.m_size) + { + t.m_first = nullptr; + t.m_last = nullptr; + t.m_size = 0; + } + + tailqueue& operator=(tailqueue const&) = delete; + tailqueue& operator=(tailqueue&& t) + { + TORRENT_ASSERT(m_first == nullptr); + TORRENT_ASSERT(m_last == nullptr); + TORRENT_ASSERT(m_size == 0); + + m_first = t.m_first; + m_last = t.m_last; + m_size = t.m_size; + + t.m_first = nullptr; + t.m_last = nullptr; + t.m_size = 0; + return *this; + } + + tailqueue_iterator iterate() const + { return tailqueue_iterator(m_first); } + + tailqueue_iterator iterate() + { return tailqueue_iterator(m_first); } + + void append(tailqueue rhs) & + { + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + TORRENT_ASSERT(rhs.m_last == nullptr || rhs.m_last->next == nullptr); + + if (rhs.m_first == nullptr) return; + + if (m_first == nullptr) + { + swap(rhs); + return; + } + + m_last->next = rhs.m_first; + m_last = rhs.m_last; + m_size += rhs.m_size; + + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + } + + void prepend(tailqueue rhs) & + { + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + TORRENT_ASSERT(rhs.m_last == nullptr || rhs.m_last->next == nullptr); + TORRENT_ASSERT((m_last == nullptr) == (m_first == nullptr)); + TORRENT_ASSERT((rhs.m_last == nullptr) == (rhs.m_first == nullptr)); + + if (rhs.m_first == nullptr) return; + + if (m_first == nullptr) + { + swap(rhs); + return; + } + + swap(rhs); + append(std::move(rhs)); + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + TORRENT_ASSERT(rhs.m_last == nullptr || rhs.m_last->next == nullptr); + } + + T* pop_front() + { + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + T* e = m_first; + m_first = m_first->next; + if (e == m_last) m_last = nullptr; + e->next = nullptr; + --m_size; + return e; + } + void push_front(T* e) & + { + TORRENT_ASSERT(e->next == nullptr); + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + e->next = m_first; + m_first = e; + if (!m_last) m_last = e; + ++m_size; + } + void push_back(T* e) & + { + TORRENT_ASSERT(e->next == nullptr); + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + if (m_last) m_last->next = e; + else m_first = e; + m_last = e; + e->next = nullptr; + ++m_size; + } + T* get_all() & + { + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + T* e = m_first; + m_first = nullptr; + m_last = nullptr; + m_size = 0; + return e; + } + void swap(tailqueue& rhs) + { + std::swap(m_first, rhs.m_first); + std::swap(m_last, rhs.m_last); + std::swap(m_size, rhs.m_size); + } + int size() const { TORRENT_ASSERT(m_size >= 0); return m_size; } + bool empty() const { TORRENT_ASSERT(m_size >= 0); return m_size == 0; } + T* first() const& { TORRENT_ASSERT(m_size > 0); return m_first; } + T* last() const& { TORRENT_ASSERT(m_size > 0); return m_last; } + private: + T* m_first; + T* m_last; + int m_size; + }; +} + +namespace std { + +template +void swap(libtorrent::tailqueue& lhs, libtorrent::tailqueue& rhs) +{ + lhs.swap(rhs); +} + +} + +#endif // TAILQUEUE_HPP diff --git a/docs/include/libtorrent/time.hpp b/docs/include/libtorrent/time.hpp new file mode 100644 index 0000000..7cf3880 --- /dev/null +++ b/docs/include/libtorrent/time.hpp @@ -0,0 +1,92 @@ +/* + +Copyright (c) 2007, 2009, 2014-2017, 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TIME_HPP_INCLUDED +#define TORRENT_TIME_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include +#include + +#if defined TORRENT_BUILD_SIMULATOR +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include "simulator/simulator.hpp" +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + +namespace libtorrent { + +#if defined TORRENT_BUILD_SIMULATOR + using clock_type = sim::chrono::high_resolution_clock; +#else + using clock_type = std::chrono::high_resolution_clock; +#endif + + using time_point = clock_type::time_point; + using time_duration = clock_type::duration; + + // 32 bit versions of time_point and duration, with second resolution + using milliseconds32 = std::chrono::duration>; + using seconds32 = std::chrono::duration; + using minutes32 = std::chrono::duration>; + using time_point32 = std::chrono::time_point; + + using seconds = std::chrono::seconds; + using milliseconds = std::chrono::milliseconds; + using microseconds = std::chrono::microseconds; + using minutes = std::chrono::minutes; + using hours = std::chrono::hours; + using std::chrono::duration_cast; + using std::chrono::time_point_cast; + + // internal + inline time_point min_time() { return (time_point::min)(); } + + // internal + inline time_point max_time() { return (time_point::max)(); } + + template + std::int64_t total_seconds(T td) + { return duration_cast(td).count(); } + + template + std::int64_t total_milliseconds(T td) + { return duration_cast(td).count(); } + + template + std::int64_t total_microseconds(T td) + { return duration_cast(td).count(); } + +} + +#endif // TORRENT_TIME_HPP_INCLUDED diff --git a/docs/include/libtorrent/torrent.hpp b/docs/include/libtorrent/torrent.hpp new file mode 100644 index 0000000..226b6a9 --- /dev/null +++ b/docs/include/libtorrent/torrent.hpp @@ -0,0 +1,1814 @@ +/* + +Copyright (c) 2003-2022, Arvid Norberg +Copyright (c) 2003, Daniel Wallin +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016-2020, Alden Torres +Copyright (c) 2017, AllSeeingEyeTolledEweSew +Copyright (c) 2017, Falcosc +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2018-2019, Steven Siloti +Copyright (c) 2018, d-komarov +Copyright (c) 2019, ghbplayer +Copyright (c) 2020, Paul-Louis Ageneau +Copyright (c) 2021, AdvenT +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_HPP_INCLUDE +#define TORRENT_TORRENT_HPP_INCLUDE + +#include +#include +#include +#include +#include +#include // for numeric_limits +#include // for unique_ptr + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/fwd.hpp" +#include "libtorrent/optional.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/peer_list.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/hash_picker.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/bandwidth_limit.hpp" +#include "libtorrent/aux_/bandwidth_queue_entry.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/peer_class_set.hpp" +#include "libtorrent/link.hpp" +#include "libtorrent/vector_utils.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/piece_block.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/aux_/file_progress.hpp" +#include "libtorrent/aux_/suggest_piece.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/deferred_handler.hpp" +#include "libtorrent/aux_/allocating_handler.hpp" +#include "libtorrent/aux_/announce_entry.hpp" +#include "libtorrent/extensions.hpp" // for add_peer_flags_t +#include "libtorrent/ssl.hpp" + +#ifdef TORRENT_SSL_PEERS +// there is no forward declaration header for asio +namespace boost { +namespace asio { +namespace ssl { + class context; + class verify_context; +} +} +} +#endif + +#if TORRENT_COMPLETE_TYPES_REQUIRED +#include "libtorrent/peer_connection.hpp" +#endif + +// define as 0 to disable. 1 enables debug output of the pieces and requested +// blocks. 2 also enables trace output of the time critical piece picking +// logic +#define TORRENT_DEBUG_STREAMING 0 + +namespace libtorrent { + + class http_parser; + struct tracker_request; + struct bt_peer_connection; + + using web_seed_flag_t = flags::bitfield_flag; + + // internal + enum class waste_reason + { + piece_timed_out, piece_cancelled, piece_unknown, piece_seed + , piece_end_game, piece_closing + , max + }; + + TORRENT_EXTRA_EXPORT std::int64_t calc_bytes(file_storage const& fs, piece_count const& pc); + +#ifndef TORRENT_DISABLE_STREAMING + struct time_critical_piece + { + // when this piece was first requested + time_point first_requested; + // when this piece was last requested + time_point last_requested; + // by what time we want this piece + time_point deadline; + // 1 = send alert with piece data when available + deadline_flags_t flags; + // how many peers it's been requested from + int peers; + // the piece index + piece_index_t piece; +#if TORRENT_DEBUG_STREAMING > 0 + // the number of multiple requests are allowed + // to blocks still not downloaded (debugging only) + int timed_out; +#endif + bool operator<(time_critical_piece const& rhs) const + { return deadline < rhs.deadline; } + }; +#endif // TORRENT_DISABLE_STREAMING + + // this is the internal representation of web seeds + struct web_seed_t : web_seed_entry + { + explicit web_seed_t(web_seed_entry const& wse); + web_seed_t(std::string const& url_, web_seed_entry::type_t type_ + , std::string const& auth_ = std::string() + , web_seed_entry::headers_t const& extra_headers_ = web_seed_entry::headers_t()); + + // if this is > now, we can't reconnect yet + time_point32 retry = aux::time_now32(); + + // if the hostname of the web seed has been resolved, + // these are its IP addresses + std::vector endpoints; + + // this is the peer_info field used for the + // connection, just to count hash failures + // it's also used to hold the peer_connection + // pointer, when the web seed is connected + ipv4_peer peer_info{tcp::endpoint(), true, {}}; + + // this is initialized to true, but if we discover the + // server not to support it, it's set to false, and we + // make larger requests. + bool supports_keepalive = true; + + // this indicates whether or not we're resolving the + // hostname of this URL + bool resolving = false; + + // if the user wanted to remove this while + // we were resolving it. In this case, we set + // the removed flag to true, to make the resolver + // callback remove it + bool removed = false; + + // this indicates whether this web seed has any files. A server that only + // redirects to other servers for instance, may not have any files and + // once we've seen all redirects, there's no point in connecting to it + // again. + bool interesting = true; + + // if this is true, this URL was created by a redirect and should not be + // saved in the resume data + bool ephemeral = false; + + // this is set to true when this web seed was created from a redirect + // from a global IP, and SSRF mitigation is enabled. It prevents this + // web seed from resolving to any local network IPs. + bool no_local_ips = false; + + // This means we want to preserve the web seed in resume data, but not + // use it for the remainder of this session. For example: + // this web seed maye have responded with a redirect. The redirected + // URLs have been added as ephemeral. If an ephemeral URL is redirected, + // its web seed entry is removed. It could also mean we don't support + // the URL protocol, but maybe another client may support it. + bool disabled = false; + + // if the web server doesn't support keepalive or a block request was + // interrupted, the block received so far is kept here for the next + // connection to pick up + peer_request restart_request = { piece_index_t(-1), -1, -1}; + aux::vector restart_piece; + + // this maps file index to a URL it has been redirected to. If an entry is + // missing, it means it has not been redirected and the full path should + // be constructed normally based on the filename. All redirections are + // relative to the web seed hostname root. + std::map redirects; + + // if this bitfield is non-empty, it represents the files this web server + // has. + typed_bitfield have_files; +#if defined __GNUC__ && defined _GLIBCXX_DEBUG + // this works around a bug in libstdc++'s checked iterators + // http://stackoverflow.com/questions/22915325/avoiding-self-assignment-in-stdshuffle + web_seed_t& operator=(web_seed_t&& rhs) noexcept + { + if (&rhs == this) return *this; + + web_seed_entry::operator=(std::move(rhs)); + retry = std::move(rhs.retry); + endpoints = std::move(rhs.endpoints); + peer_info = std::move(rhs.peer_info); + supports_keepalive = std::move(rhs.supports_keepalive); + resolving = std::move(rhs.resolving); + removed = std::move(rhs.removed); + ephemeral = std::move(rhs.ephemeral); + no_local_ips = std::move(rhs.no_local_ips); + disabled = std::move(rhs.disabled); + restart_request = std::move(rhs.restart_request); + restart_piece = std::move(rhs.restart_piece); + redirects = std::move(rhs.redirects); + have_files = std::move(rhs.have_files); + return *this; + } + + web_seed_t& operator=(web_seed_t const&) = default; + web_seed_t(web_seed_t const&) = default; +#endif + }; + + struct TORRENT_EXTRA_EXPORT torrent_hot_members + { + torrent_hot_members(aux::session_interface& ses + , add_torrent_params const& p, bool session_paused); + + protected: + // the piece picker. This is allocated lazily. When we don't + // have anything in the torrent (for instance, if it hasn't + // been started yet) or if we have everything, there is no + // picker. It's allocated on-demand the first time we need + // it in torrent::need_picker(). In order to tell the + // difference between having everything and nothing in + // the case there is no piece picker, see m_have_all. + std::unique_ptr m_picker; + + std::unique_ptr m_hash_picker; + + // TODO: make this a raw pointer. perhaps keep the shared_ptr + // around further down the object to maintain an owner + std::shared_ptr m_torrent_file; + + // This is the sum of all non-pad file sizes. In the next major version + // this is stored in file_storage and no longer need to be kept here. + std::int64_t m_size_on_disk = 0; + + // a back reference to the session + // this torrent belongs to. + aux::session_interface& m_ses; + + // this vector is sorted at all times, by the pointer value. + // use sorted_insert() and sorted_find() on it. The GNU STL + // implementation on Darwin uses significantly less memory to + // represent a vector than a set, and this set is typically + // relatively small, and it's cheap to copy pointers. + aux::vector m_connections; + + // the scrape data from the tracker response, this + // is optional and may be 0xffffff + std::uint32_t m_complete:24; + + // set to true when this torrent may not download anything + bool m_upload_mode:1; + + // this is set to false as long as the connections + // of this torrent haven't been initialized. If we + // have metadata from the start, connections are + // initialized immediately, if we didn't have metadata, + // they are initialized right after files_checked(). + // valid_resume_data() will return false as long as + // the connections aren't initialized, to avoid + // them from altering the piece-picker before it + // has been initialized with files_checked(). + bool m_connections_initialized:1; + + // is set to true when the torrent has + // been aborted. + bool m_abort:1; + + // is true if this torrent has allows having peers + bool m_paused:1; + + // is true if the session is paused, in which case the torrent is + // effectively paused as well. + bool m_session_paused:1; + +#ifndef TORRENT_DISABLE_SHARE_MODE + // this is set when the torrent is in share-mode + bool m_share_mode:1; +#endif + + // this is true if we have all pieces. If it's false, + // it means we either don't have any pieces, or, if + // there is a piece_picker object present, it contains + // the state of how many pieces we have + bool m_have_all:1; + + // set to true when this torrent has been paused but + // is waiting to finish all current download requests + // before actually closing all connections, when in graceful pause mode, + // m_paused is also true. + bool m_graceful_pause_mode:1; + + // state subscription. If set, a pointer to this torrent will be added + // to the session_impl::m_torrent_lists[torrent_state_updates] + // whenever this torrent's state changes (any state). + bool m_state_subscription:1; + + // the maximum number of connections for this torrent + std::uint32_t m_max_connections:24; + + // the state of this torrent (queued, checking, downloading, etc.) + std::uint32_t m_state:3; + + std::unique_ptr m_peer_list; + }; + + // a torrent is a class that holds information + // for a specific download. It updates itself against + // the tracker + struct TORRENT_EXTRA_EXPORT torrent + : private single_threaded + , private torrent_hot_members + , request_callback + , peer_class_set + , std::enable_shared_from_this + { + // add_torrent_params may contain large merkle trees that are best + // moved. Deleting the const& overload ensures that it's always moved in. + torrent(aux::session_interface& ses, bool session_paused, add_torrent_params&& p); + torrent(aux::session_interface&, bool, add_torrent_params const& p) = delete; + ~torrent() override; + + // This may be called from multiple threads + info_hash_t const& info_hash() const { return m_info_hash; } + + bool is_deleted() const { return m_deleted; } + + // starts the announce timer + void start(); + + void added() + { + TORRENT_ASSERT(m_added == false); + m_added = true; + update_gauge(); + } + + void removed() + { + TORRENT_ASSERT(m_added == true); + m_added = false; + set_queue_position(no_pos); + // make sure we decrement the gauge counter for this torrent + update_gauge(); + } + + bool is_added() const { return m_added; } + + // returns which stats gauge this torrent currently + // has incremented. + int current_stats_state() const; + +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extension(std::shared_ptr); + void remove_extension(std::shared_ptr); + void add_extension_fun(std::function(torrent_handle const&, client_data_t)> const& ext + , client_data_t userdata); + void notify_extension_add_peer(tcp::endpoint const& ip + , peer_source_flags_t src, add_peer_flags_t flags); +#endif + + peer_connection* find_lowest_ranking_peer() const; + +#if TORRENT_USE_ASSERTS + bool has_peer(peer_connection const* p) const + { return sorted_find(m_connections, p) != m_connections.end(); } + bool is_single_thread() const { return single_threaded::is_single_thread(); } +#endif + + // this is called when the torrent has metadata. + // it will initialize the storage and the piece-picker + void init(); + + void load_merkle_trees(aux::vector, file_index_t> t + , aux::vector, file_index_t> mask + , aux::vector, file_index_t> verified); + + // find the peer that introduced us to the given endpoint. This is + // used when trying to holepunch. We need the introducer so that we + // can send a rendezvous connect message + bt_peer_connection* find_introducer(tcp::endpoint const& ep) const; + + // if we're connected to a peer at ep, return its peer connection + // only count BitTorrent peers + bt_peer_connection* find_peer(tcp::endpoint const& ep) const; + peer_connection* find_peer(peer_id const& pid); + + // checks to see if this peer id is used in one of our own outgoing + // connections. + bool is_self_connection(peer_id const& pid) const; + + void on_resume_data_checked(status_t status, storage_error const& error); + void on_force_recheck(status_t status, storage_error const& error); + void on_piece_hashed(aux::vector block_hashes + , piece_index_t piece, sha1_hash const& piece_hash + , storage_error const& error); + void files_checked(); + void start_checking(); + + void start_announcing(); + void stop_announcing(); + + void send_upload_only(); + +#ifndef TORRENT_DISABLE_SHARE_MODE + void send_share_mode(); + void set_share_mode(bool s); + bool share_mode() const { return m_share_mode; } +#endif + + // TODO: make graceful pause also finish all sending blocks + // before disconnecting + bool graceful_pause() const { return m_graceful_pause_mode; } + + torrent_flags_t flags() const; + void set_flags(torrent_flags_t flags, torrent_flags_t mask); + + void set_upload_mode(bool b); + bool upload_mode() const { return m_upload_mode || m_graceful_pause_mode; } + bool is_upload_only() const { return is_finished() || upload_mode(); } + + int seed_rank(aux::session_settings const& s) const; + + void add_piece(piece_index_t piece, char const* data, add_piece_flags_t flags); + void add_piece_async(piece_index_t piece, std::vector data, add_piece_flags_t flags); + void on_disk_write_complete(storage_error const& error + , peer_request const& p); + + void set_progress_ppm(int p) { m_progress_ppm = std::uint32_t(p); } + struct read_piece_struct + { + boost::shared_array piece_data; + int blocks_left; + bool fail; + error_code error; + }; + void read_piece(piece_index_t); + void on_disk_read_complete(disk_buffer_holder, storage_error const& + , peer_request const&, std::shared_ptr); + + storage_mode_t storage_mode() const; + + // this will flag the torrent as aborted. The main + // loop in session_impl will check for this state + // on all torrents once every second, and take + // the necessary actions then. + void abort(); + bool is_aborted() const { return m_abort; } + void panic(); + + void new_external_ip(); + + torrent_status::state_t state() const + { return torrent_status::state_t(m_state); } + void set_state(torrent_status::state_t s); + + aux::session_settings const& settings() const; + aux::session_interface& session() { return m_ses; } + + void set_sequential_download(bool sd); + bool is_sequential_download() const + { return m_sequential_download || m_auto_sequential; } + + void queue_up(); + void queue_down(); + void set_queue_position(queue_position_t p); + queue_position_t queue_position() const { return m_sequence_number; } + // used internally + void set_queue_position_impl(queue_position_t const p) + { + if (m_sequence_number == p) return; + m_sequence_number = p; + state_updated(); + } + + void second_tick(int tick_interval_ms); + + // see if we need to connect to web seeds, and if so, + // connect to them + void maybe_connect_web_seeds(); + + std::string name() const; + + stat statistics() const { return m_stat; } + boost::optional bytes_left() const; + + void bytes_done(torrent_status& st, status_flags_t) const; + + void sent_bytes(int bytes_payload, int bytes_protocol); + void received_bytes(int bytes_payload, int bytes_protocol); + void trancieve_ip_packet(int bytes, bool ipv6); + void sent_syn(bool ipv6); + void received_synack(bool ipv6); + + void set_ip_filter(std::shared_ptr ipf); + void privileged_port_updated(); + void port_filter_updated(); + ip_filter const* get_ip_filter() { return m_ip_filter.get(); } + + std::string resolve_filename(file_index_t file) const; + void handle_exception(); + + enum class disk_class { none, write }; + void handle_disk_error(string_view job_name + , storage_error const& error, peer_connection* c = nullptr + , disk_class rw = disk_class::none); + void handle_inconsistent_hashes(piece_index_t piece); + void clear_error(); + + void set_error(error_code const& ec, file_index_t file); + bool has_error() const { return !!m_error; } + error_code error() const { return m_error; } + + void flush_cache(); + void pause(pause_flags_t flags = {}); + void resume(); + + void set_session_paused(bool b); + void set_paused(bool b, pause_flags_t flags = {}); + void set_announce_to_dht(bool b) { m_announce_to_dht = b; } + void set_announce_to_trackers(bool b) { m_announce_to_trackers = b; } + void set_announce_to_lsd(bool b) { m_announce_to_lsd = b; } + + void stop_when_ready(bool b); + + time_point32 started() const { return m_started; } + void step_session_time(int seconds); + void do_pause(bool was_paused = false); + void do_resume(); + + seconds32 finished_time() const; + seconds32 active_time() const; + seconds32 seeding_time() const; + seconds32 upload_mode_time() const; + + bool is_paused() const; + bool is_torrent_paused() const { return m_paused; } + void force_recheck(); + void save_resume_data(resume_data_flags_t flags); + + bool need_save_resume_data(resume_data_flags_t flags) const + { + return bool(m_need_save_resume_data & flags); + } + + void set_need_save_resume(resume_data_flags_t const flag) + { + // every category sets this bit. TODO: make this flag a combination + // of the other ones + m_need_save_resume_data |= torrent_handle::only_if_modified; + + if (m_need_save_resume_data & flag) return; + m_need_save_resume_data |= flag; + state_updated(); + } + + bool is_auto_managed() const { return m_auto_managed; } + void auto_managed(bool a); + + bool should_check_files() const; + + bool delete_files(remove_flags_t options); + void peers_erased(std::vector const& peers); + +#if TORRENT_ABI_VERSION == 1 +#if !TORRENT_NO_FPU + void file_progress_float(aux::vector& fp); +#endif +#endif // TORRENT_ABI_VERSION + + void post_piece_availability(); + void piece_availability(aux::vector& avail) const; + + void set_piece_priority(piece_index_t index, download_priority_t priority); + download_priority_t piece_priority(piece_index_t index) const; + + void prioritize_pieces(aux::vector const& pieces); + void prioritize_piece_list(std::vector> const& pieces); + void piece_priorities(aux::vector*) const; + + void set_file_priority(file_index_t index, download_priority_t priority); + download_priority_t file_priority(file_index_t index) const; + + void on_file_priority(storage_error const& err, aux::vector prios); + void prioritize_files(aux::vector files); + void file_priorities(aux::vector*) const; + +#ifndef TORRENT_DISABLE_STREAMING + void cancel_non_critical(); + void set_piece_deadline(piece_index_t piece, int t, deadline_flags_t flags); + void reset_piece_deadline(piece_index_t piece); + void clear_time_critical(); +#endif // TORRENT_DISABLE_STREAMING + + void update_piece_priorities( + aux::vector const& file_prios); + + void post_status(status_flags_t flags); + void status(torrent_status* st, status_flags_t flags); + + // this torrent changed state, if the user is subscribing to + // it, add it to the m_state_updates list in session_impl + void state_updated(); + + void file_progress(aux::vector& fp, file_progress_flags_t flags); + void post_file_progress(file_progress_flags_t flags); + +#if TORRENT_ABI_VERSION == 1 + void use_interface(std::string net_interface); +#endif + + void connect_to_url_seed(std::list::iterator); + bool connect_to_peer(torrent_peer*, bool ignore_limit = false); + + int priority() const; +#if TORRENT_ABI_VERSION == 1 + void set_priority(int prio); +#endif // TORRENT_ABI_VERSION + +// -------------------------------------------- + // BANDWIDTH MANAGEMENT + + void set_upload_limit(int limit); + int upload_limit() const; + void set_download_limit(int limit); + int download_limit() const; + + peer_class_t peer_class() const { return m_peer_class; } + + void set_max_uploads(int limit, bool state_update = true); + int max_uploads() const { return int(m_max_uploads); } + void set_max_connections(int limit, bool state_update = true); + int max_connections() const { return int(m_max_connections); } + +// -------------------------------------------- + // PEER MANAGEMENT + + // A web seed entry that's added because of a redirect is flagged as + // ephemeral. We don't want to save these in the resume data. + static constexpr web_seed_flag_t ephemeral = 0_bit; + // A web seed entry with this flag set is not allowed to resolve to an + // IP on a local network. This is part of SSRF mitigation, as it may be + // malicious + static constexpr web_seed_flag_t no_local_ips = 1_bit; + + // add_web_seed won't add duplicates. If we have already added an entry + // with this URL, we'll get back the existing entry + web_seed_t* add_web_seed(std::string const& url + , web_seed_t::type_t type + , std::string const& auth = std::string() + , web_seed_t::headers_t const& extra_headers = web_seed_entry::headers_t() + , web_seed_flag_t flags = {}); + + void remove_web_seed(std::string const& url, web_seed_t::type_t type); + + void retry_web_seed(peer_connection* p, boost::optional retry = boost::none); + + void remove_web_seed_conn(peer_connection* peer); + + std::set web_seeds(web_seed_entry::type_t type) const; + + bool free_upload_slots() const + { return m_num_uploads < m_max_uploads; } + + bool choke_peer(peer_connection& c); + bool unchoke_peer(peer_connection& c, bool optimistic = false); + + void trigger_unchoke() noexcept; + void trigger_optimistic_unchoke() noexcept; + + // used by peer_connection to attach itself to a torrent + // since incoming connections don't know what torrent + // they're a part of until they have received an info_hash. + // false means attach failed + bool attach_peer(peer_connection* p); + + // this will remove the peer and make sure all + // the pieces it had have their reference counter + // decreased in the piece_picker + void remove_peer(std::shared_ptr p) noexcept; + + // cancel requests to this block from any peer we're + // connected to on this torrent + void cancel_block(piece_block block); + + bool want_tick() const; + void update_want_tick(); + void update_state_list(); + + bool want_peers() const; + bool want_peers_download() const; + bool want_peers_finished() const; + + void update_want_peers(); + void update_want_scrape(); + void update_gauge(); + + bool try_connect_peer(); + torrent_peer* add_peer(tcp::endpoint const& adr + , peer_source_flags_t source, pex_flags_t flags = {}); + bool ban_peer(torrent_peer* tp); + void update_peer_port(int port, torrent_peer* p, peer_source_flags_t src); + void set_seed(torrent_peer* p, bool s); + void clear_failcount(torrent_peer* p); + std::pair find_peers(address const& a); + + // the number of peers that belong to this torrent + int num_peers() const { return int(m_connections.size() - m_peers_to_disconnect.size()); } + int num_seeds() const; + int num_downloaders() const; + + using peer_iterator = std::vector::iterator; + using const_peer_iterator = std::vector::const_iterator; + + const_peer_iterator begin() const { return m_connections.begin(); } + const_peer_iterator end() const { return m_connections.end(); } + + peer_iterator begin() { return m_connections.begin(); } + peer_iterator end() { return m_connections.end(); } + +#if TORRENT_ABI_VERSION == 1 + void get_full_peer_list(std::vector* v) const; +#endif + void post_peer_info(); + void get_peer_info(std::vector* v); + void get_download_queue(std::vector* queue) const; + void post_download_queue(); + + void update_auto_sequential(); + private: + void remove_connection(peer_connection const* p); + public: +// -------------------------------------------- + // TRACKER MANAGEMENT + + // these are callbacks called by the tracker_connection instance + // (either http_tracker_connection or udp_tracker_connection) + // when this torrent got a response from its tracker request + // or when a failure occurred + void tracker_response( + tracker_request const& r + , address const& tracker_ip + , std::list
    const& tracker_ips + , struct tracker_response const& resp) override; + void tracker_request_error(tracker_request const& r + , error_code const& ec, operation_t op, const std::string& msg + , seconds32 retry_interval) override; + void tracker_warning(tracker_request const& req + , std::string const& msg) override; + void tracker_scrape_response(tracker_request const& req + , int complete, int incomplete, int downloaded, int downloaders) override; + + void update_scrape_state(); + +#if TORRENT_ABI_VERSION == 1 + // if no password and username is set + // this will return an empty string, otherwise + // it will concatenate the login and password + // ready to be sent over http (but without + // base64 encoding). + std::string tracker_login() const; +#endif + + // generate the tracker key for this torrent. + // The key is passed to http trackers as ``&key=``. + std::uint32_t tracker_key() const; + + // if we need a connect boost, connect some peers + // immediately + void do_connect_boost(); + + // forcefully sets next_announce to the current time + void force_tracker_request(time_point, int tracker_idx, reannounce_flags_t flags); + void scrape_tracker(int idx, bool user_triggered); + void announce_with_tracker(event_t = event_t::none); + +#ifndef TORRENT_DISABLE_DHT + void dht_announce(); +#endif + +#if TORRENT_ABI_VERSION == 1 + // sets the username and password that will be sent to + // the tracker + void set_tracker_login(std::string const& name, std::string const& pw); +#endif + + aux::announce_entry* find_tracker(std::string const& url); +// -------------------------------------------- + // PIECE MANAGEMENT + +#ifndef TORRENT_DISABLE_SHARE_MODE + void recalc_share_mode(); +#endif + +#ifndef TORRENT_DISABLE_SUPERSEEDING + bool super_seeding() const + { + // we're not super seeding if we're not a seed + return m_super_seeding; + } + + void set_super_seeding(bool on); + piece_index_t get_piece_to_super_seed(typed_bitfield const&); +#endif + + // returns true if we have downloaded the given piece + // but not necessarily flushed it to disk + bool have_piece(piece_index_t index) const + { + if (!valid_metadata()) return false; + if (!has_picker()) return m_have_all; + return m_picker->have_piece(index); + } + + // returns true if we have downloaded the given piece + bool user_have_piece(piece_index_t index) const + { + if (!valid_metadata()) return false; + if (index < piece_index_t{0} || index >= m_torrent_file->end_piece()) return false; + if (!has_picker()) return m_have_all; + return m_picker->have_piece(index); + } + +#ifndef TORRENT_DISABLE_PREDICTIVE_PIECES + // a predictive piece is a piece that we might + // not have yet, but still announced to peers, anticipating that + // we'll have it very soon + bool is_predictive_piece(piece_index_t index) const + { + return std::binary_search(m_predictive_pieces.begin(), m_predictive_pieces.end(), index); + } +#endif // TORRENT_DISABLE_PREDICTIVE_PIECES + + private: + + // called when we learn that we have a piece + // only once per piece + void we_have(piece_index_t index, bool loading_resume = false); + + // process the v2 block hashes for a piece + boost::tribool on_blocks_hashed(piece_index_t piece + , span block_hashes); + + public: + + // the number of pieces that have passed + // hash check, but aren't necessarily + // flushed to disk yet + int num_have() const + { + // pretend we have every piece when in seed mode + if (m_seed_mode) return m_torrent_file->num_pieces(); + if (has_picker()) return m_picker->have().num_pieces; + if (m_have_all) return m_torrent_file->num_pieces(); + return 0; + } + + // when we get a have message, this is called for that piece + void peer_has(piece_index_t index, peer_connection const* peer); + + // when we get a bitfield message, this is called for that piece + void peer_has(typed_bitfield const& bits, peer_connection const* peer); + + void peer_has_all(peer_connection const* peer); + + void peer_lost(piece_index_t index, peer_connection const* peer); + void peer_lost(typed_bitfield const& bits + , peer_connection const* peer); + + int block_size() const + { + return valid_metadata() + ? (std::min)(m_torrent_file->piece_length(), default_block_size) + : default_block_size; + } + peer_request to_req(piece_block const& p) const; + + void disconnect_all(error_code const& ec, operation_t op); + int disconnect_peers(int num, error_code const& ec); + + // called every time a block is marked as finished in the + // piece picker. We might have completed the torrent and + // we can delete the piece picker + void maybe_done_flushing(); + + // this is called when the torrent has completed + // the download. It will post an event, disconnect + // all seeds and let the tracker know we're finished. + void completed(); + +#if TORRENT_USE_I2P + void on_i2p_resolve(error_code const& ec, char const* dest); + bool is_i2p() const { return m_i2p; } +#endif + + // this is the asio callback that is called when a name + // lookup for a PEER is completed. + void on_peer_name_lookup(error_code const& + , std::vector
    const& + , int port + , protocol_version); + + // this is the asio callback that is called when a name + // lookup for a WEB SEED is completed. + void on_name_lookup(error_code const& + , std::vector
    const& + , int port + , std::list::iterator); + + void connect_web_seed(std::list::iterator web, tcp::endpoint a); + + // this is the asio callback that is called when a name + // lookup for a proxy for a web seed is completed. + void on_proxy_name_lookup(error_code const& e + , std::vector
    const& addrs + , std::list::iterator web, int port); + + // re-evaluates whether this torrent should be considered inactive or not + void on_inactivity_tick(error_code const& ec); + + + // calculate the instantaneous inactive state (the externally facing + // inactive state is not instantaneous, but low-pass filtered) + bool is_inactive_internal() const; + + // remove a web seed, or schedule it for removal in case there + // are outstanding operations on it + void remove_web_seed_iter(std::list::iterator web); + + // this is called when the torrent has finished. i.e. + // all the pieces we have not filtered have been downloaded. + // If no pieces are filtered, this is called first and then + // completed() is called immediately after it. + void finished(); + + // This is the opposite of finished. It is called if we used + // to be finished but enabled some files for download so that + // we wasn't finished anymore. + void resume_download(); + + void verify_piece(piece_index_t piece); + void on_piece_verified(aux::vector block_hashes + , piece_index_t piece + , sha1_hash const& piece_hash, storage_error const& error); + + // this is called whenever a peer in this swarm becomes interesting + // it is responsible for issuing a block request, if appropriate + void peer_is_interesting(peer_connection& c); + + // piece_passed is called when a piece passes the hash check + // this will tell all peers that we just got his piece + // and also let the piece picker know that we have this piece + // so it wont pick it for download + void piece_passed(piece_index_t index); + + // piece_failed is called when a piece fails the hash check + // for failures detected with v2 hashes the failing blocks(s) + // are specified in blocks + // *blocks must be sorted in ascending order* + void piece_failed(piece_index_t index, std::vector blocks = std::vector()); + + // the peers in "peers" participated in sending a bad piece. If + // "known_bad_peer" is true, we know for sure the peers are guilty, + // otherwise only one may be guilty (meaning we can't unconditionally + // disconnect) + void penalize_peers(std::set const& peers + , piece_index_t index + , bool known_bad_peer); + + // this is the handler for hash failure piece synchronization + // i.e. resetting the piece + void on_piece_sync(piece_index_t piece, std::vector const& blocks); + + // this is the handler for write failure piece synchronization + void on_piece_fail_sync(piece_index_t piece, piece_block b); + + void add_redundant_bytes(int b, waste_reason reason); + void add_failed_bytes(int b); + + // this is true if we have all the pieces, but not necessarily flushed them to disk + bool is_seed() const; + + // this is true if we have all the pieces that we want + // the pieces don't necessarily need to be flushed to disk + bool is_finished() const; + + bool is_inactive() const; + + std::string save_path() const; + aux::alert_manager& alerts() const; + piece_picker& picker() + { + TORRENT_ASSERT(m_picker.get()); + return *m_picker; + } + piece_picker const& picker() const + { + TORRENT_ASSERT(m_picker.get()); + return *m_picker; + } + void need_picker(); + bool has_picker() const + { + return m_picker.get() != nullptr; + } + + hash_picker& get_hash_picker() + { + TORRENT_ASSERT(m_hash_picker.get()); + return *m_hash_picker; + } + hash_picker const& get_hash_picker() const + { + TORRENT_ASSERT(m_hash_picker.get()); + return *m_hash_picker; + } + + void need_hash_picker(); + bool has_hash_picker() const + { + return m_hash_picker.get() != nullptr; + } + + void update_max_failcount() + { + if (!m_peer_list) return; + torrent_state st = get_peer_list_state(); + m_peer_list->set_max_failcount(&st); + } + int num_known_peers() const { return m_peer_list ? m_peer_list->num_peers() : 0; } + int num_connect_candidates() const { return m_peer_list ? m_peer_list->num_connect_candidates() : 0; } + + void clear_peers(); + + bool has_storage() const { return bool(m_storage); } + storage_index_t storage() const { return m_storage; } + + torrent_info const& torrent_file() const + { return *m_torrent_file; } + + hash_request pick_hashes(peer_connection* peer); + std::vector get_hashes(hash_request const& req) const; + bool add_hashes(hash_request const& req, span hashes); + void hashes_rejected(hash_request const& req); + void verify_block_hashes(piece_index_t index); + + std::shared_ptr get_torrent_file() const; + + std::shared_ptr get_torrent_copy_with_hashes() const; + + std::vector> get_piece_layers() const; + + void post_trackers(); + std::vector trackers() const; + + // this sets all the "enabled" states on all trackers, giving them + // all one more chance of being tried + void enable_all_trackers(); + + void replace_trackers(std::vector const& urls); + + // returns true if the tracker was added, and false if it was already + // in the tracker list (in which case the source was added to the + // entry in the list) + bool add_tracker(announce_entry const& url); + + torrent_handle get_handle(); + + void write_resume_data(resume_data_flags_t const flags, add_torrent_params& ret) const; + + void seen_complete() { m_last_seen_complete = aux::posix_time(); } + int time_since_complete() const { return int(aux::posix_time() - m_last_seen_complete); } + time_t last_seen_complete() const { return m_last_seen_complete; } + + template + void wrap(Fun f, Args&&... a); + + // LOGGING +#ifndef TORRENT_DISABLE_LOGGING + bool should_log() const override; + void debug_log(const char* fmt, ...) const noexcept override TORRENT_FORMAT(2,3); + + void log_to_all_peers(char const* message); + time_point m_dht_start_time; +#endif + + // DEBUG +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + +// -------------------------------------------- + // RESOURCE MANAGEMENT + + // flags are defined in storage.hpp + void move_storage(std::string const& save_path, move_flags_t flags); + + // renames the file with the given index to the new name + // the name may include a directory path + // posts alert to indicate success or failure + void rename_file(file_index_t index, std::string name); + + // unless this returns true, new connections must wait + // with their initialization. + bool ready_for_connections() const + { return m_connections_initialized; } + bool valid_metadata() const + { return m_torrent_file->is_valid(); } + bool are_files_checked() const + { return m_files_checked; } + + error_code initialize_merkle_trees(); + + // parses the info section from the given + // bencoded tree and moves the torrent + // to the checker thread for initial checking + // of the storage. + // a return value of false indicates an error + bool set_metadata(span metadata); + + queue_position_t sequence_number() const { return m_sequence_number; } + + bool seed_mode() const { return m_seed_mode; } + + enum class seed_mode_t { check_files, skip_checking }; + + void leave_seed_mode(seed_mode_t checking); + + bool all_verified() const + { return int(m_num_verified) == m_torrent_file->num_pieces(); } + bool verifying_piece(piece_index_t const piece) const + { return m_verifying.get_bit(piece); } + void verifying(piece_index_t const piece) + { + TORRENT_ASSERT(m_verifying.get_bit(piece) == false); + m_verifying.set_bit(piece); + } + bool verified_piece(piece_index_t piece) const + { return m_verified.get_bit(piece); } + void verified(piece_index_t piece); + + // this is called once periodically for torrents + // that are not private + void lsd_announce(); + + void update_last_upload() { m_last_upload = aux::time_now32(); } + + void set_apply_ip_filter(bool b); + bool apply_ip_filter() const { return m_apply_ip_filter; } + +#ifndef TORRENT_DISABLE_PREDICTIVE_PIECES + std::vector const& predictive_pieces() const + { return m_predictive_pieces; } + + // this is called whenever we predict to have this piece + // within one second + void predicted_have_piece(piece_index_t index, int milliseconds); +#endif + + void clear_in_state_update() + { + TORRENT_ASSERT(m_links[aux::session_interface::torrent_state_updates].in_list()); + m_links[aux::session_interface::torrent_state_updates].clear(); + } + + void inc_num_connecting(torrent_peer* pp) + { + ++m_num_connecting; + if (pp->seed) ++m_num_connecting_seeds; + } + void dec_num_connecting(torrent_peer* pp) + { + TORRENT_ASSERT(m_num_connecting > 0); + --m_num_connecting; + if (pp->seed) + { + TORRENT_ASSERT(m_num_connecting_seeds > 0); + --m_num_connecting_seeds; + } + TORRENT_ASSERT(m_num_connecting <= int(m_connections.size())); + } + + bool is_ssl_torrent() const { return m_ssl_torrent; } +#ifdef TORRENT_SSL_PEERS + void set_ssl_cert(std::string const& certificate + , std::string const& private_key + , std::string const& dh_params + , std::string const& passphrase); + void set_ssl_cert_buffer(std::string const& certificate + , std::string const& private_key + , std::string const& dh_params); + ssl::context* ssl_ctx() const { return m_ssl_ctx.get(); } +#endif + + int num_time_critical_pieces() const + { +#ifndef TORRENT_DISABLE_STREAMING + return int(m_time_critical_pieces.size()); +#else + return 0; +#endif + } + + int get_suggest_pieces(std::vector& p + , typed_bitfield const& bits + , int const n) + { + return m_suggest_pieces.get_pieces(p, bits, n); + } + void add_suggest_piece(piece_index_t index); + + client_data_t get_userdata() const { return m_userdata; } + + static constexpr int no_gauge_state = 0xf; + + private: + + void on_exception(std::exception const& e); + void on_error(error_code const& ec); + + // trigger deferred disconnection of peers + void on_remove_peers() noexcept; + + void ip_filter_updated(); + + void inc_stats_counter(int c, int value = 1); + + // initialize the torrent_state structure passed to peer_list + // member functions. Don't forget to also call peers_erased() + // on the erased member after the peer_list call + torrent_state get_peer_list_state(); + + void construct_storage(); + void update_list(torrent_list_index_t list, bool in); + + void on_files_deleted(storage_error const& error); + void on_torrent_paused(); + void on_storage_moved(status_t status, std::string const& path + , storage_error const& error); + void on_file_renamed(std::string const& filename + , file_index_t file_idx + , storage_error const& error); + void on_cache_flushed(bool manually_triggered); + + // this is used when a torrent is being removed.It synchronizes with the + // disk thread + void on_torrent_aborted(); + + // upload and download rate limits for the torrent + void set_limit_impl(int limit, int channel, bool state_update = true); + int limit_impl(int channel) const; + + int deprioritize_tracker(int tracker_index); + + void update_peer_interest(bool was_finished); + void prioritize_udp_trackers(); + + void update_tracker_timer(time_point32 now); + + void on_tracker_announce(error_code const& ec); + +#ifndef TORRENT_DISABLE_DHT + static void on_dht_announce_response_disp(std::weak_ptr t + , protocol_version v, std::vector const& peers); + void on_dht_announce_response(protocol_version v, std::vector const& peers); + bool should_announce_dht() const; +#endif + +#ifndef TORRENT_DISABLE_STREAMING + void remove_time_critical_piece(piece_index_t piece, bool finished = false); + void remove_time_critical_pieces(aux::vector const& priority); + void request_time_critical_pieces(); +#endif // TORRENT_DISABLE_STREAMING + + void need_peer_list(); + + std::shared_ptr m_ip_filter; + + // all time totals of uploaded and downloaded payload + // stored in resume data + std::int64_t m_total_uploaded = 0; + std::int64_t m_total_downloaded = 0; + + // the number of bytes of pad files + std::int64_t m_padding_bytes = 0; + + // this is a handle that keeps the storage object in the disk io subsystem + // alive, as well as the index referencing the storage/torrent in the disk + // I/O. When this destructs, the torrent will be removed from the disk + // subsystem. + storage_holder m_storage; + +#ifdef TORRENT_SSL_PEERS + std::unique_ptr m_ssl_ctx; + + bool verify_peer_cert(bool const preverified, ssl::verify_context& ctx); + + void init_ssl(string_view cert); +#endif + + void setup_peer_class(); + + // The list of web seeds in this torrent. Seeds with fatal errors are + // removed from the set. It's important that iterators are not + // invalidated as entries are added and removed from this list, hence the + // std::list + std::list m_web_seeds; + +#ifndef TORRENT_DISABLE_EXTENSIONS + std::list> m_extensions; +#endif + + // used for tracker announces + deadline_timer m_tracker_timer; + + // used to detect when we are active or inactive for long enough + // to trigger the auto-manage logic + deadline_timer m_inactivity_timer; + + // this is the upload and download statistics for the whole torrent. + // it's updated from all its peers once every second. + libtorrent::stat m_stat; + + // ----------------------------- + + // this vector is allocated lazily. If no file priorities are + // ever changed, this remains empty. Any unallocated slot + // implicitly means the file has priority 4. + // TODO: this wastes 5 bits per file + aux::vector m_file_priority; + + // any file priority updates attempted while another file priority update + // is in-progress/outstanding with the disk I/O thread, are queued up in + // this dictionary. Once the outstanding update comes back, all of these + // are applied in one batch + std::map m_deferred_file_priorities; + + // this object is used to track download progress of individual files + aux::file_progress m_file_progress; + + // a queue of the most recent low-availability pieces we accessed on disk. + // These are good candidates for suggesting other peers to request from + // us. + aux::suggest_piece m_suggest_pieces; + + aux::vector m_trackers; + +#ifndef TORRENT_DISABLE_STREAMING + // this list is sorted by time_critical_piece::deadline + std::vector m_time_critical_pieces; +#endif + + std::string m_trackerid; +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.1 + std::string m_username; + std::string m_password; +#endif + + std::string m_save_path; + +#ifndef TORRENT_DISABLE_PREDICTIVE_PIECES + // this is a list of all pieces that we have announced + // as having, without actually having yet. If we receive + // a request for a piece in this list, we need to hold off + // on responding until we have completed the piece and + // verified its hash. If the hash fails, send reject to + // peers with outstanding requests, and dont_have to other + // peers. This vector is ordered, to make lookups fast. + + // TODO: 3 factor out predictive pieces and all operations on it into a + // separate class (to use as member here instead) + std::vector m_predictive_pieces; +#endif + + // v2 merkle tree for each file + aux::vector m_merkle_trees; + + // the performance counters of this session + counters& m_stats_counters; + + // each bit represents a piece. a set bit means + // the piece has had its hash verified. This + // is only used in seed mode (when m_seed_mode + // is true) + typed_bitfield m_verified; + + // this means there is an outstanding, async, operation + // to verify each piece that has a 1 + typed_bitfield m_verifying; + + // set if there's an error on this torrent + error_code m_error; + + // used if there is any resume data. Some of the information from the + // add_torrent_params struct are needed later in the torrent object's life + // cycle, and not in the constructor. So we need to save if away here + std::unique_ptr m_add_torrent_params; + + // if the torrent is started without metadata, it may + // still be given a name until the metadata is received + // once the metadata is received this field will no + // longer be used and will be reset + std::unique_ptr m_name; + + // the posix time this torrent was added and when + // it was completed. If the torrent isn't yet + // completed, m_completed_time is 0 + std::time_t m_added_time; + std::time_t m_completed_time; + + // this was the last time _we_ saw a seed in this swarm + std::time_t m_last_seen_complete = 0; + + // this is the time last any of our peers saw a seed + // in this swarm + std::time_t m_swarm_last_seen_complete = 0; + + // keep a copy if the info-hash here, so it can be accessed from multiple + // threads, and be cheap to access from the client + info_hash_t m_info_hash; + + public: + // these are the lists this torrent belongs to. For more + // details about each list, see session_impl.hpp. Each list + // represents a group this torrent belongs to and makes it + // efficient to enumerate only torrents belonging to a specific + // group. Such as torrents that want peer connections or want + // to be ticked etc. + + // TODO: 3 factor out the links (as well as update_list() to a separate + // class that torrent can inherit) + aux::array + m_links; + + private: + + // m_num_verified = m_verified.count() + std::uint32_t m_num_verified = 0; + + // if this torrent is running, this was the time + // when it was started. This is used to have a + // bias towards keeping seeding torrents that + // recently was started, to avoid oscillation + // this is specified at a second granularity + time_point32 m_started = aux::time_now32(); + + // if we're a seed, this is the timestamp of when we became one + time_point32 m_became_seed = aux::time_now32(); + + // if we're finished, this is the timestamp of when we finished + time_point32 m_became_finished = aux::time_now32(); + + // when checking, this is the first piece we have not + // issued a hash job for + piece_index_t m_checking_piece{0}; + + // the number of pieces we completed the check of + piece_index_t m_num_checked_pieces{0}; + + // if the error occurred on a file, this is the index of that file + // there are a few special cases, when this is negative. See + // set_error() + file_index_t m_error_file; + + // the average time it takes to download one time critical piece + std::int32_t m_average_piece_time = 0; + + // the average piece download time deviation + std::int32_t m_piece_time_deviation = 0; + + // the number of bytes that has been + // downloaded that failed the hash-test + std::int64_t m_total_failed_bytes = 0; + std::int64_t m_total_redundant_bytes = 0; + + // the sequence number for this torrent, this is a + // monotonically increasing number for each added torrent + queue_position_t m_sequence_number; + + // used to post a message to defer disconnecting peers + std::vector> m_peers_to_disconnect; + aux::deferred_handler m_deferred_disconnect; + aux::handler_storage m_deferred_handler_storage; + + // these are the peer IDs we've used for our outgoing peer connections for + // this torrent. If we get an incoming peer claiming to have one of these, + // it's a connection to ourself, and we should reject it. + std::set m_outgoing_pids; + + // for torrents who have a bandwidth limit, this is != 0 + // and refers to a peer_class in the session. + peer_class_t m_peer_class{0}; + + // of all peers in m_connections, this is the number + // of peers that are outgoing and still waiting to + // complete the connection. This is used to possibly + // kick out these connections when we get incoming + // connections (if we've reached the connection limit) + std::uint16_t m_num_connecting = 0; + + // this is the peer id we generate when we add the torrent. Peers won't + // use this (they generate their own peer ids) but this is used in case + // the tracker returns peer IDs, to identify ourself in the peer list to + // avoid connecting back to it. + peer_id m_peer_id; + + // ============================== + // The following members are specifically + // ordered to make the 24 bit members + // properly 32 bit aligned by inserting + // 8 bits after each one + // ============================== + + // if we're currently in upload-mode this is the time timestamp of when + // we entered it + time_point32 m_upload_mode_time = aux::time_now32(); + + // true when this torrent should announce to + // trackers + bool m_announce_to_trackers:1; + + // true when this torrent should announce to + // the local network + bool m_announce_to_lsd:1; + + // is set to true every time there is an incoming + // connection to this torrent + bool m_has_incoming:1; + + // this is set to true when the files are checked + // before the files are checked, we don't try to + // connect to peers + bool m_files_checked:1; + + // determines the storage state for this torrent. + unsigned int m_storage_mode:2; + + // this is true while tracker announcing is enabled + // is is disabled while paused and checking files + bool m_announcing:1; + + // this is true when the torrent has been added to the session. Before + // then, it isn't included in the counters (session_stats) + bool m_added:1; + + // this is > 0 while the tracker deadline timer + // is in use. i.e. one or more trackers are waiting + // for a reannounce + std::int8_t m_waiting_tracker = 0; + +// ---- + + // total time we've been active on this torrent. i.e. either (trying to) + // download or seed. does not count time when the torrent is stopped or + // paused. specified in seconds. This only track time _before_ we started + // the torrent this last time. When the torrent is paused, this counter is + // incremented to include this current session. + seconds32 m_active_time{0}; + + // the index to the last tracker that worked + std::int8_t m_last_working_tracker = -1; + +// ---- + + // total time we've been finished with this torrent. + // does not count when the torrent is stopped or paused. + seconds32 m_finished_time{0}; + + // in case the piece picker hasn't been constructed + // when this settings is set, this variable will keep + // its value until the piece picker is created + bool m_sequential_download:1; + + // this is set if the auto_sequential setting is true and this swarm + // satisfies the criteria to be considered high-availability. i.e. if + // there's mostly seeds in the swarm, download the files sequentially + // for improved disk I/O performance. + bool m_auto_sequential:1; + + // this means we haven't verified the file content + // of the files we're seeding. the m_verified bitfield + // indicates which pieces have been verified and which + // haven't + bool m_seed_mode:1; + +#ifndef TORRENT_DISABLE_SUPERSEEDING + // if this is true, we're currently super seeding this + // torrent. + bool m_super_seeding:1; +#endif + + // if this is set, whenever transitioning into a downloading/seeding state + // from a non-downloading/seeding state, the torrent is paused. + bool m_stop_when_ready:1; + + // when this is true, this torrent participates in the DHT + bool m_enable_dht:1; + + // when this is true, this torrent participates in local service discovery + bool m_enable_lsd:1; + + bool m_i2p:1; +// ---- + + // total time we've been available as a seed on this torrent. + // does not count when the torrent is stopped or paused. This value only + // accounts for the time prior to the current start of the torrent. When + // the torrent is paused, this counter is incremented to account for the + // additional seeding time. + seconds32 m_seeding_time{0}; + +// ---- + + // the maximum number of uploads for this torrent + std::uint32_t m_max_uploads:24; + + // bits set to indicate which category of resume data state has updated + resume_data_flags_t m_need_save_resume_data; + +// ---- + + // the number of unchoked peers in this torrent + unsigned int m_num_uploads:24; + + // 4 unused bits + + // when this is true, this torrent supports peer exchange + bool m_enable_pex:1; + + // set to true if the session IP filter applies to this + // torrent or not. Defaults to true. + bool m_apply_ip_filter:1; + + // this is true when our effective inactive state is different from our + // actual inactive state. Whenever this state changes, there is a + // quarantine period until we change the effective state. This is to avoid + // flapping. If the state changes back during this period, we cancel the + // quarantine + bool m_pending_active_change:1; + + // this is set to true if all piece layers were successfully loaded and + // validated. Only for v2 torrents + // TODO: this member can probably be removed + bool m_v2_piece_layers_validated:1; + +// ---- + + // this is set to the connect boost quota for this torrent. + // After having received this many priority peer connection attempts, it + // falls back onto the steady state peer connection logic, driven by the + // session tick. Each tracker response, as long as this is non-zero, will + // attempt to connect to peers immediately and decrement the counter. + // We give torrents a connect boost when they are first added and then + // every time they resume from being paused. + std::uint8_t m_connect_boost_counter; + +// ---- + + // the scrape data from the tracker response, this + // is optional and may be 0xffffff + std::uint32_t m_incomplete:24; + + // true when the torrent should announce to + // the DHT + bool m_announce_to_dht:1; + + // even if we're not built to support SSL torrents, + // remember that this is an SSL torrent, so that we don't + // accidentally start seeding it without any authentication. + bool m_ssl_torrent:1; + + // this is set to true if we're trying to delete the + // files belonging to it. When set, don't write any + // more blocks to disk! + bool m_deleted:1; + +// ---- + + // the timestamp of the last piece passed for this torrent specified in + // seconds since epoch. + time_point32 m_last_download{seconds32(0)}; + + // the number of peer connections to seeds. This should be the same as + // counting the peer connections that say true for is_seed() + std::uint16_t m_num_seeds = 0; + + // this is the number of peers that are seeds, and count against + // m_num_seeds, but have not yet been connected + std::uint16_t m_num_connecting_seeds = 0; + + // the timestamp of the last byte uploaded from this torrent specified in + // seconds since epoch. + time_point32 m_last_upload{seconds32(0)}; + + // user data as passed in by add_torrent_params + client_data_t m_userdata; + +// ---- + + // if this is true, libtorrent may pause and resume + // this torrent depending on queuing rules. Torrents + // started with auto_managed flag set may be added in + // a paused state in case there are no available + // slots. + bool m_auto_managed:1; + + // the current stats gauge this torrent counts against + std::uint32_t m_current_gauge_state:4; + + // set to true while moving the storage + bool m_moving_storage:1; + + // this is true if this torrent is considered inactive from the + // queuing mechanism's point of view. If a torrent doesn't transfer + // at high enough rates, it's inactive. + bool m_inactive:1; + +// ---- + + // the scrape data from the tracker response, this + // is optional and may be 0xffffff + std::uint32_t m_downloaded:24; + +#if TORRENT_ABI_VERSION == 1 + // the timestamp of the last scrape request to one of the trackers in + // this torrent specified in session_time. This is signed because it must + // be able to represent time before the session started + time_point32 m_last_scrape{seconds32(0)}; +#endif + +// ---- + + // progress parts per million (the number of + // millionths of completeness) + std::uint32_t m_progress_ppm:20; + + // set to true once init() completes successfully. This is important to + // track in case it fails and need to be retried if the client clears + // the torrent error + bool m_torrent_initialized:1; + + // this is set to true while waiting for an async_set_file_priority + bool m_outstanding_file_priority:1; + + // set to true if we've sent an event=completed to any tracker. This will + // prevent us from sending it again to anyone + bool m_complete_sent:1; + +#if TORRENT_USE_ASSERTS + // set to true when torrent is start()ed. It may only be started once + bool m_was_started = false; + bool m_outstanding_check_files = false; + + // this is set to true while we're looping over m_connections. We may not + // mutate the list while doing this + mutable int m_iterating_connections = 0; +#endif + }; +} + +#endif // TORRENT_TORRENT_HPP_INCLUDED diff --git a/docs/include/libtorrent/torrent_flags.hpp b/docs/include/libtorrent/torrent_flags.hpp new file mode 100644 index 0000000..90ee420 --- /dev/null +++ b/docs/include/libtorrent/torrent_flags.hpp @@ -0,0 +1,334 @@ +/* + +Copyright (c) 2017-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_FLAGS_HPP +#define TORRENT_TORRENT_FLAGS_HPP + +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/flags.hpp" + +namespace libtorrent { + +using torrent_flags_t = flags::bitfield_flag; + +namespace torrent_flags { + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_push.hpp" +#endif + + // If ``seed_mode`` is set, libtorrent will assume that all files + // are present for this torrent and that they all match the hashes in + // the torrent file. Each time a peer requests to download a block, + // the piece is verified against the hash, unless it has been verified + // already. If a hash fails, the torrent will automatically leave the + // seed mode and recheck all the files. The use case for this mode is + // if a torrent is created and seeded, or if the user already know + // that the files are complete, this is a way to avoid the initial + // file checks, and significantly reduce the startup time. + // + // Setting ``seed_mode`` on a torrent without metadata (a + // .torrent file) is a no-op and will be ignored. + // + // It is not possible to *set* the ``seed_mode`` flag on a torrent after it has + // been added to a session. It is possible to *clear* it though. + constexpr torrent_flags_t seed_mode = 0_bit; + + // If ``upload_mode`` is set, the torrent will be initialized in + // upload-mode, which means it will not make any piece requests. This + // state is typically entered on disk I/O errors, and if the torrent + // is also auto managed, it will be taken out of this state + // periodically (see ``settings_pack::optimistic_disk_retry``). + // + // This mode can be used to avoid race conditions when + // adjusting priorities of pieces before allowing the torrent to start + // downloading. + // + // If the torrent is auto-managed (``auto_managed``), the torrent + // will eventually be taken out of upload-mode, regardless of how it + // got there. If it's important to manually control when the torrent + // leaves upload mode, don't make it auto managed. + constexpr torrent_flags_t upload_mode = 1_bit; + + // determines if the torrent should be added in *share mode* or not. + // Share mode indicates that we are not interested in downloading the + // torrent, but merely want to improve our share ratio (i.e. increase + // it). A torrent started in share mode will do its best to never + // download more than it uploads to the swarm. If the swarm does not + // have enough demand for upload capacity, the torrent will not + // download anything. This mode is intended to be safe to add any + // number of torrents to, without manual screening, without the risk + // of downloading more than is uploaded. + // + // A torrent in share mode sets the priority to all pieces to 0, + // except for the pieces that are downloaded, when pieces are decided + // to be downloaded. This affects the progress bar, which might be set + // to "100% finished" most of the time. Do not change file or piece + // priorities for torrents in share mode, it will make it not work. + // + // The share mode has one setting, the share ratio target, see + // ``settings_pack::share_mode_target`` for more info. + constexpr torrent_flags_t share_mode = 2_bit; + + // determines if the IP filter should apply to this torrent or not. By + // default all torrents are subject to filtering by the IP filter + // (i.e. this flag is set by default). This is useful if certain + // torrents needs to be exempt for some reason, being an auto-update + // torrent for instance. + constexpr torrent_flags_t apply_ip_filter = 3_bit; + + // specifies whether or not the torrent is paused. i.e. it won't connect to the tracker or any of the peers + // until it's resumed. Note that a paused torrent that also has the + // auto_managed flag set can be started at any time by libtorrent's queuing + // logic. See queuing_. + constexpr torrent_flags_t paused = 4_bit; + + // If the torrent is auto-managed (``auto_managed``), the torrent + // may be resumed at any point, regardless of how it paused. If it's + // important to manually control when the torrent is paused and + // resumed, don't make it auto managed. + // + // If ``auto_managed`` is set, the torrent will be queued, + // started and seeded automatically by libtorrent. When this is set, + // the torrent should also be started as paused. The default queue + // order is the order the torrents were added. They are all downloaded + // in that order. For more details, see queuing_. + constexpr torrent_flags_t auto_managed = 5_bit; + + // used in add_torrent_params to indicate that it's an error to attempt + // to add a torrent that's already in the session. If it's not considered an + // error, a handle to the existing torrent is returned. + // This flag is not saved by write_resume_data(), since it is only meant for + // adding torrents. + constexpr torrent_flags_t duplicate_is_error = 6_bit; + + // on by default and means that this torrent will be part of state + // updates when calling post_torrent_updates(). + // This flag is not saved by write_resume_data(). + constexpr torrent_flags_t update_subscribe = 7_bit; + + // sets the torrent into super seeding/initial seeding mode. If the torrent + // is not a seed, this flag has no effect. + constexpr torrent_flags_t super_seeding = 8_bit; + + // sets the sequential download state for the torrent. In this mode the + // piece picker will pick pieces with low index numbers before pieces with + // high indices. The actual pieces that are picked depend on other factors + // still, such as which pieces a peer has and whether it is in parole mode + // or "prefer whole pieces"-mode. Sequential mode is not ideal for streaming + // media. For that, see set_piece_deadline() instead. + constexpr torrent_flags_t sequential_download = 9_bit; + + // When this flag is set, the torrent will *force stop* whenever it + // transitions from a non-data-transferring state into a data-transferring + // state (referred to as being ready to download or seed). This is useful + // for torrents that should not start downloading or seeding yet, but want + // to be made ready to do so. A torrent may need to have its files checked + // for instance, so it needs to be started and possibly queued for checking + // (auto-managed and started) but as soon as it's done, it should be + // stopped. + // + // *Force stopped* means auto-managed is set to false and it's paused. As + // if the auto_manages flag is cleared and the paused flag is set on the torrent. + // + // Note that the torrent may transition into a downloading state while + // setting this flag, and since the logic is edge triggered you may + // miss the edge. To avoid this race, if the torrent already is in a + // downloading state when this call is made, it will trigger the + // stop-when-ready immediately. + // + // When the stop-when-ready logic fires, the flag is cleared. Any + // subsequent transitions between downloading and non-downloading states + // will not be affected, until this flag is set again. + // + // The behavior is more robust when setting this flag as part of adding + // the torrent. See add_torrent_params. + // + // The stop-when-ready flag fixes the inherent race condition of waiting + // for the state_changed_alert and then call pause(). The download/seeding + // will most likely start in between posting the alert and receiving the + // call to pause. + // + // A downloading state is one where peers are being connected. Which means + // just downloading the metadata via the ``ut_metadata`` extension counts + // as a downloading state. In order to stop a torrent once the metadata + // has been downloaded, instead set all file priorities to dont_download + constexpr torrent_flags_t stop_when_ready = 10_bit; + + // when this flag is set, the tracker list in the add_torrent_params + // object override any trackers from the torrent file. If the flag is + // not set, the trackers from the add_torrent_params object will be + // added to the list of trackers used by the torrent. + // This flag is set by read_resume_data() if there are trackers present in + // the resume data file. This effectively makes the trackers saved in the + // resume data take precedence over the original trackers. This includes if + // there's an empty list of trackers, to support the case where they were + // explicitly removed in the previous session. + // This flag is not saved by write_resume_data() + constexpr torrent_flags_t override_trackers = 11_bit; + + // If this flag is set, the web seeds from the add_torrent_params + // object will override any web seeds in the torrent file. If it's not + // set, web seeds in the add_torrent_params object will be added to the + // list of web seeds used by the torrent. + // This flag is set by read_resume_data() if there are web seeds present in + // the resume data file. This effectively makes the web seeds saved in the + // resume data take precedence over the original ones. This includes if + // there's an empty list of web seeds, to support the case where they were + // explicitly removed in the previous session. + // This flag is not saved by write_resume_data() + constexpr torrent_flags_t override_web_seeds = 12_bit; + + // if this flag is set (which it is by default) the torrent will be + // considered needing to save its resume data immediately, in the + // category if_metadata_changed. See resume_data_flags_t and + // save_resume_data() for details. + // + // This flag is cleared by a successful call to save_resume_data() + // This flag is not saved by write_resume_data(), since it represents an + // ephemeral state of a running torrent. + constexpr torrent_flags_t need_save_resume = 13_bit; + +#if TORRENT_ABI_VERSION == 1 + // indicates that this torrent should never be unloaded from RAM, even + // if unloading torrents are allowed in general. Setting this makes + // the torrent exempt from loading/unloading management. + TORRENT_DEPRECATED constexpr torrent_flags_t pinned = 14_bit; + + // If ``override_resume_data`` is set, flags set for this torrent + // in this ``add_torrent_params`` object will take precedence over + // whatever states are saved in the resume data. For instance, the + // ``paused``, ``auto_managed``, ``sequential_download``, ``seed_mode``, + // ``super_seeding``, ``max_uploads``, ``max_connections``, + // ``upload_limit`` and ``download_limit`` are all affected by this + // flag. The intention of this flag is to have any field in + // add_torrent_params configuring the torrent override the corresponding + // configuration from the resume file, with the one exception of save + // resume data, which has its own flag (for historic reasons). + // "file_priorities" and "save_path" are not affected by this flag. + TORRENT_DEPRECATED constexpr torrent_flags_t override_resume_data = 15_bit; + + // defaults to on and specifies whether tracker URLs loaded from + // resume data should be added to the trackers in the torrent or + // replace the trackers. When replacing trackers (i.e. this flag is not + // set), any trackers passed in via add_torrent_params are also + // replaced by any trackers in the resume data. The default behavior is + // to have the resume data override the .torrent file _and_ the + // trackers added in add_torrent_params. + TORRENT_DEPRECATED constexpr torrent_flags_t merge_resume_trackers = 16_bit; + + // if this flag is set, the save path from the resume data file, if + // present, is honored. This defaults to not being set, in which + // case the save_path specified in add_torrent_params is always used. + TORRENT_DEPRECATED constexpr torrent_flags_t use_resume_save_path = 17_bit; + + // defaults to on and specifies whether web seed URLs loaded from + // resume data should be added to the ones in the torrent file or + // replace them. No distinction is made between the two different kinds + // of web seeds (`BEP 17`_ and `BEP 19`_). When replacing web seeds + // (i.e. when this flag is not set), any web seeds passed in via + // add_torrent_params are also replaced. The default behavior is to + // have any web seeds in the resume data take precedence over whatever + // is passed in here as well as the .torrent file. + TORRENT_DEPRECATED constexpr torrent_flags_t merge_resume_http_seeds = 18_bit; +#endif + + // set this flag to disable DHT for this torrent. This lets you have the DHT + // enabled for the whole client, and still have specific torrents not + // participating in it. i.e. not announcing to the DHT nor picking up peers + // from it. + constexpr torrent_flags_t disable_dht = 19_bit; + + // set this flag to disable local service discovery for this torrent. + constexpr torrent_flags_t disable_lsd = 20_bit; + + // set this flag to disable peer exchange for this torrent. + constexpr torrent_flags_t disable_pex = 21_bit; + + // if this flag is set, the resume data will be assumed to be correct + // without validating it against any files on disk. This may be used when + // restoring a session by loading resume data from disk. It will save time + // and also delay any hard disk errors until files are actually needed. If + // the resume data cannot be trusted, or if a torrent is added for the first + // time to some save path that may already have some of the files, this flag + // should not be set. + constexpr torrent_flags_t no_verify_files = 22_bit; + + // default all file priorities to dont_download. This is useful for adding + // magnet links where the number of files is unknown, but the + // file_priorities is still set for some files. Any file not covered by + // the file_priorities list will be set to normal download priority, + // unless this flag is set, in which case they will be set to 0 + // (dont_download). + constexpr torrent_flags_t default_dont_download = 23_bit; + + // this flag makes the torrent be considered an "i2p torrent" for purposes + // of the allow_i2p_mixed setting. When mixing regular peers and i2p peers + // is disabled, i2p torrents won't add normal peers to its peer list. + // Note that non i2p torrents may still allow i2p peers (on the off-chance + // that a tracker return them and the session is configured with a SAM + // connection). + // This flag is set automatically when adding a torrent that has at least + // one tracker whose hostname ends with .i2p. + // It's also set by parse_magnet_uri() if the tracker list contains such + // URL. + constexpr torrent_flags_t i2p_torrent = 24_bit; + + // all torrent flags combined. Can conveniently be used when creating masks + // for flags + constexpr torrent_flags_t all = torrent_flags_t::all(); + + // internal + constexpr torrent_flags_t default_flags = + torrent_flags::update_subscribe + | torrent_flags::auto_managed + | torrent_flags::paused + | torrent_flags::apply_ip_filter + | torrent_flags::need_save_resume +#if TORRENT_ABI_VERSION == 1 + | torrent_flags::pinned + | torrent_flags::merge_resume_http_seeds + | torrent_flags::merge_resume_trackers +#endif + ; + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + +} // torrent_flags +} // libtorrent + +#endif + diff --git a/docs/include/libtorrent/torrent_handle.hpp b/docs/include/libtorrent/torrent_handle.hpp new file mode 100644 index 0000000..6ffd49f --- /dev/null +++ b/docs/include/libtorrent/torrent_handle.hpp @@ -0,0 +1,1430 @@ +/* + +Copyright (c) 2019, Amir Abrams +Copyright (c) 2003-2022, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2015, 2018, Steven Siloti +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, 2020, AllSeeingEyeTolledEweSew +Copyright (c) 2017, Falcosc +Copyright (c) 2019, Andrei Kurushin +Copyright (c) 2019, ghbplayer +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_HANDLE_HPP_INCLUDED +#define TORRENT_TORRENT_HANDLE_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#if TORRENT_ABI_VERSION == 1 +// for deprecated force_reannounce +#include +#endif +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/fwd.hpp" +#include "libtorrent/socket.hpp" // tcp::endpoint +#include "libtorrent/span.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/torrent_flags.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/peer_info.hpp" // for peer_source_flags_t +#include "libtorrent/download_priority.hpp" +#include "libtorrent/pex_flags.hpp" +#include "libtorrent/client_data.hpp" +#include "libtorrent/address.hpp" // for address_v4 and address_v6 + +namespace libtorrent { +namespace aux { + struct session_impl; +} + +#if TORRENT_ABI_VERSION == 1 + struct peer_list_entry; +#endif + struct torrent; + struct client_data_t; + +#ifndef BOOST_NO_EXCEPTIONS + [[noreturn]] void throw_invalid_handle(); +#endif + + using status_flags_t = flags::bitfield_flag; + using add_piece_flags_t = flags::bitfield_flag; + using pause_flags_t = flags::bitfield_flag; + using deadline_flags_t = flags::bitfield_flag; + using resume_data_flags_t = flags::bitfield_flag; + using reannounce_flags_t = flags::bitfield_flag; + using queue_position_t = aux::strong_typedef; + using file_progress_flags_t = flags::bitfield_flag; + + // holds the state of a block in a piece. Who we requested + // it from and how far along we are at downloading it. + struct TORRENT_EXPORT block_info + { + // this is the enum used for the block_info::state field. + enum block_state_t + { + // This block has not been downloaded or requested form any peer. + none, + // The block has been requested, but not completely downloaded yet. + requested, + // The block has been downloaded and is currently queued for being + // written to disk. + writing, + // The block has been written to disk. + finished + }; + + private: + union addr_t + { + address_v4::bytes_type v4; + address_v6::bytes_type v6; + }; + addr_t addr; + + std::uint16_t port; + public: + + // The peer is the ip address of the peer this block was downloaded from. + void set_peer(tcp::endpoint const& ep); + tcp::endpoint peer() const; + + // the number of bytes that have been received for this block + unsigned bytes_progress:15; + + // the total number of bytes in this block. + unsigned block_size:15; + + // the state this block is in (see block_state_t) + unsigned state:2; + + // the number of peers that is currently requesting this block. Typically + // this is 0 or 1, but at the end of the torrent blocks may be requested + // by more peers in parallel to speed things up. + unsigned num_peers:14; + private: + // the type of the addr union + bool is_v6_addr:1; + }; + + // This class holds information about pieces that have outstanding requests + // or outstanding writes + struct TORRENT_EXPORT partial_piece_info + { +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_push.hpp" + partial_piece_info() = default; + partial_piece_info(partial_piece_info&&) noexcept = default; + partial_piece_info(partial_piece_info const&) = default; + partial_piece_info& operator=(partial_piece_info const&) & = default; + partial_piece_info& operator=(partial_piece_info&&) & noexcept = default; +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + // the index of the piece in question. ``blocks_in_piece`` is the number + // of blocks in this particular piece. This number will be the same for + // most pieces, but + // the last piece may have fewer blocks than the standard pieces. + piece_index_t piece_index; + + // the number of blocks in this piece + int blocks_in_piece; + + // the number of blocks that are in the finished state + int finished; + + // the number of blocks that are in the writing state + int writing; + + // the number of blocks that are in the requested state + int requested; + + // this is an array of ``blocks_in_piece`` number of + // items. One for each block in the piece. + // + // .. warning:: This is a pointer that points to an array + // that's owned by the session object. The next time + // get_download_queue() is called, it will be invalidated. + // In the case of piece_info_alert, these pointers point into the alert + // object itself, and will be invalidated when the alert destruct. + block_info const* blocks; + +#if TORRENT_ABI_VERSION == 1 + // the speed classes. These may be used by the piece picker to + // coalesce requests of similar download rates + enum state_t { none, slow, medium, fast }; + + // the download speed class this piece falls into. + // this is used internally to cluster peers of the same + // speed class together when requesting blocks. + // + // set to either ``fast``, ``medium``, ``slow`` or ``none``. It tells + // which download rate category the peers downloading this piece falls + // into. ``none`` means that no peer is currently downloading any part of + // the piece. Peers prefer picking pieces from the same category as + // themselves. The reason for this is to keep the number of partially + // downloaded pieces down. Pieces set to ``none`` can be converted into + // any of ``fast``, ``medium`` or ``slow`` as soon as a peer want to + // download from it. + TORRENT_DEPRECATED state_t piece_state; +#endif + }; + + // for std::hash (and to support using this type in unordered_map etc.) + TORRENT_EXPORT std::size_t hash_value(torrent_handle const& h); + + // You will usually have to store your torrent handles somewhere, since it's + // the object through which you retrieve information about the torrent and + // aborts the torrent. + // + // .. warning:: + // Any member function that returns a value or fills in a value has to be + // made synchronously. This means it has to wait for the main thread to + // complete the query before it can return. This might potentially be + // expensive if done from within a GUI thread that needs to stay + // responsive. Try to avoid querying for information you don't need, and + // try to do it in as few calls as possible. You can get most of the + // interesting information about a torrent from the + // torrent_handle::status() call. + // + // The default constructor will initialize the handle to an invalid state. + // Which means you cannot perform any operation on it, unless you first + // assign it a valid handle. If you try to perform any operation on an + // uninitialized handle, it will throw ``invalid_handle``. + // + // .. warning:: + // All operations on a torrent_handle may throw system_error + // exception, in case the handle is no longer referring to a torrent. + // There is one exception is_valid() will never throw. Since the torrents + // are processed by a background thread, there is no guarantee that a + // handle will remain valid between two calls. + // + struct TORRENT_EXPORT torrent_handle + { + friend struct aux::session_impl; + friend struct session_handle; + friend struct torrent; + TORRENT_EXPORT friend std::size_t hash_value(torrent_handle const& th); + + // constructs a torrent handle that does not refer to a torrent. + // i.e. is_valid() will return false. + torrent_handle() noexcept = default; + + // hidden + torrent_handle(torrent_handle const& t) = default; + torrent_handle(torrent_handle&& t) noexcept = default; + torrent_handle& operator=(torrent_handle const&) & = default; + torrent_handle& operator=(torrent_handle&&) & noexcept = default; + + +#if TORRENT_ABI_VERSION == 1 + using flags_t = add_piece_flags_t; + using status_flags_t = libtorrent::status_flags_t; + using pause_flags_t = libtorrent::pause_flags_t; + using save_resume_flags_t = libtorrent::resume_data_flags_t; + using reannounce_flags_t = libtorrent::reannounce_flags_t; +#endif + + // instruct libtorrent to overwrite any data that may already have been + // downloaded with the data of the new piece being added. Using this + // flag when adding a piece that is actively being downloaded from other + // peers may have some unexpected consequences, as blocks currently + // being downloaded from peers may not be replaced. + static constexpr add_piece_flags_t overwrite_existing = 0_bit; + + // This function will write ``data`` to the storage as piece ``piece``, + // as if it had been downloaded from a peer. + // + // By default, data that's already been downloaded is not overwritten by + // this buffer. If you trust this data to be correct (and pass the piece + // hash check) you may pass the overwrite_existing flag. This will + // instruct libtorrent to overwrite any data that may already have been + // downloaded with this data. + // + // Since the data is written asynchronously, you may know that is passed + // or failed the hash check by waiting for piece_finished_alert or + // hash_failed_alert. + // + // Adding pieces while the torrent is being checked (i.e. in + // torrent_status::checking_files state) is not supported. + // + // The overload taking a raw pointer to the data is a blocking call. It + // won't return until the libtorrent thread has copied the data into its + // disk write buffer. ``data`` is expected to point to a buffer of as + // many bytes as the size of the specified piece. See + // file_storage::piece_size(). + // + // The data in the buffer is copied and passed on to the disk IO thread + // to be written at a later point. + // + // The overload taking a ``std::vector`` is not blocking, it will + // send the buffer to the main thread and return immediately. + void add_piece(piece_index_t piece, char const* data, add_piece_flags_t flags = {}) const; + void add_piece(piece_index_t piece, std::vector data, add_piece_flags_t flags = {}) const; + + // This function starts an asynchronous read operation of the specified + // piece from this torrent. You must have completed the download of the + // specified piece before calling this function. + // + // When the read operation is completed, it is passed back through an + // alert, read_piece_alert. Since this alert is a response to an explicit + // call, it will always be posted, regardless of the alert mask. + // + // Note that if you read multiple pieces, the read operations are not + // guaranteed to finish in the same order as you initiated them. + void read_piece(piece_index_t piece) const; + + // Returns true if this piece has been completely downloaded and written + // to disk, and false otherwise. + bool have_piece(piece_index_t piece) const; + +#if TORRENT_ABI_VERSION == 1 + // internal + TORRENT_DEPRECATED + void get_full_peer_list(std::vector& v) const; +#endif + + // Query information about connected peers for this torrent. If the + // torrent_handle is invalid, it will throw a system_error exception. + // + // ``post_peer_info()`` is asynchronous and will trigger the posting of + // a peer_info_alert. The alert contain a list of peer_info objects, one + // for each connected peer. + // + // ``get_peer_info()`` is synchronous and takes a reference to a vector + // that will be cleared and filled with one entry for each peer + // connected to this torrent, given the handle is valid. Each entry in + // the vector contains information about that particular peer. See + // peer_info. + void post_peer_info() const; + void get_peer_info(std::vector& v) const; + + // calculates ``distributed_copies``, ``distributed_full_copies`` and + // ``distributed_fraction``. + static constexpr status_flags_t query_distributed_copies = 0_bit; + + // includes partial downloaded blocks in ``total_done`` and + // ``total_wanted_done``. + static constexpr status_flags_t query_accurate_download_counters = 1_bit; + + // includes ``last_seen_complete``. + static constexpr status_flags_t query_last_seen_complete = 2_bit; + // populate the ``pieces`` field in torrent_status. + static constexpr status_flags_t query_pieces = 3_bit; + // includes ``verified_pieces`` (only applies to torrents in *seed + // mode*). + static constexpr status_flags_t query_verified_pieces = 4_bit; + // includes ``torrent_file``, which is all the static information from + // the .torrent file. + static constexpr status_flags_t query_torrent_file = 5_bit; + // includes ``name``, the name of the torrent. This is either derived + // from the .torrent file, or from the ``&dn=`` magnet link argument + // or possibly some other source. If the name of the torrent is not + // known, this is an empty string. + static constexpr status_flags_t query_name = 6_bit; + // includes ``save_path``, the path to the directory the files of the + // torrent are saved to. + static constexpr status_flags_t query_save_path = 7_bit; + + // ``status()`` will return a structure with information about the status + // of this torrent. If the torrent_handle is invalid, it will throw + // system_error exception. See torrent_status. The ``flags`` + // argument filters what information is returned in the torrent_status. + // Some information in there is relatively expensive to calculate, and if + // you're not interested in it (and see performance issues), you can + // filter them out. + // + // The ``status()`` function will block until the internal libtorrent + // thread responds with the torrent_status object. To avoid blocking, + // instead call ``post_status()``. It will trigger posting of a + // state_update_alert with a single torrent_status object for this + // torrent. + // + // In order to get regular updates for torrents whose status changes, + // consider calling session::post_torrent_updates()`` instead. + // + // By default everything is included. The flags you can use to decide + // what to *include* are defined in this class. + torrent_status status(status_flags_t flags = status_flags_t::all()) const; + void post_status(status_flags_t flags = status_flags_t::all()) const; + + // ``post_download_queue()`` triggers a download_queue_alert to be + // posted. + // ``get_download_queue()`` is a synchronous call and returns a vector + // with information about pieces that are partially downloaded or not + // downloaded but partially requested. See partial_piece_info for the + // fields in the returned vector. + void post_download_queue() const; + std::vector get_download_queue() const; + void get_download_queue(std::vector& queue) const; + + // used to ask libtorrent to send an alert once the piece has been + // downloaded, by passing alert_when_available. When set, the + // read_piece_alert alert will be delivered, with the piece data, when + // it's downloaded. + static constexpr deadline_flags_t alert_when_available = 0_bit; + + // This function sets or resets the deadline associated with a specific + // piece index (``index``). libtorrent will attempt to download this + // entire piece before the deadline expires. This is not necessarily + // possible, but pieces with a more recent deadline will always be + // prioritized over pieces with a deadline further ahead in time. The + // deadline (and flags) of a piece can be changed by calling this + // function again. + // + // If the piece is already downloaded when this call is made, nothing + // happens, unless the alert_when_available flag is set, in which case it + // will have the same effect as calling read_piece() for ``index``. + // + // ``deadline`` is the number of milliseconds until this piece should be + // completed. + // + // ``reset_piece_deadline`` removes the deadline from the piece. If it + // hasn't already been downloaded, it will no longer be considered a + // priority. + // + // ``clear_piece_deadlines()`` removes deadlines on all pieces in + // the torrent. As if reset_piece_deadline() was called on all pieces. + void set_piece_deadline(piece_index_t index, int deadline, deadline_flags_t flags = {}) const; + void reset_piece_deadline(piece_index_t index) const; + void clear_piece_deadlines() const; + +#if TORRENT_ABI_VERSION == 1 + // This sets the bandwidth priority of this torrent. The priority of a + // torrent determines how much bandwidth its peers are assigned when + // distributing upload and download rate quotas. A high number gives more + // bandwidth. The priority must be within the range [0, 255]. + // + // The default priority is 0, which is the lowest priority. + // + // To query the priority of a torrent, use the + // ``torrent_handle::status()`` call. + // + // Torrents with higher priority will not necessarily get as much + // bandwidth as they can consume, even if there's is more quota. Other + // peers will still be weighed in when bandwidth is being distributed. + // With other words, bandwidth is not distributed strictly in order of + // priority, but the priority is used as a weight. + // + // Peers whose Torrent has a higher priority will take precedence when + // distributing unchoke slots. This is a strict prioritisation where + // every interested peer on a high priority torrent will be unchoked + // before any other, lower priority, torrents have any peers unchoked. + // deprecated in 1.2 + TORRENT_DEPRECATED + void set_priority(int prio) const; + +#if !TORRENT_NO_FPU + // fills the specified vector with the download progress [0, 1] + // of each file in the torrent. The files are ordered as in + // the torrent_info. + TORRENT_DEPRECATED + void file_progress(std::vector& progress) const; +#endif + + TORRENT_DEPRECATED + void file_status(std::vector& status) const; +#endif + +#if TORRENT_ABI_VERSION <= 2 + using file_progress_flags_t = libtorrent::file_progress_flags_t; +#endif + // only calculate file progress at piece granularity. This makes + // the file_progress() call cheaper and also only takes bytes that + // have passed the hash check into account, so progress cannot + // regress in this mode. + static constexpr file_progress_flags_t piece_granularity = 0_bit; + + // This function fills in the supplied vector, or returns a vector, with + // the number of bytes downloaded of each file in this torrent. The + // progress values are ordered the same as the files in the + // torrent_info. + // + // This operation is not very cheap. Its complexity is *O(n + mj)*. + // Where *n* is the number of files, *m* is the number of currently + // downloading pieces and *j* is the number of blocks in a piece. + // + // The ``flags`` parameter can be used to specify the granularity of the + // file progress. If left at the default value of 0, the progress will be + // as accurate as possible, but also more expensive to calculate. If + // ``torrent_handle::piece_granularity`` is specified, the progress will + // be specified in piece granularity. i.e. only pieces that have been + // fully downloaded and passed the hash check count. When specifying + // piece granularity, the operation is a lot cheaper, since libtorrent + // already keeps track of this internally and no calculation is required. + void file_progress(std::vector& progress, file_progress_flags_t flags = {}) const; + std::vector file_progress(file_progress_flags_t flags = {}) const; + void post_file_progress(file_progress_flags_t flags) const; + + // This function returns a vector with status about files + // that are open for this torrent. Any file that is not open + // will not be reported in the vector, i.e. it's possible that + // the vector is empty when returning, if none of the files in the + // torrent are currently open. + // + // See open_file_state + std::vector file_status() const; + + // If the torrent is in an error state (i.e. ``torrent_status::error`` is + // non-empty), this will clear the error and start the torrent again. + void clear_error() const; + + // ``trackers()`` returns the list of trackers for this torrent. The + // announce entry contains both a string ``url`` which specify the + // announce url for the tracker as well as an int ``tier``, which is + // specifies the order in which this tracker is tried. If you want + // libtorrent to use another list of trackers for this torrent, you can + // use ``replace_trackers()`` which takes a list of the same form as the + // one returned from ``trackers()`` and will replace it. If you want an + // immediate effect, you have to call force_reannounce(). See + // announce_entry. + // + // ``post_trackers()`` is the asynchronous version of ``trackers()``. It + // will trigger a tracker_list_alert to be posted. + // + // ``add_tracker()`` will look if the specified tracker is already in the + // set. If it is, it doesn't do anything. If it's not in the current set + // of trackers, it will insert it in the tier specified in the + // announce_entry. + // + // The updated set of trackers will be saved in the resume data, and when + // a torrent is started with resume data, the trackers from the resume + // data will replace the original ones. + std::vector trackers() const; + void replace_trackers(std::vector const&) const; + void add_tracker(announce_entry const&) const; + void post_trackers() const; + + // TODO: 3 unify url_seed and http_seed with just web_seed, using the + // web_seed_entry. + + // ``add_url_seed()`` adds another url to the torrent's list of url + // seeds. If the given url already exists in that list, the call has no + // effect. The torrent will connect to the server and try to download + // pieces from it, unless it's paused, queued, checking or seeding. + // ``remove_url_seed()`` removes the given url if it exists already. + // ``url_seeds()`` return a set of the url seeds currently in this + // torrent. Note that URLs that fails may be removed automatically from + // the list. + // + // See http-seeding_ for more information. + void add_url_seed(std::string const& url) const; + void remove_url_seed(std::string const& url) const; + std::set url_seeds() const; + + // These functions are identical as the ``*_url_seed()`` variants, but + // they operate on `BEP 17`_ web seeds instead of `BEP 19`_. + // + // See http-seeding_ for more information. + void add_http_seed(std::string const& url) const; + void remove_http_seed(std::string const& url) const; + std::set http_seeds() const; + + // add the specified extension to this torrent. The ``ext`` argument is + // a function that will be called from within libtorrent's context + // passing in the internal torrent object and the specified userdata + // pointer. The function is expected to return a shared pointer to + // a torrent_plugin instance. + void add_extension( + std::function(torrent_handle const&, client_data_t)> const& ext + , client_data_t userdata = client_data_t{}); + + // ``set_metadata`` expects the *info* section of metadata. i.e. The + // buffer passed in will be hashed and verified against the info-hash. If + // it fails, a ``metadata_failed_alert`` will be generated. If it passes, + // a ``metadata_received_alert`` is generated. The function returns true + // if the metadata is successfully set on the torrent, and false + // otherwise. If the torrent already has metadata, this function will not + // affect the torrent, and false will be returned. + bool set_metadata(span metadata) const; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + bool set_metadata(char const* metadata, int size) const + { return set_metadata({metadata, size}); } +#endif + + // Returns true if this handle refers to a valid torrent and false if it + // hasn't been initialized or if the torrent it refers to has been + // removed from the session AND destructed. + // + // To tell if the torrent_handle is in the session, use + // torrent_handle::in_session(). This will return true before + // session_handle::remove_torrent() is called, and false + // afterward. + // + // Clients should only use is_valid() to determine if the result of + // session::find_torrent() was successful. + // + // Unlike other member functions which return a value, is_valid() + // completes immediately, without blocking on a result from the + // network thread. Also unlike other functions, it never throws + // the system_error exception. + bool is_valid() const; + + // will delay the disconnect of peers that we're still downloading + // outstanding requests from. The torrent will not accept any more + // requests and will disconnect all idle peers. As soon as a peer is done + // transferring the blocks that were requested from it, it is + // disconnected. This is a graceful shut down of the torrent in the sense + // that no downloaded bytes are wasted. + static constexpr pause_flags_t graceful_pause = 0_bit; + + // hidden + TORRENT_DEPRECATED static constexpr pause_flags_t clear_disk_cache = 1_bit; + + // ``pause()``, and ``resume()`` will disconnect all peers and reconnect + // all peers respectively. When a torrent is paused, it will however + // remember all share ratios to all peers and remember all potential (not + // connected) peers. Torrents may be paused automatically if there is a + // file error (e.g. disk full) or something similar. See + // file_error_alert. + // + // For possible values of the ``flags`` parameter, see pause_flags_t. + // + // To know if a torrent is paused or not, call + // ``torrent_handle::flags()`` and check for the + // ``torrent_status::paused`` flag. + // + // .. note:: + // Torrents that are auto-managed may be automatically resumed again. It + // does not make sense to pause an auto-managed torrent without making it + // not auto-managed first. Torrents are auto-managed by default when added + // to the session. For more information, see queuing_. + // + void pause(pause_flags_t flags = {}) const; + void resume() const; + + // sets and gets the torrent state flags. See torrent_flags_t. + // The ``set_flags`` overload that take a mask will affect all + // flags part of the mask, and set their values to what the + // ``flags`` argument is set to. This allows clearing and + // setting flags in a single function call. + // The ``set_flags`` overload that just takes flags, sets all + // the specified flags and leave any other flags unchanged. + // ``unset_flags`` clears the specified flags, while leaving + // any other flags unchanged. + // + // The `seed_mode` flag is special, it can only be cleared once the + // torrent has been added, and it can only be set as part of the + // add_torrent_params flags, when adding the torrent. + torrent_flags_t flags() const; + void set_flags(torrent_flags_t flags, torrent_flags_t mask) const; + void set_flags(torrent_flags_t flags) const; + void unset_flags(torrent_flags_t flags) const; + + // Instructs libtorrent to flush all the disk caches for this torrent and + // close all file handles. This is done asynchronously and you will be + // notified that it's complete through cache_flushed_alert. + // + // Note that by the time you get the alert, libtorrent may have cached + // more data for the torrent, but you are guaranteed that whatever cached + // data libtorrent had by the time you called + // ``torrent_handle::flush_cache()`` has been written to disk. + void flush_cache() const; + + // ``force_recheck`` puts the torrent back in a state where it assumes to + // have no resume data. All peers will be disconnected and the torrent + // will stop announcing to the tracker. The torrent will be added to the + // checking queue, and will be checked (all the files will be read and + // compared to the piece hashes). Once the check is complete, the torrent + // will start connecting to peers again, as normal. + // The torrent will be placed last in queue, i.e. its queue position + // will be the highest of all torrents in the session. + void force_recheck() const; + + // the disk cache will be flushed before creating the resume data. + // This avoids a problem with file timestamps in the resume data in + // case the cache hasn't been flushed yet. + static constexpr resume_data_flags_t flush_disk_cache = 0_bit; + + // the resume data will contain the metadata from the torrent file as + // well. This is useful for clients that don't keep .torrent files + // around separately, or for torrents that were added via a magnet link. + static constexpr resume_data_flags_t save_info_dict = 1_bit; + + // this flag has the same behavior as the combination of: + // if_counters_changed | if_download_progress | if_config_changed | + // if_state_changed | if_metadata_changed + static constexpr resume_data_flags_t only_if_modified = 2_bit; + + // save resume data if any counters has changed since the last time + // resume data was saved. This includes upload/download counters, active + // time counters and scrape data. A torrent that is not paused will have + // its active time counters incremented continuously. + static constexpr resume_data_flags_t if_counters_changed = 3_bit; + + // save the resume data if any blocks have been downloaded since the + // last time resume data was saved. This includes: + // * checking existing files on disk + // * downloading a block from a peer + static constexpr resume_data_flags_t if_download_progress = 4_bit; + + // save the resume data if configuration options changed since last time + // the resume data was saved. This includes: + // * file- or piece priorities + // * upload- and download rate limits + // * change max-uploads (unchoke slots) + // * change max connection limit + // * enable/disable peer-exchange, local service discovery or DHT + // * enable/disable apply IP-filter + // * enable/disable auto-managed + // * enable/disable share-mode + // * enable/disable sequential-mode + // * files renamed + // * storage moved (save_path changed) + static constexpr resume_data_flags_t if_config_changed = 5_bit; + + // save the resume data if torrent state has changed since last time the + // resume data was saved. This includes: + // * upload mode + // * paused state + // * super-seeding + // * seed-mode + static constexpr resume_data_flags_t if_state_changed = 6_bit; + + // save the resume data if any *metadata* changed since the last time + // resume data was saved. This includes: + // * add/remove web seeds + // * add/remove trackers + // * receiving metadata for a magnet link + static constexpr resume_data_flags_t if_metadata_changed = 7_bit; + + // ``save_resume_data()`` asks libtorrent to generate fast-resume data for + // this torrent. The fast resume data (stored in an add_torrent_params + // object) can be used to resume a torrent in the next session without + // having to check all files for which pieces have been downloaded. It + // can also be used to save a .torrent file for a torrent_handle. + // + // This operation is asynchronous, ``save_resume_data`` will return + // immediately. The resume data is delivered when it's done through a + // save_resume_data_alert. + // + // The operation will fail, and post a save_resume_data_failed_alert + // instead, in the following cases: + // + // 1. The torrent is in the process of being removed. + // 2. No torrent state has changed since the last saving of resume + // data, and the only_if_modified flag is set. + // metadata (see libtorrent's metadata-from-peers_ extension) + // + // Note that some counters may be outdated by the time you receive the fast resume data + // + // When saving resume data because of shutting down, make sure not to + // remove_torrent() before you receive the save_resume_data_alert. + // There's no need to pause the session or torrent when saving resume + // data. + // + // The paused state of a torrent is saved in the resume data, so pausing + // all torrents before saving resume data will all torrents be restored + // in a paused state. + // + //.. note:: + // It is typically a good idea to save resume data whenever a torrent + // is completed or paused. If you save resume data for torrents when they are + // paused, you can accelerate the shutdown process by not saving resume + // data again for those torrents. Completed torrents should have their + // resume data saved when they complete and on exit, since their + // statistics might be updated. + // + // Example code to pause and save resume data for all torrents and wait + // for the alerts: + // + // .. code:: c++ + // + // extern int outstanding_resume_data; // global counter of outstanding resume data + // std::vector handles = ses.get_torrents(); + // for (torrent_handle const& h : handles) try + // { + // h.save_resume_data(torrent_handle::only_if_modified); + // ++outstanding_resume_data; + // } + // catch (lt::system_error const& e) + // { + // // the handle was invalid, ignore this one and move to the next + // } + // + // while (outstanding_resume_data > 0) + // { + // alert const* a = ses.wait_for_alert(seconds(30)); + // + // // if we don't get an alert within 30 seconds, abort + // if (a == nullptr) break; + // + // std::vector alerts; + // ses.pop_alerts(&alerts); + // + // for (alert* i : alerts) + // { + // if (alert_cast(i)) + // { + // process_alert(i); + // --outstanding_resume_data; + // continue; + // } + // + // save_resume_data_alert const* rd = alert_cast(i); + // if (rd == nullptr) + // { + // process_alert(i); + // continue; + // } + // + // std::ofstream out((rd->params.save_path + // + "/" + rd->params.name + ".fastresume").c_str() + // , std::ios_base::binary); + // std::vector buf = write_resume_data_buf(rd->params); + // out.write(buf.data(), buf.size()); + // --outstanding_resume_data; + // } + // } + // + //.. note:: + // Note how ``outstanding_resume_data`` is a global counter in this + // example. This is deliberate, otherwise there is a race condition for + // torrents that was just asked to save their resume data, they posted + // the alert, but it has not been received yet. Those torrents would + // report that they don't need to save resume data again, and skipped by + // the initial loop, and thwart the counter otherwise. + void save_resume_data(resume_data_flags_t flags = {}) const; + + // This function returns true if anything that is stored in the resume + // data has changed since the last time resume data was saved. + // The overload that takes ``flags`` let you ask if specific categories + // of properties have changed. These flags have the same behavior as in + // the save_resume_data() call. + // + // This is a *blocking* call. It will wait for a response from + // libtorrent's main thread. A way to avoid blocking is to instead + // call save_resume_data() directly, specifying the conditions under + // which resume data should be saved. + // + //.. note:: + // A torrent's resume data is considered saved as soon as the + // save_resume_data_alert is posted. It is important to make sure this + // alert is received and handled in order for this function to be + // meaningful. + bool need_save_resume_data() const; + bool need_save_resume_data(resume_data_flags_t flags) const; + + // Every torrent that is added is assigned a queue position exactly one + // greater than the greatest queue position of all existing torrents. + // Torrents that are being seeded have -1 as their queue position, since + // they're no longer in line to be downloaded. + // + // When a torrent is removed or turns into a seed, all torrents with + // greater queue positions have their positions decreased to fill in the + // space in the sequence. + // + // ``queue_position()`` returns the torrent's position in the download + // queue. The torrents with the smallest numbers are the ones that are + // being downloaded. The smaller number, the closer the torrent is to the + // front of the line to be started. + // + // The queue position is also available in the torrent_status. + // + // The ``queue_position_*()`` functions adjust the torrents position in + // the queue. Up means closer to the front and down means closer to the + // back of the queue. Top and bottom refers to the front and the back of + // the queue respectively. + queue_position_t queue_position() const; + void queue_position_up() const; + void queue_position_down() const; + void queue_position_top() const; + void queue_position_bottom() const; + + // updates the position in the queue for this torrent. The relative order + // of all other torrents remain intact but their numerical queue position + // shifts to make space for this torrent's new position + void queue_position_set(queue_position_t p) const; + + // For SSL torrents, use this to specify a path to a .pem file to use as + // this client's certificate. The certificate must be signed by the + // certificate in the .torrent file to be valid. + // + // The set_ssl_certificate_buffer() overload takes the actual certificate, + // private key and DH params as strings, rather than paths to files. + // + // ``cert`` is a path to the (signed) certificate in .pem format + // corresponding to this torrent. + // + // ``private_key`` is a path to the private key for the specified + // certificate. This must be in .pem format. + // + // ``dh_params`` is a path to the Diffie-Hellman parameter file, which + // needs to be in .pem format. You can generate this file using the + // openssl command like this: ``openssl dhparam -outform PEM -out + // dhparams.pem 512``. + // + // ``passphrase`` may be specified if the private key is encrypted and + // requires a passphrase to be decrypted. + // + // Note that when a torrent first starts up, and it needs a certificate, + // it will suspend connecting to any peers until it has one. It's + // typically desirable to resume the torrent after setting the SSL + // certificate. + // + // If you receive a torrent_need_cert_alert, you need to call this to + // provide a valid cert. If you don't have a cert you won't be allowed to + // connect to any peers. + void set_ssl_certificate(std::string const& certificate + , std::string const& private_key + , std::string const& dh_params + , std::string const& passphrase = ""); + void set_ssl_certificate_buffer(std::string const& certificate + , std::string const& private_key + , std::string const& dh_params); + + // torrent_file() returns a pointer to the torrent_info object + // associated with this torrent. The torrent_info object may be a copy + // of the internal object. If the torrent doesn't have metadata, the + // pointer will not be initialized (i.e. a nullptr). The torrent may be + // in a state without metadata only if it was started without a .torrent + // file, e.g. by being added by magnet link. + // + // Note that the torrent_info object returned here may be a different + // instance than the one added to the session, with different attributes + // like piece layers, dht nodes and trackers. A torrent_info object does + // not round-trip cleanly when added to a session. + // + // If you want to save a .torrent file from the torrent_handle, instead + // call save_resume_data() and write_torrent_file() the + // add_torrent_params object passed back in the alert. + // + // torrent_file_with_hashes() returns a *copy* of the internal + // torrent_info and piece layer hashes (if it's a v2 torrent). The piece + // layers will only be included if they are available. If this torrent + // was added from a .torrent file with piece layers or if it's seeding, + // the piece layers are available. This function is more expensive than + // torrent_file() since it needs to make copies of this information. + // + // The torrent_file_with_hashes() is here for backwards compatibility + // when constructing a create_torrent object from a torrent_info that's + // in a session. Prefer save_resume_data() + write_torrent_file(). + // + // Note that a torrent added from a magnet link may not have the full + // merkle trees for all files, and hence not have the complete piece + // layers. In that state, you cannot create a .torrent file even from + // the torrent_info returned from torrent_file_with_hashes(). Once the + // torrent completes downloading all files, becoming a seed, you can + // make a .torrent file from it. + std::shared_ptr torrent_file() const; + std::shared_ptr torrent_file_with_hashes() const; + + // returns the piece layers for all files in the torrent. If this is a + // v1 torrent (and doesn't have any piece layers) it returns an empty + // vector. This is a blocking call that will synchronize with the + // libtorrent network thread. + std::vector> piece_layers() const; + +#if TORRENT_ABI_VERSION == 1 + + // ================ start deprecation ============ + + // deprecated in 1.2 + // use set_flags() and unset_flags() instead + TORRENT_DEPRECATED + void stop_when_ready(bool b) const; + TORRENT_DEPRECATED + void set_upload_mode(bool b) const; + TORRENT_DEPRECATED + void set_share_mode(bool b) const; + TORRENT_DEPRECATED + void apply_ip_filter(bool b) const; + TORRENT_DEPRECATED + void auto_managed(bool m) const; + TORRENT_DEPRECATED + void set_pinned(bool p) const; + TORRENT_DEPRECATED + void set_sequential_download(bool sd) const; + + + // deprecated in 1.0 + // use status() instead (with query_save_path) + TORRENT_DEPRECATED + std::string save_path() const; + + // deprecated in 1.0 + // use status() instead (with query_name) + // returns the name of this torrent, in case it doesn't + // have metadata it returns the name assigned to it + // when it was added. + TORRENT_DEPRECATED + std::string name() const; + + // use torrent_file() instead + TORRENT_DEPRECATED + const torrent_info& get_torrent_info() const; + + // deprecated in 0.16, feature will be removed + TORRENT_DEPRECATED + int get_peer_upload_limit(tcp::endpoint ip) const; + TORRENT_DEPRECATED + int get_peer_download_limit(tcp::endpoint ip) const; + TORRENT_DEPRECATED + void set_peer_upload_limit(tcp::endpoint ip, int limit) const; + TORRENT_DEPRECATED + void set_peer_download_limit(tcp::endpoint ip, int limit) const; + + // deprecated in 0.16, feature will be removed + TORRENT_DEPRECATED + void set_ratio(float up_down_ratio) const; + + // deprecated in 0.16. use flags() instead + TORRENT_DEPRECATED + bool is_seed() const; + TORRENT_DEPRECATED + bool is_finished() const; + TORRENT_DEPRECATED + bool is_paused() const; + TORRENT_DEPRECATED + bool is_auto_managed() const; + TORRENT_DEPRECATED + bool is_sequential_download() const; + TORRENT_DEPRECATED + bool has_metadata() const; + TORRENT_DEPRECATED + bool super_seeding() const; + + // deprecated in 0.14 + // use save_resume_data() instead. It is async. and + // will return the resume data in an alert + TORRENT_DEPRECATED + entry write_resume_data() const; + + // ``use_interface()`` sets the network interface this torrent will use + // when it opens outgoing connections. By default, it uses the same + // interface as the session uses to listen on. The parameter must be a + // string containing one or more, comma separated, ip-address (either an + // IPv4 or IPv6 address). When specifying multiple interfaces, the + // torrent will round-robin which interface to use for each outgoing + // connection. This is useful for clients that are multi-homed. + TORRENT_DEPRECATED + void use_interface(const char* net_interface) const; + // ================ end deprecation ============ +#endif + + // The piece availability is the number of peers that we are connected + // that has advertised having a particular piece. This is the information + // that libtorrent uses in order to prefer picking rare pieces. + // + // ``post_piece_availability()`` will trigger a piece_availability_alert + // to be posted. + // + // ``piece_availability()`` fills the specified ``std::vector`` + // with the availability for each piece in this torrent. libtorrent does + // not keep track of availability for seeds, so if the torrent is + // seeding the availability for all pieces is reported as 0. + void post_piece_availability() const; + void piece_availability(std::vector& avail) const; + + // These functions are used to set and get the priority of individual + // pieces. By default all pieces have priority 4. That means that the + // random rarest first algorithm is effectively active for all pieces. + // You may however change the priority of individual pieces. There are 8 + // priority levels. 0 means not to download the piece at all. Otherwise, + // lower priority values means less likely to be picked. Piece priority + // takes precedence over piece availability. Every piece with priority 7 + // will be attempted to be picked before a priority 6 piece and so on. + // + // The default priority of pieces is 4. + // + // Piece priorities can not be changed for torrents that have not + // downloaded the metadata yet. Magnet links won't have metadata + // immediately. see the metadata_received_alert. + // + // ``piece_priority`` sets or gets the priority for an individual piece, + // specified by ``index``. + // + // ``prioritize_pieces`` takes a vector of integers, one integer per + // piece in the torrent. All the piece priorities will be updated with + // the priorities in the vector. + // The second overload of ``prioritize_pieces`` that takes a vector of pairs + // will update the priorities of only select pieces, and leave all other + // unaffected. Each pair is (piece, priority). That is, the first item is + // the piece index and the second item is the priority of that piece. + // Invalid entries, where the piece index or priority is out of range, are + // not allowed. + // + // ``get_piece_priorities`` returns a vector with one element for each piece + // in the torrent. Each element is the current priority of that piece. + // + // It's possible to cancel the effect of *file* priorities by setting the + // priorities for the affected pieces. Care has to be taken when mixing + // usage of file- and piece priorities. + void piece_priority(piece_index_t index, download_priority_t priority) const; + download_priority_t piece_priority(piece_index_t index) const; + void prioritize_pieces(std::vector const& pieces) const; + void prioritize_pieces(std::vector> const& pieces) const; + std::vector get_piece_priorities() const; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + void prioritize_pieces(std::vector const& pieces) const; + TORRENT_DEPRECATED + void prioritize_pieces(std::vector> const& pieces) const; + TORRENT_DEPRECATED + std::vector piece_priorities() const; +#endif + + // ``index`` must be in the range [0, number_of_files). + // + // ``file_priority()`` queries or sets the priority of file ``index``. + // + // ``prioritize_files()`` takes a vector that has at as many elements as + // there are files in the torrent. Each entry is the priority of that + // file. The function sets the priorities of all the pieces in the + // torrent based on the vector. + // + // ``get_file_priorities()`` returns a vector with the priorities of all + // files. + // + // The priority values are the same as for piece_priority(). See + // download_priority_t. + // + // Whenever a file priority is changed, all other piece priorities are + // reset to match the file priorities. In order to maintain special + // priorities for particular pieces, piece_priority() has to be called + // again for those pieces. + // + // You cannot set the file priorities on a torrent that does not yet have + // metadata or a torrent that is a seed. ``file_priority(int, int)`` and + // prioritize_files() are both no-ops for such torrents. + // + // Since changing file priorities may involve disk operations (of moving + // files in- and out of the part file), the internal accounting of file + // priorities happen asynchronously. i.e. setting file priorities and then + // immediately querying them may not yield the same priorities just set. + // To synchronize with the priorities taking effect, wait for the + // file_prio_alert. + // + // When combining file- and piece priorities, the resume file will record + // both. When loading the resume data, the file priorities will be applied + // first, then the piece priorities. + // + // Moving data from a file into the part file is currently not + // supported. If a file has its priority set to 0 *after* it has already + // been created, it will not be moved into the partfile. + void file_priority(file_index_t index, download_priority_t priority) const; + download_priority_t file_priority(file_index_t index) const; + void prioritize_files(std::vector const& files) const; + std::vector get_file_priorities() const; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + void prioritize_files(std::vector const& files) const; + TORRENT_DEPRECATED + std::vector file_priorities() const; +#endif + + // by default, force-reannounce will still honor the min-interval + // published by the tracker. If this flag is set, it will be ignored + // and the tracker is announced immediately. + static constexpr reannounce_flags_t ignore_min_interval = 0_bit; + + // ``force_reannounce()`` will force this torrent to do another tracker + // request, to receive new peers. The ``seconds`` argument specifies how + // many seconds from now to issue the tracker announces. + // + // If the tracker's ``min_interval`` has not passed since the last + // announce, the forced announce will be scheduled to happen immediately + // as the ``min_interval`` expires. This is to honor trackers minimum + // re-announce interval settings. + // + // The ``tracker_index`` argument specifies which tracker to re-announce. + // If set to -1 (which is the default), all trackers are re-announce. + // + // The ``flags`` argument can be used to affect the re-announce. See + // ignore_min_interval. + // + // ``force_dht_announce`` will announce the torrent to the DHT + // immediately. + // + // ``force_lsd_announce`` will announce the torrent on LSD + // immediately. + void force_reannounce(int seconds = 0, int idx = -1, reannounce_flags_t = {}) const; + void force_dht_announce() const; + void force_lsd_announce() const; + +#if TORRENT_ABI_VERSION == 1 + // forces a reannounce in the specified amount of time. + // This overrides the default announce interval, and no + // announce will take place until the given time has + // timed out. + TORRENT_DEPRECATED + void force_reannounce(boost::posix_time::time_duration) const; +#endif + + // ``scrape_tracker()`` will send a scrape request to a tracker. By + // default (``idx`` = -1) it will scrape the last working tracker. If + // ``idx`` is >= 0, the tracker with the specified index will scraped. + // + // A scrape request queries the tracker for statistics such as total + // number of incomplete peers, complete peers, number of downloads etc. + // + // This request will specifically update the ``num_complete`` and + // ``num_incomplete`` fields in the torrent_status struct once it + // completes. When it completes, it will generate a scrape_reply_alert. + // If it fails, it will generate a scrape_failed_alert. + void scrape_tracker(int idx = -1) const; + + // ``set_upload_limit`` will limit the upload bandwidth used by this + // particular torrent to the limit you set. It is given as the number of + // bytes per second the torrent is allowed to upload. + // ``set_download_limit`` works the same way but for download bandwidth + // instead of upload bandwidth. Note that setting a higher limit on a + // torrent then the global limit + // (``settings_pack::upload_rate_limit``) will not override the global + // rate limit. The torrent can never upload more than the global rate + // limit. + // + // ``upload_limit`` and ``download_limit`` will return the current limit + // setting, for upload and download, respectively. + // + // Local peers are not rate limited by default. see peer-classes_. + void set_upload_limit(int limit) const; + int upload_limit() const; + void set_download_limit(int limit) const; + int download_limit() const; + + // ``connect_peer()`` is a way to manually connect to peers that one + // believe is a part of the torrent. If the peer does not respond, or is + // not a member of this torrent, it will simply be disconnected. No harm + // can be done by using this other than an unnecessary connection attempt + // is made. If the torrent is uninitialized or in queued or checking + // mode, this will throw system_error. The second (optional) + // argument will be bitwise ORed into the source mask of this peer. + // Typically this is one of the source flags in peer_info. i.e. + // ``tracker``, ``pex``, ``dht`` etc. + // + // For possible values of ``flags``, see pex_flags_t. + void connect_peer(tcp::endpoint const& adr, peer_source_flags_t source = {} + , pex_flags_t flags = pex_encryption | pex_utp | pex_holepunch) const; + + // This will disconnect all peers and clear the peer list for this + // torrent. New peers will have to be acquired before resuming, from + // trackers, DHT or local service discovery, for example. + void clear_peers(); + + // ``set_max_uploads()`` sets the maximum number of peers that's unchoked + // at the same time on this torrent. If you set this to -1, there will be + // no limit. This defaults to infinite. The primary setting controlling + // this is the global unchoke slots limit, set by unchoke_slots_limit in + // settings_pack. + // + // ``max_uploads()`` returns the current settings. + void set_max_uploads(int max_uploads) const; + int max_uploads() const; + + // ``set_max_connections()`` sets the maximum number of connection this + // torrent will open. If all connections are used up, incoming + // connections may be refused or poor connections may be closed. This + // must be at least 2. The default is unlimited number of connections. If + // -1 is given to the function, it means unlimited. There is also a + // global limit of the number of connections, set by + // ``connections_limit`` in settings_pack. + // + // ``max_connections()`` returns the current settings. + void set_max_connections(int max_connections) const; + int max_connections() const; + +#if TORRENT_ABI_VERSION == 1 + // sets a username and password that will be sent along in the HTTP-request + // of the tracker announce. Set this if the tracker requires authorization. + TORRENT_DEPRECATED + void set_tracker_login(std::string const& name + , std::string const& password) const; +#endif + + // Moves the file(s) that this torrent are currently seeding from or + // downloading to. If the given ``save_path`` is not located on the same + // drive as the original save path, the files will be copied to the new + // drive and removed from their original location. This will block all + // other disk IO, and other torrents download and upload rates may drop + // while copying the file. + // + // Since disk IO is performed in a separate thread, this operation is + // also asynchronous. Once the operation completes, the + // ``storage_moved_alert`` is generated, with the new path as the + // message. If the move fails for some reason, + // ``storage_moved_failed_alert`` is generated instead, containing the + // error message. + // + // The ``flags`` argument determines the behavior of the copying/moving + // of the files in the torrent. see move_flags_t. + // + // ``always_replace_files`` is the default and replaces any file that + // exist in both the source directory and the target directory. + // + // ``fail_if_exist`` first check to see that none of the copy operations + // would cause an overwrite. If it would, it will fail. Otherwise it will + // proceed as if it was in ``always_replace_files`` mode. Note that there + // is an inherent race condition here. If the files in the target + // directory appear after the check but before the copy or move + // completes, they will be overwritten. When failing because of files + // already existing in the target path, the ``error`` of + // ``move_storage_failed_alert`` is set to + // ``boost::system::errc::file_exists``. + // + // The intention is that a client may use this as a probe, and if it + // fails, ask the user which mode to use. The client may then re-issue + // the ``move_storage`` call with one of the other modes. + // + // ``dont_replace`` always keeps the existing file in the target + // directory, if there is one. The source files will still be removed in + // that case. Note that it won't automatically re-check files. If an + // incomplete torrent is moved into a directory with the complete files, + // pause, move, force-recheck and resume. Without the re-checking, the + // torrent will keep downloading and files in the new download directory + // will be overwritten. + // + // Files that have been renamed to have absolute paths are not moved by + // this function. Keep in mind that files that don't belong to the + // torrent but are stored in the torrent's directory may be moved as + // well. This goes for files that have been renamed to absolute paths + // that still end up inside the save path. + // + // When copying files, sparse regions are not likely to be preserved. + // This makes it proportionally more expensive to move a large torrent + // when only few pieces have been downloaded, since the files are then + // allocated with zeros in the destination directory. + void move_storage(std::string const& save_path + , move_flags_t flags = move_flags_t::always_replace_files + ) const; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + TORRENT_DEPRECATED + void move_storage(std::string const& save_path, int flags) const; +#endif + + // Renames the file with the given index asynchronously. The rename + // operation is complete when either a file_renamed_alert or + // file_rename_failed_alert is posted. + void rename_file(file_index_t index, std::string const& new_name) const; + +#if TORRENT_ABI_VERSION == 1 + // Enables or disabled super seeding/initial seeding for this torrent. + // The torrent needs to be a seed for this to take effect. + TORRENT_DEPRECATED + void super_seeding(bool on) const; +#endif // TORRENT_ABI_VERSION + + // returns the info-hash(es) of the torrent. If this handle is to a + // torrent that hasn't loaded yet (for instance by being added) by a + // URL, the returned value is undefined. + // The ``info_hash()`` returns the SHA-1 info-hash for v1 torrents and a + // truncated hash for v2 torrents. For the full v2 info-hash, use + // ``info_hashes()`` instead. + sha1_hash info_hash() const; + info_hash_t info_hashes() const; + + // comparison operators. The order of the torrents is unspecified + // but stable. + bool operator==(const torrent_handle& h) const + { return !m_torrent.owner_before(h.m_torrent) && !h.m_torrent.owner_before(m_torrent); } + bool operator!=(const torrent_handle& h) const + { return m_torrent.owner_before(h.m_torrent) || h.m_torrent.owner_before(m_torrent); } + bool operator<(const torrent_handle& h) const + { return m_torrent.owner_before(h.m_torrent); } + + // returns a unique identifier for this torrent. It's not a dense index. + // It's not preserved across sessions. + std::uint32_t id() const + { + uintptr_t ret = reinterpret_cast(m_torrent.lock().get()); + // a torrent object is about 1024 (2^10) bytes, so + // it's safe to shift 10 bits + return std::uint32_t(ret >> 10); + } + + // This function is intended only for use by plugins and the alert + // dispatch function. This type does not have a stable ABI and should + // be relied on as little as possible. Accessing the handle returned by + // this function is not thread safe outside of libtorrent's internal + // thread (which is used to invoke plugin callbacks). + // The ``torrent`` class is not only eligible for changing ABI across + // minor versions of libtorrent, its layout is also dependent on build + // configuration. This adds additional requirements on a client to be + // built with the exact same build configuration as libtorrent itself. + // i.e. the ``TORRENT_`` macros must match between libtorrent and the + // client builds. + std::shared_ptr native_handle() const; + + // returns the userdata pointer as set in add_torrent_params + client_data_t userdata() const; + + // Returns true if the torrent is in the session. It returns true before + // session::remove_torrent() is called, and false afterward. + // + // Note that this is a blocking function, unlike torrent_handle::is_valid() + // which returns immediately. + bool in_session() const; + + private: + + template + void async_call(Fun f, Args&&... a) const; + + template + void sync_call(Fun f, Args&&... a) const; + + template + Ret sync_call_ret(Ret def, Fun f, Args&&... a) const; + + explicit torrent_handle(std::weak_ptr const& t) + { if (!t.expired()) m_torrent = t; } + + std::weak_ptr m_torrent; + }; +} + +namespace std { + template <> + struct hash + { + std::size_t operator()(libtorrent::torrent_handle const& th) const + { + return libtorrent::hash_value(th); + } + }; +} + +#endif // TORRENT_TORRENT_HANDLE_HPP_INCLUDED diff --git a/docs/include/libtorrent/torrent_info.hpp b/docs/include/libtorrent/torrent_info.hpp new file mode 100644 index 0000000..ff6facc --- /dev/null +++ b/docs/include/libtorrent/torrent_info.hpp @@ -0,0 +1,792 @@ +/* + +Copyright (c) 2003-2011, 2013-2022, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016-2018, 2020, Alden Torres +Copyright (c) 2016, Markus +Copyright (c) 2017, 2019, Andrei Kurushin +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2017-2019, Steven Siloti +Copyright (c) 2019, Amir Abrams +Copyright (c) 2020, Mike Tzou +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_INFO_HPP_INCLUDED +#define TORRENT_TORRENT_INFO_HPP_INCLUDED + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/copy_ptr.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/info_hash.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/index_range.hpp" +#include "libtorrent/aux_/merkle_tree.hpp" + +namespace libtorrent { + + struct invariant_access; + +namespace aux { + + // internal, exposed for the unit test + TORRENT_EXTRA_EXPORT void sanitize_append_path_element(std::string& path + , string_view element); + TORRENT_EXTRA_EXPORT bool verify_encoding(std::string& target); + + struct internal_drained_state + { + aux::vector urls; + std::vector web_seeds; + std::vector> nodes; + }; +} + + // the web_seed_entry holds information about a web seed (also known + // as URL seed or HTTP seed). It is essentially a URL with some state + // associated with it. For more information, see `BEP 17`_ and `BEP 19`_. + struct TORRENT_EXPORT web_seed_entry + { + // http seeds are different from url seeds in the + // protocol they use. http seeds follows the original + // http seed spec. by John Hoffman + enum type_t { url_seed, http_seed }; + + using headers_t = std::vector>; + + // hidden + web_seed_entry(std::string url_, type_t type_ + , std::string auth_ = std::string() + , headers_t extra_headers_ = headers_t()); + + // URL and type comparison + bool operator==(web_seed_entry const& e) const + { return type == e.type && url == e.url; } + + // URL and type less-than comparison + bool operator<(web_seed_entry const& e) const + { + if (url < e.url) return true; + if (url > e.url) return false; + return type < e.type; + } + + // The URL of the web seed + std::string url; + + // Optional authentication. If this is set, it's passed + // in as HTTP basic auth to the web seed. The format is: + // username:password. + std::string auth; + + // Any extra HTTP headers that need to be passed to the web seed + headers_t extra_headers; + + // The type of web seed (see type_t) + std::uint8_t type; + }; + + // hidden + class from_span_t {}; + + // used to disambiguate a bencoded buffer and a filename + extern TORRENT_EXPORT from_span_t from_span; + + // this object holds configuration options for limits to use when loading + // torrents. They are meant to prevent loading potentially malicious torrents + // that cause excessive memory allocations. + struct TORRENT_EXPORT load_torrent_limits + { + // the max size of a .torrent file to load into RAM + int max_buffer_size = 10000000; + + // the max number of pieces allowed in the torrent + int max_pieces = 0x200000; + + // the max recursion depth in the bdecoded structure + int max_decode_depth = 100; + + // the max number of bdecode tokens + int max_decode_tokens = 3000000; + }; + + using torrent_info_flags_t = flags::bitfield_flag; + +TORRENT_VERSION_NAMESPACE_3 + + // the torrent_info class holds the information found in a .torrent file. + class TORRENT_EXPORT torrent_info + { + public: + + // The constructor that takes an info-hash will initialize the info-hash + // to the given value, but leave all other fields empty. This is used + // internally when downloading torrents without the metadata. The + // metadata will be created by libtorrent as soon as it has been + // downloaded from the swarm. + // + // The constructor that takes a bdecode_node will create a torrent_info + // object from the information found in the given torrent_file. The + // bdecode_node represents a tree node in an bencoded file. To load an + // ordinary .torrent file into a bdecode_node, use bdecode(). + // + // The version that takes a buffer pointer and a size will decode it as a + // .torrent file and initialize the torrent_info object for you. + // + // The version that takes a filename will simply load the torrent file + // and decode it inside the constructor, for convenience. This might not + // be the most suitable for applications that want to be able to report + // detailed errors on what might go wrong. + // + // There is an upper limit on the size of the torrent file that will be + // loaded by the overload taking a filename. If it's important that even + // very large torrent files are loaded, use one of the other overloads. + // + // The overloads that takes an ``error_code const&`` never throws if an + // error occur, they will simply set the error code to describe what went + // wrong and not fully initialize the torrent_info object. The overloads + // that do not take the extra error_code parameter will always throw if + // an error occurs. These overloads are not available when building + // without exception support. + // + // The overload that takes a ``span`` also needs an extra parameter of + // type ``from_span_t`` to disambiguate the ``std::string`` overload for + // string literals. There is an object in the libtorrent namespace of this + // type called ``from_span``. +#ifndef BOOST_NO_EXCEPTIONS + explicit torrent_info(bdecode_node const& torrent_file); + torrent_info(char const* buffer, int size) + : torrent_info(span{buffer, size}, from_span) {} + explicit torrent_info(span buffer, from_span_t); + explicit torrent_info(std::string const& filename); + torrent_info(std::string const& filename, load_torrent_limits const& cfg); + torrent_info(span buffer, load_torrent_limits const& cfg, from_span_t); + torrent_info(bdecode_node const& torrent_file, load_torrent_limits const& cfg); +#endif // BOOST_NO_EXCEPTIONS + torrent_info(torrent_info const& t); + explicit torrent_info(info_hash_t const& info_hash); + torrent_info(bdecode_node const& torrent_file, error_code& ec); + torrent_info(char const* buffer, int size, error_code& ec) + : torrent_info(span{buffer, size}, ec, from_span) {} + torrent_info(span buffer, error_code& ec, from_span_t); + torrent_info(std::string const& filename, error_code& ec); + +#if TORRENT_ABI_VERSION == 1 +#ifndef BOOST_NO_EXCEPTIONS + TORRENT_DEPRECATED + torrent_info(char const* buffer, int size, int) + : torrent_info(span{buffer, size}, from_span) {} +#endif + TORRENT_DEPRECATED + torrent_info(bdecode_node const& torrent_file, error_code& ec, int) + : torrent_info(torrent_file, ec) {} + TORRENT_DEPRECATED + torrent_info(std::string const& filename, error_code& ec, int) + : torrent_info(filename, ec) {} + TORRENT_DEPRECATED + torrent_info(char const* buffer, int size, error_code& ec, int) + : torrent_info(span{buffer, size}, ec, from_span) {} +#endif // TORRENT_ABI_VERSION + + // frees all storage associated with this torrent_info object + ~torrent_info(); + + // hidden + torrent_info& operator=(torrent_info const&) = delete; + torrent_info& operator=(torrent_info&&); + + // The file_storage object contains the information on how to map the + // pieces to files. It is separated from the torrent_info object because + // when creating torrents a storage object needs to be created without + // having a torrent file. When renaming files in a storage, the storage + // needs to make its own copy of the file_storage in order to make its + // mapping differ from the one in the torrent file. + // + // ``orig_files()`` returns the original (unmodified) file storage for + // this torrent. This is used by the web server connection, which needs + // to request files with the original names. Filename may be changed using + // ``torrent_info::rename_file()``. + // + // For more information on the file_storage object, see the separate + // document on how to create torrents. + file_storage const& files() const { return m_files; } + file_storage const& orig_files() const; + + // Renames the file with the specified index to the new name. The new + // filename is reflected by the ``file_storage`` returned by ``files()`` + // but not by the one returned by ``orig_files()``. + // + // If you want to rename the base name of the torrent (for a multi file + // torrent), you can copy the ``file_storage`` (see files() and + // orig_files() ), change the name, and then use `remap_files()`_. + // + // The ``new_filename`` can both be a relative path, in which case the + // file name is relative to the ``save_path`` of the torrent. If the + // ``new_filename`` is an absolute path (i.e. ``is_complete(new_filename) + // == true``), then the file is detached from the ``save_path`` of the + // torrent. In this case the file is not moved when move_storage() is + // invoked. + void rename_file(file_index_t index, std::string const& new_filename); + + // .. warning:: + // Using `remap_files()` is discouraged as it's incompatible with v2 + // torrents. This is because the piece boundaries and piece hashes in + // v2 torrents are intimately tied to the file boundaries. Instead, + // just rename individual files, or implement a custom disk_interface + // to customize how to store files. + // + // Remaps the file storage to a new file layout. This can be used to, for + // instance, download all data in a torrent to a single file, or to a + // number of fixed size sector aligned files, regardless of the number + // and sizes of the files in the torrent. + // + // The new specified ``file_storage`` must have the exact same size as + // the current one. + void remap_files(file_storage const& f); + + // ``add_tracker()`` adds a tracker to the announce-list. The ``tier`` + // determines the order in which the trackers are to be tried. + // The ``trackers()`` function will return a sorted vector of + // announce_entry. Each announce entry contains a string, which is + // the tracker url, and a tier index. The tier index is the high-level + // priority. No matter which trackers that works or not, the ones with + // lower tier will always be tried before the one with higher tier + // number. For more information, see announce_entry. + // + // ``trackers()`` returns all entries from announce-list. + // + // ``clear_trackers()`` removes all trackers from announce-list. + void add_tracker(std::string const& url, int tier = 0); + void add_tracker(std::string const& url, int tier + , announce_entry::tracker_source source); + std::vector const& trackers() const { return m_urls; } + void clear_trackers(); + + // These two functions are related to `BEP 38`_ (mutable torrents). The + // vectors returned from these correspond to the "similar" and + // "collections" keys in the .torrent file. Both info-hashes and + // collections from within the info-dict and from outside of it are + // included. + std::vector similar_torrents() const; + std::vector collections() const; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 0.16. Use web_seeds() instead + TORRENT_DEPRECATED + std::vector url_seeds() const; + TORRENT_DEPRECATED + std::vector http_seeds() const; +#endif // TORRENT_ABI_VERSION + + // ``web_seeds()`` returns all url seeds and http seeds in the torrent. + // Each entry is a ``web_seed_entry`` and may refer to either a url seed + // or http seed. + // + // ``add_url_seed()`` and ``add_http_seed()`` adds one url to the list of + // url/http seeds. + // + // ``set_web_seeds()`` replaces all web seeds with the ones specified in + // the ``seeds`` vector. + // + // The ``extern_auth`` argument can be used for other authorization + // schemes than basic HTTP authorization. If set, it will override any + // username and password found in the URL itself. The string will be sent + // as the HTTP authorization header's value (without specifying "Basic"). + // + // The ``extra_headers`` argument defaults to an empty list, but can be + // used to insert custom HTTP headers in the requests to a specific web + // seed. + // + // See http-seeding_ for more information. + void add_url_seed(std::string const& url + , std::string const& ext_auth = std::string() + , web_seed_entry::headers_t const& ext_headers = web_seed_entry::headers_t()); + void add_http_seed(std::string const& url + , std::string const& extern_auth = std::string() + , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t()); + std::vector const& web_seeds() const { return m_web_seeds; } + void set_web_seeds(std::vector seeds); + + // internal + aux::internal_drained_state _internal_drain() { + return aux::internal_drained_state{std::move(m_urls), std::move(m_web_seeds), std::move(m_nodes)}; + } + + // ``total_size()`` returns the total number of bytes the torrent-file + // represents. Note that this is the number of pieces times the piece + // size (modulo the last piece possibly being smaller). With pad files, + // the total size will be larger than the sum of all (regular) file + // sizes. + std::int64_t total_size() const { return m_files.total_size(); } + + // ``piece_length()`` and ``num_pieces()`` returns the number of byte + // for each piece and the total number of pieces, respectively. The + // difference between ``piece_size()`` and ``piece_length()`` is that + // ``piece_size()`` takes the piece index as argument and gives you the + // exact size of that piece. It will always be the same as + // ``piece_length()`` except in the case of the last piece, which may be + // smaller. + int piece_length() const { return m_files.piece_length(); } + int num_pieces() const { return m_files.num_pieces(); } + + // returns the number of blocks there are in the typical piece. There + // may be fewer in the last piece) + int blocks_per_piece() const { return m_files.blocks_per_piece(); } + + // ``last_piece()`` returns the index to the last piece in the torrent and + // ``end_piece()`` returns the index to the one-past-end piece in the + // torrent + // ``piece_range()`` returns an implementation-defined type that can be + // used as the container in a range-for loop. Where the values are the + // indices of all pieces in the file_storage. + piece_index_t last_piece() const { return m_files.last_piece(); } + piece_index_t end_piece() const + { + TORRENT_ASSERT(m_files.num_pieces() > 0); + return m_files.end_piece(); + } + index_range piece_range() const + { return m_files.piece_range(); } + + // returns the info-hash of the torrent. For BitTorrent v2 support, use + // ``info_hashes()`` to get an object that may hold both a v1 and v2 + // info-hash + sha1_hash info_hash() const noexcept; + info_hash_t const& info_hashes() const { return m_info_hash; } + + // returns whether this torrent has v1 and/or v2 metadata, respectively. + // Hybrid torrents have both. These are shortcuts for + // info_hashes().has_v1() and info_hashes().has_v2() calls. + bool v1() const; + bool v2() const; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.0. Use the variants that take an index instead + // internal_file_entry is no longer exposed in the API + using file_iterator = file_storage::iterator; + using reverse_file_iterator = file_storage::reverse_iterator; + + // This class will need some explanation. First of all, to get a list of + // all files in the torrent, you can use ``begin_files()``, + // ``end_files()``, ``rbegin_files()`` and ``rend_files()``. These will + // give you standard vector iterators with the type + // ``internal_file_entry``, which is an internal type. + // + // You can resolve it into the public representation of a file + // (``file_entry``) using the ``file_storage::at`` function, which takes + // an index and an iterator. + TORRENT_DEPRECATED + file_iterator begin_files() const { return m_files.begin_deprecated(); } + TORRENT_DEPRECATED + file_iterator end_files() const { return m_files.end_deprecated(); } + reverse_file_iterator rbegin_files() const { return m_files.rbegin_deprecated(); } + TORRENT_DEPRECATED + reverse_file_iterator rend_files() const { return m_files.rend_deprecated(); } + + TORRENT_DEPRECATED + file_iterator file_at_offset(std::int64_t offset) const + { return m_files.file_at_offset_deprecated(offset); } + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + TORRENT_DEPRECATED + file_entry file_at(int index) const { return m_files.at_deprecated(index); } + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif // TORRENT_ABI_VERSION + + // If you need index-access to files you can use the ``num_files()`` along + // with the ``file_path()``, ``file_size()``-family of functions to access + // files using indices. + int num_files() const { return m_files.num_files(); } + + // This function will map a piece index, a byte offset within that piece + // and a size (in bytes) into the corresponding files with offsets where + // that data for that piece is supposed to be stored. See file_slice. + std::vector map_block(piece_index_t const piece + , std::int64_t offset, int size) const + { + TORRENT_ASSERT(is_loaded()); + return m_files.map_block(piece, offset, size); + } + + // This function will map a range in a specific file into a range in the + // torrent. The ``file_offset`` parameter is the offset in the file, + // given in bytes, where 0 is the start of the file. See peer_request. + // + // The input range is assumed to be valid within the torrent. + // ``file_offset`` + ``size`` is not allowed to be greater than the file + // size. ``file_index`` must refer to a valid file, i.e. it cannot be >= + // ``num_files()``. + peer_request map_file(file_index_t const file, std::int64_t offset, int size) const + { + TORRENT_ASSERT(is_loaded()); + return m_files.map_file(file, offset, size); + } + +#if TORRENT_ABI_VERSION == 1 +// ------- start deprecation ------- + // deprecated in 1.2 + void load(char const*, int, error_code&) {} + void unload() {} + + TORRENT_DEPRECATED + explicit torrent_info(entry const& torrent_file); +// ------- end deprecation ------- +#endif + + // Returns the SSL root certificate for the torrent, if it is an SSL + // torrent. Otherwise returns an empty string. The certificate is + // the public certificate in x509 format. + string_view ssl_cert() const; + + // returns true if this torrent_info object has a torrent loaded. + // This is primarily used to determine if a magnet link has had its + // metadata resolved yet or not. + bool is_valid() const { return m_files.is_valid(); } + + // returns true if this torrent is private. i.e., the client should not + // advertise itself on the trackerless network (the Kademlia DHT) for this torrent. + bool priv() const { return bool(m_flags & private_torrent); } + + // returns true if this is an i2p torrent. This is determined by whether + // or not it has a tracker whose URL domain name ends with ".i2p". i2p + // torrents disable the DHT and local peer discovery as well as talking + // to peers over anything other than the i2p network. + bool is_i2p() const { return bool(m_flags & i2p); } + + // internal + bool v2_piece_hashes_verified() const { return bool(m_flags & v2_has_piece_hashes); } + void set_piece_layers(aux::vector, file_index_t> pl); + + // returns the piece size of file with ``index``. This will be the same as piece_length(), + // except for the last piece, which may be shorter. + int piece_size(piece_index_t index) const { return m_files.piece_size(index); } + + // ``hash_for_piece()`` takes a piece-index and returns the 20-bytes + // sha1-hash for that piece and ``info_hash()`` returns the 20-bytes + // sha1-hash for the info-section of the torrent file. + // ``hash_for_piece_ptr()`` returns a pointer to the 20 byte sha1 digest + // for the piece. Note that the string is not 0-terminated. + sha1_hash hash_for_piece(piece_index_t index) const; + char const* hash_for_piece_ptr(piece_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= piece_index_t(0)); + TORRENT_ASSERT_PRECOND(index < m_files.end_piece()); + TORRENT_ASSERT(is_loaded()); + int const idx = static_cast(index); + TORRENT_ASSERT(m_piece_hashes > 0); + TORRENT_ASSERT(m_piece_hashes < m_info_section_size); + TORRENT_ASSERT(idx < int((m_info_section_size - m_piece_hashes) / 20)); + return &m_info_section[std::ptrdiff_t(m_piece_hashes) + idx * 20]; + } + + bool is_loaded() const { return m_files.num_files() > 0; } + +#if TORRENT_ABI_VERSION <= 2 + // support for BEP 30 merkle torrents has been removed + + // ``merkle_tree()`` returns a reference to the merkle tree for this + // torrent, if any. + // ``set_merkle_tree()`` moves the passed in merkle tree into the + // torrent_info object. i.e. ``h`` will not be identical after the call. + // You need to set the merkle tree for a torrent that you've just created + // (as a merkle torrent). The merkle tree is retrieved from the + // ``create_torrent::merkle_tree()`` function, and need to be saved + // separately from the torrent file itself. Once it's added to + // libtorrent, the merkle tree will be persisted in the resume data. + TORRENT_DEPRECATED + std::vector const& merkle_tree() const { return m_merkle_tree; } + TORRENT_DEPRECATED + void set_merkle_tree(std::vector& h) + { TORRENT_ASSERT(h.size() == m_merkle_tree.size() ); m_merkle_tree.swap(h); } +#endif + + // ``name()`` returns the name of the torrent. + // name contains UTF-8 encoded string. + const std::string& name() const { return m_files.name(); } + + // ``creation_date()`` returns the creation date of the torrent as time_t + // (`posix time`_). If there's no time stamp in the torrent file, 0 is + // returned. + // .. _`posix time`: http://www.opengroup.org/onlinepubs/009695399/functions/time.html + std::time_t creation_date() const + { return m_creation_date; } + + // ``creator()`` returns the creator string in the torrent. If there is + // no creator string it will return an empty string. + const std::string& creator() const + { return m_created_by; } + + // ``comment()`` returns the comment associated with the torrent. If + // there's no comment, it will return an empty string. + // comment contains UTF-8 encoded string. + const std::string& comment() const + { return m_comment; } + + // If this torrent contains any DHT nodes, they are put in this vector in + // their original form (host name and port number). + std::vector> const& nodes() const + { return m_nodes; } + + // This is used when creating torrent. Use this to add a known DHT node. + // It may be used, by the client, to bootstrap into the DHT network. + void add_node(std::pair const& node) + { m_nodes.push_back(node); } + + // populates the torrent_info by providing just the info-dict buffer. + // This is used when loading a torrent from a magnet link for instance, + // where we only have the info-dict. The bdecode_node ``e`` points to a + // parsed info-dictionary. ``ec`` returns an error code if something + // fails (typically if the info dictionary is malformed). + // The `max_pieces` parameter allows limiting the amount of memory + // dedicated to loading the torrent, and fails for torrents that exceed + // the limit. To load large torrents, this limit may also need to be + // raised in settings_pack::max_piece_count and in calls to + // read_resume_data(). + bool parse_info_section(bdecode_node const& info, error_code& ec, int max_pieces); + +#if TORRENT_ABI_VERSION < 3 + TORRENT_DEPRECATED + bool parse_info_section(bdecode_node const& info, error_code& ec); +#endif + + // This function looks up keys from the info-dictionary of the loaded + // torrent file. It can be used to access extension values put in the + // .torrent file. If the specified key cannot be found, it returns nullptr. + bdecode_node info(char const* key) const; + + // returns a the raw info section of the torrent file. + // The underlying buffer is still owned by the torrent_info object + span info_section() const + { return span(m_info_section.get(), m_info_section_size); } + +#if TORRENT_ABI_VERSION <= 2 + // swap the content of this and ``ti``. + TORRENT_DEPRECATED + void swap(torrent_info& ti); + + // ``metadata()`` returns a the raw info section of the torrent file. The size + // of the metadata is returned by ``metadata_size()``. + // Even though the bytes returned by ``metadata()`` are not ``const``, + // they must not be modified. + TORRENT_DEPRECATED + int metadata_size() const { return m_info_section_size; } + TORRENT_DEPRECATED + boost::shared_array metadata() const; +#endif + + // return the bytes of the piece layer hashes for the specified file. If + // the file doesn't have a piece layer, an empty span is returned. + // The span size is divisible by 32, the size of a SHA-256 hash. + // If the size of the file is smaller than or equal to the piece size, + // the files "root hash" is the hash of the file and is not saved + // separately in the "piece layers" field, but this function still + // returns the root hash of the file in that case. + span piece_layer(file_index_t) const; + + // clears the piece layers from the torrent_info. This is done by the + // session when a torrent is added, to avoid storing it twice. The piece + // layer (or other hashes part of the merkle tree) are stored in the + // internal torrent object. + void free_piece_layers(); + + // internal + void internal_set_creator(string_view); + void internal_set_creation_date(std::time_t); + void internal_set_comment(string_view); + +#if TORRENT_ABI_VERSION <= 2 + // support for BEP 30 merkle torrents has been removed + + // internal + TORRENT_DEPRECATED + bool add_merkle_nodes(std::map const& + , piece_index_t) { return false; } + TORRENT_DEPRECATED + std::map build_merkle_list(piece_index_t) const + { + return std::map(); + } + + // returns whether or not this is a merkle torrent. + // see `BEP 30`__. + // + // __ https://www.bittorrent.org/beps/bep_0030.html + TORRENT_DEPRECATED + bool is_merkle_torrent() const { return !m_merkle_tree.empty(); } +#endif + + private: + + // populate the piece layers from the metadata + bool parse_piece_layers(bdecode_node const& e, error_code& ec); + + bool parse_torrent_file(bdecode_node const& torrent_file, error_code& ec, int piece_limit); + + void resolve_duplicate_filenames(); + + // the slow path, in case we detect/suspect a name collision + void resolve_duplicate_filenames_slow(); + +#if TORRENT_USE_INVARIANT_CHECKS + friend struct ::lt::invariant_access; + void check_invariant() const; +#endif + + void copy_on_write(); + + file_storage m_files; + + // if m_files is modified, it is first copied into + // m_orig_files so that the original name and + // filenames are preserved. + // the original filenames are required to build URLs for web seeds for + // instance + copy_ptr m_orig_files; + + // the URLs to the trackers + aux::vector m_urls; + std::vector m_web_seeds; + // dht nodes to add to the routing table/bootstrap from + std::vector> m_nodes; + + // the info-hashes (20 bytes each) in the "similar" key. These are offsets + // into the info dict buffer. + std::vector m_similar_torrents; + + // these are similar torrents from outside of the info-dict. We can't + // have non-owning pointers to those, as we only keep the info-dict + // around. + std::vector m_owned_similar_torrents; + + // these or strings of the "collections" key from the torrent file. The + // first value is the offset into the metadata where the string is, the + // second value is the length of the string. Strings are not 0-terminated. + std::vector> m_collections; + + // these are the collections from outside of the info-dict. These are + // owning strings, since we only keep the info-section around, these + // cannot be pointers into that buffer. + std::vector m_owned_collections; + +#if TORRENT_ABI_VERSION <= 2 + // if this is a merkle torrent, this is the merkle + // tree. It has space for merkle_num_nodes(merkle_num_leafs(num_pieces)) + // hashes + aux::vector m_merkle_tree; +#endif + + // v2 merkle tree for each file + // the actual hash buffers are always divisible by 32 (sha256_hash::size()) + aux::vector, file_index_t> m_piece_layers; + + // this is a copy of the info section from the torrent. + // it use maintained in this flat format in order to + // make it available through the metadata extension + // TODO: change the type to std::shared_ptr in C++17 + // it is used as if immutable, it cannot be const for technical reasons + // right now. + boost::shared_array m_info_section; + + // if a comment is found in the torrent file + // this will be set to that comment + std::string m_comment; + + // an optional string naming the software used + // to create the torrent file + std::string m_created_by; + + // the info section parsed. points into m_info_section + // parsed lazily + mutable bdecode_node m_info_dict; + + // if a creation date is found in the torrent file + // this will be set to that, otherwise it'll be + // 1970, Jan 1 + std::time_t m_creation_date = 0; + + // the hash(es) that identify this torrent + info_hash_t m_info_hash; + + // this is the offset into the m_info_section buffer to the first byte of + // the first SHA-1 hash + std::int32_t m_piece_hashes = 0; + + // the number of bytes in m_info_section + std::int32_t m_info_section_size = 0; + + // this is used when creating a torrent. If there's + // only one file there are cases where it's impossible + // to know if it should be written as a multi file torrent + // or not. e.g. test/test there's one file and one directory + // and they have the same name. + static constexpr torrent_info_flags_t multifile = 0_bit; + + // this is true if the torrent is private. i.e., is should not + // be announced on the dht + static constexpr torrent_info_flags_t private_torrent = 1_bit; + + // this is true if one of the trackers has an .i2p top + // domain in its hostname. This means the DHT and LSD + // features are disabled for this torrent (unless the + // settings allows mixing i2p peers with regular peers) + static constexpr torrent_info_flags_t i2p = 2_bit; + + // this flag is set if we found an ssl-cert field in the info + // dictionary + static constexpr torrent_info_flags_t ssl_torrent = 3_bit; + + // v2 piece hashes were loaded from the torrent file and verified + static constexpr torrent_info_flags_t v2_has_piece_hashes = 4_bit; + + torrent_info_flags_t m_flags{}; + }; + +TORRENT_VERSION_NAMESPACE_3_END + +} + +#endif // TORRENT_TORRENT_INFO_HPP_INCLUDED diff --git a/docs/include/libtorrent/torrent_peer.hpp b/docs/include/libtorrent/torrent_peer.hpp new file mode 100644 index 0000000..6e2c19a --- /dev/null +++ b/docs/include/libtorrent/torrent_peer.hpp @@ -0,0 +1,312 @@ +/* + +Copyright (c) 2014-2017, 2019, 2021, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2018, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_PEER_HPP_INCLUDED +#define TORRENT_TORRENT_PEER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_info.hpp" // for peer_source_flags_t +#include "libtorrent/info_hash.hpp" +#include "libtorrent/aux_/string_ptr.hpp" +#include "libtorrent/string_view.hpp" + +namespace libtorrent { + + struct peer_connection_interface; + struct external_ip; + + // calculate the priority of a peer based on its address. One of the + // endpoint should be our own. The priority is symmetric, so it doesn't + // matter which is which + TORRENT_EXTRA_EXPORT std::uint32_t peer_priority( + tcp::endpoint e1, tcp::endpoint e2); + + struct TORRENT_EXTRA_EXPORT torrent_peer + { + torrent_peer(std::uint16_t port, bool connectable, peer_source_flags_t src); +#if TORRENT_USE_ASSERTS + torrent_peer(torrent_peer const&) = default; + torrent_peer& operator=(torrent_peer const&) & = default; + ~torrent_peer() { TORRENT_ASSERT(in_use); in_use = false; } +#endif + + std::int64_t total_download() const; + std::int64_t total_upload() const; + + std::uint32_t rank(external_ip const& external, int external_port) const; + + libtorrent::address address() const; + string_view dest() const; + + tcp::endpoint ip() const { return tcp::endpoint(address(), port); } + +#ifndef TORRENT_DISABLE_LOGGING + std::string to_string() const; +#endif + + protocol_version protocol() { return protocol_v2 ? protocol_version::V2 : protocol_version::V1; } + + // this is the accumulated amount of + // uploaded and downloaded data to this + // torrent_peer. It only accounts for what was + // shared during the last connection to + // this torrent_peer. i.e. These are only updated + // when the connection is closed. For the + // total amount of upload and download + // we'll have to add these figures with the + // statistics from the peer_connection. + // since these values don't need to be stored + // with byte-precision, they specify the number + // of kiB. i.e. shift left 10 bits to compare to + // byte counters. + std::uint32_t prev_amount_upload; + std::uint32_t prev_amount_download; + + // if the torrent_peer is connected now, this + // will refer to a valid peer_connection + peer_connection_interface* connection; + + // as computed by hashing our IP with the remote + // IP of this peer + // calculated lazily + mutable std::uint32_t peer_rank; + + // the time when this torrent_peer was optimistically unchoked + // the last time. in seconds since session was created + // 16 bits is enough to last for 18.2 hours + // when the session time reaches 18 hours, it jumps back by + // 9 hours, and all peers' times are updated to be + // relative to that new time offset + std::uint16_t last_optimistically_unchoked; + + // the time when the torrent_peer connected to us + // or disconnected if it isn't connected right now + // in number of seconds since session was created + std::uint16_t last_connected; + + // the port this torrent_peer is or was connected on + std::uint16_t port; + + // the number of times this torrent_peer has been + // part of a piece that failed the hash check + std::uint8_t hashfails; + + // the number of failed connection attempts + // this torrent_peer has + std::uint32_t failcount:5; // [0, 31] + + // incoming peers (that don't advertise their listen port) + // will not be considered connectable. Peers that + // we have a listen port for will be assumed to be. + std::uint32_t connectable:1; + + // true if this torrent_peer currently is unchoked + // because of an optimistic unchoke. + // when the optimistic unchoke is moved to + // another torrent_peer, this torrent_peer will be choked + // if this is true + std::uint32_t optimistically_unchoked:1; + + // this is true if the torrent_peer is a seed, and we know for sure + // because we have connected to it and it told us it was a seed + std::uint32_t seed:1; + + // we've been told that this peer is upload-only, but we don't know for + // sure because we haven't connected to it yet. If we are finished, we + // will de-prioritize peers that may be seeds + std::uint32_t maybe_upload_only:1; + + // the number of times we have allowed a fast + // reconnect for this torrent_peer. + std::uint32_t fast_reconnects:4; + + // for every valid piece we receive where this + // torrent_peer was one of the participants, we increase + // this value. For every invalid piece we receive + // where this torrent_peer was a participant, we decrease + // this value. If it sinks below a threshold, its + // considered a bad torrent_peer and will be banned. + signed trust_points:4; // [-7, 8] + + // a bitmap combining the peer_source flags + // from peer_info. + std::uint32_t source:6; + + peer_source_flags_t peer_source() const + { return peer_source_flags_t(source); } + +#if !defined TORRENT_DISABLE_ENCRYPTION + // Hints encryption support of torrent_peer. Only effective + // for and when the outgoing encryption policy + // allows both encrypted and non encrypted + // connections (pe_settings::out_enc_policy + // == enabled). The initial state of this flag + // determines the initial connection attempt + // type (true = encrypted, false = standard). + // This will be toggled everytime either an + // encrypted or non-encrypted handshake fails. + bool pe_support:1; +#endif + + // this is true if the v6 union member in addr is + // the one to use, false if it's the v4 one + bool is_v6_addr:1; +#if TORRENT_USE_I2P + // set if the i2p_destination is in use in the addr union + bool is_i2p_addr:1; +#endif + + // if this is true, the torrent_peer has previously + // participated in a piece that failed the piece + // hash check. This will put the torrent_peer on parole + // and only request entire pieces. If a piece pass + // that was partially requested from this torrent_peer it + // will leave parole mode and continue download + // pieces as normal peers. + bool on_parole:1; + + // is set to true if this torrent_peer has been banned + bool banned:1; + + // we think this torrent_peer supports uTP + bool supports_utp:1; + // we have been connected via uTP at least once + bool confirmed_supports_utp:1; + bool supports_holepunch:1; + // this is set to one for web seeds. Web seeds + // are not stored in the policy m_peers list, + // and are exempt from connect candidate bookkeeping + // so, any torrent_peer with the web_seed bit set, is + // never considered a connect candidate + bool web_seed:1; + // this peer supports protocol version 2 + bool protocol_v2:1; +#if TORRENT_USE_ASSERTS + bool in_use = true; +#endif + }; + + struct TORRENT_EXTRA_EXPORT ipv4_peer : torrent_peer + { + ipv4_peer(tcp::endpoint const& ep, bool connectable, peer_source_flags_t src); + ipv4_peer(ipv4_peer const& p); + ipv4_peer& operator=(ipv4_peer const& p) &; + + address_v4 addr; + }; + +#if TORRENT_USE_I2P + struct TORRENT_EXTRA_EXPORT i2p_peer : torrent_peer + { + i2p_peer(string_view dest, bool connectable, peer_source_flags_t src); + i2p_peer(i2p_peer const&) = delete; + i2p_peer& operator=(i2p_peer const&) = delete; + i2p_peer(i2p_peer&&) = default; + i2p_peer& operator=(i2p_peer&&) & = default; + + aux::string_ptr destination; + }; +#endif + + struct TORRENT_EXTRA_EXPORT ipv6_peer : torrent_peer + { + ipv6_peer(tcp::endpoint const& ep, bool connectable, peer_source_flags_t src); + ipv6_peer(ipv6_peer const& p); + + const address_v6::bytes_type addr; + }; + + // if we have i2p peer addresses, sort them *after* all IP addresses + struct peer_address_compare + { + bool operator()(torrent_peer const* lhs, address const& rhs) const + { +#if TORRENT_USE_I2P + if (lhs->is_i2p_addr) return false; +#endif + return lhs->address() < rhs; + } + + bool operator()(address const& lhs, torrent_peer const* rhs) const + { +#if TORRENT_USE_I2P + if (rhs->is_i2p_addr) return true; +#endif + return lhs < rhs->address(); + } + +#if TORRENT_USE_I2P + bool operator()(torrent_peer const* lhs, string_view rhs) const + { + if (!lhs->is_i2p_addr) return true; + return lhs->dest() < rhs; + } + + bool operator()(string_view lhs, torrent_peer const* rhs) const + { + if (!rhs->is_i2p_addr) return false; + return lhs < rhs->dest(); + } +#endif + + bool operator()(torrent_peer const* lhs, torrent_peer const* rhs) const + { +#if TORRENT_USE_I2P + if (lhs->is_i2p_addr != rhs->is_i2p_addr) + return lhs->is_i2p_addr < rhs->is_i2p_addr; + + if (lhs->is_i2p_addr) + return lhs->dest() < rhs->dest(); +#endif + return lhs->address() < rhs->address(); + } + }; + + inline bool torrent_peer_equal(torrent_peer const* lhs, torrent_peer const* rhs) + { +#if TORRENT_USE_I2P + if (lhs->is_i2p_addr != rhs->is_i2p_addr) + return false; + + if (lhs->is_i2p_addr) + return lhs->dest() == rhs->dest(); +#endif + return lhs->address() == rhs->address() + && lhs->port == rhs->port; + } +} + +#endif diff --git a/docs/include/libtorrent/torrent_peer_allocator.hpp b/docs/include/libtorrent/torrent_peer_allocator.hpp new file mode 100644 index 0000000..b48a2a2 --- /dev/null +++ b/docs/include/libtorrent/torrent_peer_allocator.hpp @@ -0,0 +1,102 @@ +/* + +Copyright (c) 2010, 2013-2017, 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_ALLOCATOR_HPP_INCLUDED +#define TORRENT_PEER_ALLOCATOR_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/aux_/pool.hpp" + +namespace libtorrent { + + struct TORRENT_EXTRA_EXPORT torrent_peer_allocator_interface + { + enum peer_type_t + { + ipv4_peer_type, + ipv6_peer_type, + i2p_peer_type + }; + + virtual torrent_peer* allocate_peer_entry(int type) = 0; + virtual void free_peer_entry(torrent_peer* p) = 0; + protected: + ~torrent_peer_allocator_interface() {} + }; + + struct TORRENT_EXTRA_EXPORT torrent_peer_allocator final + : torrent_peer_allocator_interface + { +#if TORRENT_USE_ASSERTS + ~torrent_peer_allocator() { + m_in_use = false; + } +#endif + + torrent_peer* allocate_peer_entry(int type) override; + void free_peer_entry(torrent_peer* p) override; + + std::uint64_t total_bytes() const { return m_total_bytes; } + std::uint64_t total_allocations() const { return m_total_allocations; } + int live_bytes() const { return m_live_bytes; } + int live_allocations() const { return m_live_allocations; } + + private: + + // this is a shared pool where torrent_peer objects + // are allocated. It's a pool since we're likely + // to have tens of thousands of peers, and a pool + // saves significant overhead + + aux::pool m_ipv4_peer_pool{sizeof(libtorrent::ipv4_peer), 500}; + aux::pool m_ipv6_peer_pool{sizeof(libtorrent::ipv6_peer), 500}; +#if TORRENT_USE_I2P + aux::pool m_i2p_peer_pool{sizeof(libtorrent::i2p_peer), 500}; +#endif + + // the total number of bytes allocated (cumulative) + std::uint64_t m_total_bytes = 0; + // the total number of allocations (cumulative) + std::uint64_t m_total_allocations = 0; + // the number of currently live bytes + int m_live_bytes = 0; + // the number of currently live allocations + int m_live_allocations = 0; +#if TORRENT_USE_ASSERTS + bool m_in_use = true; +#endif + }; +} + +#endif + diff --git a/docs/include/libtorrent/torrent_status.hpp b/docs/include/libtorrent/torrent_status.hpp new file mode 100644 index 0000000..de95b24 --- /dev/null +++ b/docs/include/libtorrent/torrent_status.hpp @@ -0,0 +1,610 @@ +/* + +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, AllSeeingEyeTolledEweSew +Copyright (c) 2018, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_STATUS_HPP_INCLUDED +#define TORRENT_TORRENT_STATUS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/time.hpp" // for time_duration +#include "libtorrent/storage_defs.hpp" // for storage_mode_t +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" + +#include +#include +#include + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" +#endif + +TORRENT_VERSION_NAMESPACE_3 + + // holds a snapshot of the status of a torrent, as queried by + // torrent_handle::status(). + struct TORRENT_EXPORT torrent_status + { +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + // hidden + torrent_status() noexcept; + ~torrent_status(); + torrent_status(torrent_status const&); + torrent_status& operator=(torrent_status const&); + torrent_status(torrent_status&&) noexcept; + torrent_status& operator=(torrent_status&&); + + // compares if the torrent status objects come from the same torrent. i.e. + // only the torrent_handle field is compared. + bool operator==(torrent_status const& st) const + { return handle == st.handle; } + + // a handle to the torrent whose status the object represents. + torrent_handle handle; + + // the different overall states a torrent can be in + enum state_t + { +#if TORRENT_ABI_VERSION == 1 + // The torrent is in the queue for being checked. But there + // currently is another torrent that are being checked. + // This torrent will wait for its turn. + queued_for_checking TORRENT_DEPRECATED_ENUM, +#else + // internal + unused_enum_for_backwards_compatibility, +#endif + + // The torrent has not started its download yet, and is + // currently checking existing files. + checking_files, + + // The torrent is trying to download metadata from peers. + // This implies the ut_metadata extension is in use. + downloading_metadata, + + // The torrent is being downloaded. This is the state + // most torrents will be in most of the time. The progress + // meter will tell how much of the files that has been + // downloaded. + downloading, + + // In this state the torrent has finished downloading but + // still doesn't have the entire torrent. i.e. some pieces + // are filtered and won't get downloaded. + finished, + + // In this state the torrent has finished downloading and + // is a pure seeder. + seeding, + + // If the torrent was started in full allocation mode, this + // indicates that the (disk) storage for the torrent is + // allocated. +#if TORRENT_ABI_VERSION == 1 + allocating TORRENT_DEPRECATED_ENUM, +#else + unused_enum_for_backwards_compatibility_allocating, +#endif + + // The torrent is currently checking the fast resume data and + // comparing it to the files on disk. This is typically + // completed in a fraction of a second, but if you add a + // large number of torrents at once, they will queue up. + checking_resume_data + }; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED std::string error; +#endif + + // may be set to an error code describing why the torrent was paused, in + // case it was paused by an error. If the torrent is not paused or if it's + // paused but not because of an error, this error_code is not set. + // if the error is attributed specifically to a file, error_file is set to + // the index of that file in the .torrent file. + error_code errc; + + // if the torrent is stopped because of an disk I/O error, this field + // contains the index of the file in the torrent that encountered the + // error. If the error did not originate in a file in the torrent, there + // are a few special values this can be set to: error_file_none, + // error_file_ssl_ctx, error_file_exception, error_file_partfile or + // error_file_metadata; + file_index_t error_file = torrent_status::error_file_none; + + // special values for error_file to describe which file or component + // encountered the error (``errc``). + // the error did not occur on a file + static constexpr file_index_t error_file_none{-1}; + + // the error occurred setting up the SSL context + static constexpr file_index_t error_file_ssl_ctx{-3}; + + // the error occurred while loading the metadata for the torrent + static constexpr file_index_t error_file_metadata{-4}; + + // there was a serious error reported in this torrent. The error code + // or a torrent log alert may provide more information. + static constexpr file_index_t error_file_exception{-5}; + + // the error occurred with the partfile + static constexpr file_index_t error_file_partfile{-6}; + + // the path to the directory where this torrent's files are stored. + // It's typically the path as was given to async_add_torrent() or + // add_torrent() when this torrent was started. This field is only + // included if the torrent status is queried with + // ``torrent_handle::query_save_path``. + std::string save_path; + + // the name of the torrent. Typically this is derived from the + // .torrent file. In case the torrent was started without metadata, + // and hasn't completely received it yet, it returns the name given + // to it when added to the session. See ``session::add_torrent``. + // This field is only included if the torrent status is queried + // with ``torrent_handle::query_name``. + std::string name; + + // set to point to the ``torrent_info`` object for this torrent. It's + // only included if the torrent status is queried with + // ``torrent_handle::query_torrent_file``. + std::weak_ptr torrent_file; + + // the time until the torrent will announce itself to the tracker. + time_duration next_announce = seconds{0}; + +#if TORRENT_ABI_VERSION == 1 + // the time the tracker want us to wait until we announce ourself + // again the next time. + TORRENT_DEPRECATED time_duration announce_interval; +#endif + + // the URL of the last working tracker. If no tracker request has + // been successful yet, it's set to an empty string. + std::string current_tracker; + + // the number of bytes downloaded and uploaded to all peers, accumulated, + // *this session* only. The session is considered to restart when a + // torrent is paused and restarted again. When a torrent is paused, these + // counters are reset to 0. If you want complete, persistent, stats, see + // ``all_time_upload`` and ``all_time_download``. + std::int64_t total_download = 0; + std::int64_t total_upload = 0; + + // counts the amount of bytes send and received this session, but only + // the actual payload data (i.e the interesting data), these counters + // ignore any protocol overhead. The session is considered to restart + // when a torrent is paused and restarted again. When a torrent is + // paused, these counters are reset to 0. + std::int64_t total_payload_download = 0; + std::int64_t total_payload_upload = 0; + + // the number of bytes that has been downloaded and that has failed the + // piece hash test. In other words, this is just how much crap that has + // been downloaded since the torrent was last started. If a torrent is + // paused and then restarted again, this counter will be reset. + std::int64_t total_failed_bytes = 0; + + // the number of bytes that has been downloaded even though that data + // already was downloaded. The reason for this is that in some situations + // the same data can be downloaded by mistake. When libtorrent sends + // requests to a peer, and the peer doesn't send a response within a + // certain timeout, libtorrent will re-request that block. Another + // situation when libtorrent may re-request blocks is when the requests + // it sends out are not replied in FIFO-order (it will re-request blocks + // that are skipped by an out of order block). This is supposed to be as + // low as possible. This only counts bytes since the torrent was last + // started. If a torrent is paused and then restarted again, this counter + // will be reset. + std::int64_t total_redundant_bytes = 0; + + // a bitmask that represents which pieces we have (set to true) and the + // pieces we don't have. It's a pointer and may be set to 0 if the + // torrent isn't downloading or seeding. + typed_bitfield pieces; + + // a bitmask representing which pieces has had their hash checked. This + // only applies to torrents in *seed mode*. If the torrent is not in seed + // mode, this bitmask may be empty. + typed_bitfield verified_pieces; + + // the total number of bytes of the file(s) that we have. All this does + // not necessarily has to be downloaded during this session (that's + // ``total_payload_download``). + std::int64_t total_done = 0; + + // the total number of bytes to download for this torrent. This + // may be less than the size of the torrent in case there are + // pad files. This number only counts bytes that will actually + // be requested from peers. + std::int64_t total = 0; + + // the number of bytes we have downloaded, only counting the pieces that + // we actually want to download. i.e. excluding any pieces that we have + // but have priority 0 (i.e. not wanted). + // Once a torrent becomes seed, any piece- and file priorities are + // forgotten and all bytes are considered "wanted". + std::int64_t total_wanted_done = 0; + + // The total number of bytes we want to download. This may be smaller + // than the total torrent size in case any pieces are prioritized to 0, + // i.e. not wanted. + // Once a torrent becomes seed, any piece- and file priorities are + // forgotten and all bytes are considered "wanted". + std::int64_t total_wanted = 0; + + // are accumulated upload and download payload byte counters. They are + // saved in and restored from resume data to keep totals across sessions. + std::int64_t all_time_upload = 0; + std::int64_t all_time_download = 0; + + // the posix-time when this torrent was added. i.e. what ``time(nullptr)`` + // returned at the time. + std::time_t added_time = 0; + + // the posix-time when this torrent was finished. If the torrent is not + // yet finished, this is 0. + std::time_t completed_time = 0; + + // the time when we, or one of our peers, last saw a complete copy of + // this torrent. + std::time_t last_seen_complete = 0; + + // The allocation mode for the torrent. See storage_mode_t for the + // options. For more information, see storage-allocation_. + storage_mode_t storage_mode = storage_mode_sparse; + + // a value in the range [0, 1], that represents the progress of the + // torrent's current task. It may be checking files or downloading. + float progress = 0.f; + + // progress parts per million (progress * 1000000) when disabling + // floating point operations, this is the only option to query progress + // + // reflects the same value as ``progress``, but instead in a range [0, + // 1000000] (ppm = parts per million). When floating point operations are + // disabled, this is the only alternative to the floating point value in + // progress. + int progress_ppm = 0; + + // the position this torrent has in the download + // queue. If the torrent is a seed or finished, this is -1. + queue_position_t queue_position{}; + + // the total rates for all peers for this torrent. These will usually + // have better precision than summing the rates from all peers. The rates + // are given as the number of bytes per second. + int download_rate = 0; + int upload_rate = 0; + + // the total transfer rate of payload only, not counting protocol + // chatter. This might be slightly smaller than the other rates, but if + // projected over a long time (e.g. when calculating ETA:s) the + // difference may be noticeable. + int download_payload_rate = 0; + int upload_payload_rate = 0; + + // the number of peers that are seeding that this client is + // currently connected to. + int num_seeds = 0; + + // the number of peers this torrent currently is connected to. Peer + // connections that are in the half-open state (is attempting to connect) + // or are queued for later connection attempt do not count. Although they + // are visible in the peer list when you call get_peer_info(). + int num_peers = 0; + + // if the tracker sends scrape info in its announce reply, these fields + // will be set to the total number of peers that have the whole file and + // the total number of peers that are still downloading. set to -1 if the + // tracker did not send any scrape data in its announce reply. + int num_complete = -1; + int num_incomplete = -1; + + // the number of seeds in our peer list and the total number of peers + // (including seeds). We are not necessarily connected to all the peers + // in our peer list. This is the number of peers we know of in total, + // including banned peers and peers that we have failed to connect to. + int list_seeds = 0; + int list_peers = 0; + + // the number of peers in this torrent's peer list that is a candidate to + // be connected to. i.e. It has fewer connect attempts than the max fail + // count, it is not a seed if we are a seed, it is not banned etc. If + // this is 0, it means we don't know of any more peers that we can try. + int connect_candidates = 0; + + // the number of pieces that has been downloaded. It is equivalent to: + // ``std::accumulate(pieces->begin(), pieces->end())``. So you don't have + // to count yourself. This can be used to see if anything has updated + // since last time if you want to keep a graph of the pieces up to date. + // Note that these pieces have not necessarily been written to disk yet, + // and there is a risk the write to disk will fail. + int num_pieces = 0; + + // the number of distributed copies of the torrent. Note that one copy + // may be spread out among many peers. It tells how many copies there are + // currently of the rarest piece(s) among the peers this client is + // connected to. + int distributed_full_copies = 0; + + // tells the share of pieces that have more copies than the rarest + // piece(s). Divide this number by 1000 to get the fraction. + // + // For example, if ``distributed_full_copies`` is 2 and + // ``distributed_fraction`` is 500, it means that the rarest pieces have + // only 2 copies among the peers this torrent is connected to, and that + // 50% of all the pieces have more than two copies. + // + // If we are a seed, the piece picker is deallocated as an optimization, + // and piece availability is no longer tracked. In this case the + // distributed copies members are set to -1. + int distributed_fraction = 0; + + // the number of distributed copies of the file. note that one copy may + // be spread out among many peers. This is a floating point + // representation of the distributed copies. + // + // the integer part tells how many copies + // there are of the rarest piece(s) + // + // the fractional part tells the fraction of pieces that + // have more copies than the rarest piece(s). + float distributed_copies = 0.f; + + // the size of a block, in bytes. A block is a sub piece, it is the + // number of bytes that each piece request asks for and the number of + // bytes that each bit in the ``partial_piece_info``'s bitset represents, + // see get_download_queue(). This is typically 16 kB, but it may be + // smaller, if the pieces are smaller. + int block_size = 0; + + // the number of unchoked peers in this torrent. + int num_uploads = 0; + + // the number of peer connections this torrent has, including half-open + // connections that hasn't completed the bittorrent handshake yet. This + // is always >= ``num_peers``. + int num_connections = 0; + + // the set limit of upload slots (unchoked peers) for this torrent. + int uploads_limit = 0; + + // the set limit of number of connections for this torrent. + int connections_limit = 0; + + // the number of peers in this torrent that are waiting for more + // bandwidth quota from the torrent rate limiter. This can determine if + // the rate you get from this torrent is bound by the torrents limit or + // not. If there is no limit set on this torrent, the peers might still + // be waiting for bandwidth quota from the global limiter, but then they + // are counted in the ``session_status`` object. + int up_bandwidth_queue = 0; + int down_bandwidth_queue = 0; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + // use last_upload, last_download or + // seeding_duration, finished_duration and active_duration + // instead + + // the number of seconds since any peer last uploaded from this torrent + // and the last time a downloaded piece passed the hash check, + // respectively. Note, when starting up a torrent that needs its files + // checked, piece may pass and that will be considered downloading for the + // purpose of this counter. -1 means there either hasn't been any + // uploading/downloading, or it was too long ago for libtorrent to + // remember (currently forgetting happens after about 18 hours) + TORRENT_DEPRECATED int time_since_upload = 0; + TORRENT_DEPRECATED int time_since_download = 0; + + // These keep track of the number of seconds this torrent has been active + // (not paused) and the number of seconds it has been active while being + // finished and active while being a seed. ``seeding_time`` should be <= + // ``finished_time`` which should be <= ``active_time``. They are all + // saved in and restored from resume data, to keep totals across + // sessions. + TORRENT_DEPRECATED int active_time = 0; + TORRENT_DEPRECATED int finished_time = 0; + TORRENT_DEPRECATED int seeding_time = 0; +#endif + + // A rank of how important it is to seed the torrent, it is used to + // determine which torrents to seed and which to queue. It is based on + // the peer to seed ratio from the tracker scrape. For more information, + // see queuing_. Higher value means more important to seed + int seed_rank = 0; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + + // the number of seconds since this torrent acquired scrape data. + // If it has never done that, this value is -1. + TORRENT_DEPRECATED int last_scrape = 0; + + // the priority of this torrent + TORRENT_DEPRECATED int priority = 0; +#endif + + // the main state the torrent is in. See torrent_status::state_t. + state_t state = checking_resume_data; + + // true if this torrent has unsaved changes + // to its download state and statistics since the last resume data + // was saved. + bool need_save_resume = false; + +#if TORRENT_ABI_VERSION == 1 + // true if the session global IP filter applies + // to this torrent. This defaults to true. + TORRENT_DEPRECATED bool ip_filter_applies = false; + + // true if the torrent is blocked from downloading. This typically + // happens when a disk write operation fails. If the torrent is + // auto-managed, it will periodically be taken out of this state, in the + // hope that the disk condition (be it disk full or permission errors) + // has been resolved. If the torrent is not auto-managed, you have to + // explicitly take it out of the upload mode by calling set_upload_mode() + // on the torrent_handle. + TORRENT_DEPRECATED bool upload_mode = false; + + // true if the torrent is currently in share-mode, i.e. not downloading + // the torrent, but just helping the swarm out. + TORRENT_DEPRECATED bool share_mode = false; + + // true if the torrent is in super seeding mode + TORRENT_DEPRECATED bool super_seeding = false; + + // set to true if the torrent is paused and false otherwise. It's only + // true if the torrent itself is paused. If the torrent is not running + // because the session is paused, this is still false. To know if a + // torrent is active or not, you need to inspect both + // ``torrent_status::paused`` and ``session::is_paused()``. + TORRENT_DEPRECATED bool paused = false; + + // set to true if the torrent is auto managed, i.e. libtorrent is + // responsible for determining whether it should be started or queued. + // For more info see queuing_ + TORRENT_DEPRECATED bool auto_managed = false; + + // true when the torrent is in sequential download mode. In this mode + // pieces are downloaded in order rather than rarest first. + TORRENT_DEPRECATED bool sequential_download = false; +#endif + + // true if all pieces have been downloaded. + bool is_seeding = false; + + // true if all pieces that have a priority > 0 are downloaded. There is + // only a distinction between finished and seeding if some pieces or + // files have been set to priority 0, i.e. are not downloaded. + bool is_finished = false; + + // true if this torrent has metadata (either it was started from a + // .torrent file or the metadata has been downloaded). The only scenario + // where this can be false is when the torrent was started torrent-less + // (i.e. with just an info-hash and tracker ip, a magnet link for + // instance). + bool has_metadata = false; + + // true if there has ever been an incoming connection attempt to this + // torrent. + bool has_incoming = false; + +#if TORRENT_ABI_VERSION == 1 + // true if the torrent is in seed_mode. If the torrent was started in + // seed mode, it will leave seed mode once all pieces have been checked + // or as soon as one piece fails the hash check. + TORRENT_DEPRECATED bool seed_mode = false; +#endif + + // this is true if this torrent's storage is currently being moved from + // one location to another. This may potentially be a long operation + // if a large file ends up being copied from one drive to another. + bool moving_storage = false; + +#if TORRENT_ABI_VERSION == 1 + // true if this torrent is loaded into RAM. A torrent can be started + // and still not loaded into RAM, in case it has not had any peers interested in it + // yet. Torrents are loaded on demand. + TORRENT_DEPRECATED bool is_loaded = false; +#endif + + // these are set to true if this torrent is allowed to announce to the + // respective peer source. Whether they are true or false is determined by + // the queue logic/auto manager. Torrents that are not auto managed will + // always be allowed to announce to all peer sources. + bool announcing_to_trackers = false; + bool announcing_to_lsd = false; + bool announcing_to_dht = false; + +#if TORRENT_ABI_VERSION == 1 + // this reflects whether the ``stop_when_ready`` flag is currently enabled + // on this torrent. For more information, see + // torrent_handle::stop_when_ready(). + TORRENT_DEPRECATED bool stop_when_ready = false; +#endif + +#if TORRENT_ABI_VERSION < 3 + TORRENT_DEPRECATED sha1_hash info_hash; +#endif + + // the info-hash for this torrent + info_hash_t info_hashes; + + // the timestamps of the last time this torrent uploaded or downloaded + // payload to any peer. + time_point last_upload; + time_point last_download; + + // these are cumulative counters of for how long the torrent has been in + // different states. active means not paused and added to session. Whether + // it has found any peers or not is not relevant. + // finished means all selected files/pieces were downloaded and available + // to other peers (this is always a subset of active time). + // seeding means all files/pieces were downloaded and available to + // peers. Being available to peers does not imply there are other peers + // asking for the payload. + seconds active_duration; + seconds finished_duration; + seconds seeding_duration; + + // reflects several of the torrent's flags. For more + // information, see ``torrent_handle::flags()``. + torrent_flags_t flags{}; + }; + +TORRENT_VERSION_NAMESPACE_3_END +} // namespace libtorrent + +namespace std { + template <> + struct hash + { + std::size_t operator()(libtorrent::torrent_status const& ts) const + { + return libtorrent::hash_value(ts.handle); + } + }; +} + +#endif // TORRENT_TORRENT_STATUS_HPP_INCLUDED diff --git a/docs/include/libtorrent/tracker_manager.hpp b/docs/include/libtorrent/tracker_manager.hpp new file mode 100644 index 0000000..4f9857a --- /dev/null +++ b/docs/include/libtorrent/tracker_manager.hpp @@ -0,0 +1,413 @@ +/* + +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2004-2021, Arvid Norberg +Copyright (c) 2016, 2020, Alden Torres +Copyright (c) 2016-2017, Steven Siloti +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TRACKER_MANAGER_HPP_INCLUDED +#define TORRENT_TRACKER_MANAGER_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/flags.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/peer.hpp" // peer_entry +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/union_endpoint.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/listen_socket_handle.hpp" +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/ssl.hpp" +#include "libtorrent/string_view.hpp" + +namespace libtorrent { + + class tracker_manager; + struct timeout_handler; + class udp_tracker_connection; + class http_tracker_connection; + struct counters; +#if TORRENT_USE_I2P + class i2p_connection; +#endif +namespace aux { + struct session_logger; + struct session_settings; + struct resolver_interface; +} + +using tracker_request_flags_t = flags::bitfield_flag; + +// Kinds of tracker announces. This is typically indicated as the ``&event=`` +// HTTP query string parameter to HTTP trackers. +enum class event_t : std::uint8_t +{ + none, + completed, + started, + stopped, + paused +}; + + struct TORRENT_EXTRA_EXPORT tracker_request + { + tracker_request() = default; + + static constexpr tracker_request_flags_t scrape_request = 0_bit; + + // affects interpretation of peers string in HTTP response + // see parse_tracker_response() + static constexpr tracker_request_flags_t i2p = 1_bit; + + std::string url; + std::string trackerid; +#if TORRENT_ABI_VERSION == 1 + std::string auth; +#endif + + std::shared_ptr filter; + + std::int64_t downloaded = -1; + std::int64_t uploaded = -1; + std::int64_t left = -1; + std::int64_t corrupt = 0; + std::int64_t redundant = 0; + std::uint16_t listen_port = 0; + event_t event = event_t::none; + tracker_request_flags_t kind = {}; + + std::uint32_t key = 0; + int num_want = 0; + std::vector ipv6; + std::vector ipv4; + sha1_hash info_hash; + peer_id pid; + + aux::listen_socket_handle outgoing_socket; + + // set to true if the .torrent file this tracker announce is for is marked + // as private (i.e. has the "priv": 1 key) + bool private_torrent = false; + + // this is set to true if this request was triggered by a "manual" call to + // scrape_tracker() or force_reannounce() + bool triggered_manually = false; + +#if TORRENT_USE_SSL + ssl::context* ssl_ctx = nullptr; +#endif +#if TORRENT_USE_I2P + i2p_connection* i2pconn = nullptr; +#endif + }; + + struct tracker_response + { + tracker_response() + : interval(1800) + , min_interval(1) + , complete(-1) + , incomplete(-1) + , downloaders(-1) + , downloaded(-1) + {} + + // peers from the tracker, in various forms + std::vector peers; + std::vector peers4; + std::vector peers6; +#if TORRENT_USE_I2P + std::vector i2p_peers; +#endif + // our external IP address (if the tracker responded with ti, otherwise + // INADDR_ANY) + address external_ip; + + // the tracker id, if it was included in the response, otherwise + // an empty string + std::string trackerid; + + // if the tracker returned an error, this is set to that error + std::string failure_reason; + + // contains a warning message from the tracker, if included in + // the response + std::string warning_message; + + // re-announce interval, in seconds + seconds32 interval; + + // the lowest force-announce interval + seconds32 min_interval; + + // the number of seeds in the swarm + int complete; + + // the number of downloaders in the swarm + int incomplete; + + // if supported by the tracker, the number of actively downloading peers. + // i.e. partial seeds. If not supported, -1 + int downloaders; + + // the number of times the torrent has been downloaded + int downloaded; + }; + + struct TORRENT_EXTRA_EXPORT request_callback + { + friend class tracker_manager; + request_callback() {} + virtual ~request_callback() {} + virtual void tracker_warning(tracker_request const& req + , std::string const& msg) = 0; + virtual void tracker_scrape_response(tracker_request const& /*req*/ + , int /*complete*/, int /*incomplete*/, int /*downloads*/ + , int /*downloaders*/) {} + virtual void tracker_response( + tracker_request const& req + , address const& tracker_ip + , std::list
    const& ip_list + , struct tracker_response const& response) = 0; + virtual void tracker_request_error( + tracker_request const& req + , error_code const& ec + , operation_t op + , const std::string& msg + , seconds32 retry_interval) = 0; + +#ifndef TORRENT_DISABLE_LOGGING + virtual bool should_log() const = 0; + virtual void debug_log(const char* fmt, ...) const noexcept TORRENT_FORMAT(2,3) = 0; +#endif + }; + + struct TORRENT_EXTRA_EXPORT timeout_handler + : std::enable_shared_from_this + { + explicit timeout_handler(io_context&); + + timeout_handler(timeout_handler const&) = delete; + timeout_handler& operator=(timeout_handler const&) = delete; + + void set_timeout(int completion_timeout, int read_timeout); + void restart_read_timeout(); + void cancel(); + bool cancelled() const { return m_abort; } + + virtual void on_timeout(error_code const& ec) = 0; + virtual ~timeout_handler(); + + auto get_executor() { return m_timeout.get_executor(); } + + private: + + void timeout_callback(error_code const&); + + int m_completion_timeout = 0; + + // used for timeouts + // this is set when the request has been sent + time_point m_start_time; + + // this is set every time something is received + time_point m_read_time; + + // the asio async operation + deadline_timer m_timeout; + + int m_read_timeout = 0; + + bool m_abort = false; +#if TORRENT_USE_ASSERTS + int m_outstanding_timer_wait = 0; +#endif + }; + + struct TORRENT_EXTRA_EXPORT tracker_connection + : timeout_handler + { + tracker_connection(tracker_manager& man + , tracker_request req + , io_context& ios + , std::weak_ptr r); + + std::shared_ptr requester() const; + ~tracker_connection() override {} + + tracker_request const& tracker_req() const { return m_req; } + + void fail(error_code const& ec, operation_t op, char const* msg = "" + , seconds32 interval = seconds32(0), seconds32 min_interval = seconds32(0)); + virtual void start() = 0; + virtual void close() = 0; + aux::listen_socket_handle const& bind_socket() const { return m_req.outgoing_socket; } + void sent_bytes(int bytes); + void received_bytes(int bytes); + + std::shared_ptr shared_from_this() + { + return std::static_pointer_cast( + timeout_handler::shared_from_this()); + } + + private: + + const tracker_request m_req; + + protected: + + void fail_impl(error_code const& ec, operation_t op, std::string msg = std::string() + , seconds32 interval = seconds32(0), seconds32 min_interval = seconds32(0)); + + std::weak_ptr m_requester; + + tracker_manager& m_man; + }; + + class TORRENT_EXTRA_EXPORT tracker_manager final + : single_threaded + { + public: + + using send_fun_t = std::function + , error_code&, udp_send_flags_t)>; + using send_fun_hostname_t = std::function + , error_code&, udp_send_flags_t)>; + + tracker_manager(send_fun_t send_fun + , send_fun_hostname_t send_fun_hostname + , counters& stats_counters + , aux::resolver_interface& resolver + , aux::session_settings const& sett +#if !defined TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + , aux::session_logger& ses +#endif + ); + + ~tracker_manager(); + + tracker_manager(tracker_manager const&) = delete; + tracker_manager& operator=(tracker_manager const&) = delete; + + void queue_request( + io_context& ios + , tracker_request&& r + , aux::session_settings const& sett + , std::weak_ptr c + = std::weak_ptr()); + void queue_request( + io_context& ios + , tracker_request const& r + , aux::session_settings const& sett + , std::weak_ptr c + = std::weak_ptr()) = delete; + void abort_all_requests(bool all = false); + void stop(); + + void remove_request(http_tracker_connection const* c); + void remove_request(udp_tracker_connection const* c); + bool empty() const; + int num_requests() const; + + void sent_bytes(int bytes); + void received_bytes(int bytes); + + void incoming_error(error_code const& ec, udp::endpoint const& ep); + bool incoming_packet(udp::endpoint const& ep, span buf); + + // this is only used for SOCKS packets, since + // they may be addressed to hostname + bool incoming_packet(string_view hostname, span buf); + + void update_transaction_id( + std::shared_ptr c + , std::uint32_t tid); + + aux::session_settings const& settings() const { return m_settings; } + aux::resolver_interface& host_resolver() { return m_host_resolver; } + + void send_hostname(aux::listen_socket_handle const& sock + , char const* hostname, int port, span p + , error_code& ec, udp_send_flags_t flags = {}); + + void send(aux::listen_socket_handle const& sock + , udp::endpoint const& ep, span p + , error_code& ec, udp_send_flags_t flags = {}); + + private: + + // maps transactionid to the udp_tracker_connection + // These must use shared_ptr to avoid a dangling reference + // if a connection is erased while a timeout event is in the queue + std::unordered_map> m_udp_conns; + + std::vector> m_http_conns; + std::deque> m_queued; + + send_fun_t m_send_fun; + send_fun_hostname_t m_send_fun_hostname; + aux::resolver_interface& m_host_resolver; + aux::session_settings const& m_settings; + counters& m_stats_counters; + bool m_abort = false; +#if !defined TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + aux::session_logger& m_ses; +#endif + }; +} + +#endif // TORRENT_TRACKER_MANAGER_HPP_INCLUDED diff --git a/docs/include/libtorrent/truncate.hpp b/docs/include/libtorrent/truncate.hpp new file mode 100644 index 0000000..0786c81 --- /dev/null +++ b/docs/include/libtorrent/truncate.hpp @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TRUNCATE_HPP_INCLUDED +#define TORRENT_TRUNCATE_HPP_INCLUDED + +#include "libtorrent/fwd.hpp" +#include "libtorrent/error_code.hpp" +#include + +namespace libtorrent { + +// Truncates files larger than specified in the file_storage, saved under +// the specified save_path. +TORRENT_EXPORT void truncate_files(file_storage const& fs, std::string const& save_path, storage_error& ec); + +} + +#endif diff --git a/docs/include/libtorrent/udp_socket.hpp b/docs/include/libtorrent/udp_socket.hpp new file mode 100644 index 0000000..d723565 --- /dev/null +++ b/docs/include/libtorrent/udp_socket.hpp @@ -0,0 +1,174 @@ +/* + +Copyright (c) 2007-2021, Arvid Norberg +Copyright (c) 2016, 2020, Alden Torres +Copyright (c) 2016, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UDP_SOCKET_HPP_INCLUDED +#define TORRENT_UDP_SOCKET_HPP_INCLUDED + +#include "libtorrent/socket.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/proxy_settings.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/aux_/listen_socket_handle.hpp" +#include "libtorrent/aux_/resolver_interface.hpp" + +#include +#include + +namespace libtorrent { + +namespace aux { struct alert_manager; } + struct socks5; + + using udp_send_flags_t = flags::bitfield_flag; + + class TORRENT_EXTRA_EXPORT udp_socket : single_threaded + { + public: + udp_socket(io_context& ios, aux::listen_socket_handle ls); + + // non-copyable + udp_socket(udp_socket const&) = delete; + udp_socket& operator=(udp_socket const&) = delete; + + static constexpr udp_send_flags_t peer_connection = 0_bit; + static constexpr udp_send_flags_t tracker_connection = 1_bit; + static constexpr udp_send_flags_t dont_queue = 2_bit; + static constexpr udp_send_flags_t dont_fragment = 3_bit; + + bool is_open() const { return m_abort == false; } + udp::socket::executor_type get_executor() { return m_socket.get_executor(); } + + template + void async_read(Handler&& h) + { + m_socket.async_wait(udp::socket::wait_read, std::forward(h)); + } + + template + void async_write(Handler&& h) + { + m_socket.async_wait(udp::socket::wait_write, std::forward(h)); + } + + struct packet + { + span data; + udp::endpoint from; + string_view hostname; + error_code error; + }; + + int read(span pkts, error_code& ec); + + // this is only valid when using a socks5 proxy + void send_hostname(char const* hostname, int port, span p + , error_code& ec, udp_send_flags_t flags = {}); + + void send(udp::endpoint const& ep, span p + , error_code& ec, udp_send_flags_t flags = {}); + void open(udp const& protocol, error_code& ec); + void bind(udp::endpoint const& ep, error_code& ec); + void close(); + int local_port() const { return m_bind_port; } + + void set_proxy_settings(aux::proxy_settings const& ps, aux::alert_manager& alerts + , aux::resolver_interface& resolver, bool send_local_ep); + aux::proxy_settings const& get_proxy_settings() { return m_proxy_settings; } + + bool is_closed() const { return m_abort; } + udp::endpoint local_endpoint(error_code& ec) const + { return m_socket.local_endpoint(ec); } + // best effort, if you want to know the error, use + // ``local_endpoint(error_code& ec)`` + udp::endpoint local_endpoint() const + { + error_code ec; + return local_endpoint(ec); + } + + using receive_buffer_size = udp::socket::receive_buffer_size; + using send_buffer_size = udp::socket::send_buffer_size; + + template + void get_option(SocketOption const& opt, error_code& ec) + { + m_socket.get_option(opt, ec); + } + + template + void set_option(SocketOption const& opt, error_code& ec) + { + m_socket.set_option(opt, ec); + } + +#ifdef TCP_NOTSENT_LOWAT + void set_option(tcp_notsent_lowat const&, error_code&) {} +#endif + + template + void get_option(SocketOption& opt, error_code& ec) + { + m_socket.get_option(opt, ec); + } + + bool active_socks5() const; + + private: + + void wrap(udp::endpoint const& ep, span p, error_code& ec, udp_send_flags_t flags); + void wrap(char const* hostname, int port, span p, error_code& ec, udp_send_flags_t flags); + bool unwrap(udp_socket::packet& pack); + + udp::socket m_socket; + + io_context& m_ioc; + + using receive_buffer = std::array; + std::unique_ptr m_buf; + aux::listen_socket_handle m_listen_socket; + + std::uint16_t m_bind_port; + + aux::proxy_settings m_proxy_settings; + + std::shared_ptr m_socks5_connection; + + bool m_abort:1; + }; +} + +#endif diff --git a/docs/include/libtorrent/udp_tracker_connection.hpp b/docs/include/libtorrent/udp_tracker_connection.hpp new file mode 100644 index 0000000..11b6158 --- /dev/null +++ b/docs/include/libtorrent/udp_tracker_connection.hpp @@ -0,0 +1,134 @@ +/* + +Copyright (c) 2004-2008, 2010, 2012, 2014-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UDP_TRACKER_CONNECTION_HPP_INCLUDED +#define TORRENT_UDP_TRACKER_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/string_view.hpp" + +namespace libtorrent { + + class TORRENT_EXTRA_EXPORT udp_tracker_connection: public tracker_connection + { + friend class tracker_manager; + public: + + udp_tracker_connection( + io_context& ios + , tracker_manager& man + , tracker_request const& req + , std::weak_ptr c); + + void start() override; + void close() override; + + std::uint32_t transaction_id() const { return m_transaction_id; } + + private: + + enum class action_t : std::uint8_t + { + connect, + announce, + scrape, + error + }; + + std::shared_ptr shared_from_this() + { + return std::static_pointer_cast( + tracker_connection::shared_from_this()); + } + + void update_transaction_id(); + + void name_lookup(error_code const& error + , std::vector
    const& addresses, int port); + void start_announce(); + + bool on_receive(udp::endpoint const& ep, span buf); + bool on_receive_hostname(string_view hostname, span buf); + bool on_connect_response(span buf); + bool on_announce_response(span buf); + bool on_scrape_response(span buf); + + // wraps tracker_connection::fail + void fail(error_code const& ec + , operation_t op + , char const* msg = "" + , seconds32 interval = seconds32(0) + , seconds32 min_interval = seconds32(30)); + + void send_udp_connect(); + void send_udp_announce(); + void send_udp_scrape(); + + void on_timeout(error_code const& ec) override; + + std::string m_hostname; + std::vector m_endpoints; + + struct connection_cache_entry + { + std::int64_t connection_id; + time_point expires; + }; + + static std::map m_connection_cache; + static std::mutex m_cache_mutex; + + udp::endpoint m_target; + + std::uint32_t m_transaction_id; + int m_attempts; + + action_t m_state; + + bool m_abort; + }; + +} + +#endif // TORRENT_UDP_TRACKER_CONNECTION_HPP_INCLUDED diff --git a/docs/include/libtorrent/union_endpoint.hpp b/docs/include/libtorrent/union_endpoint.hpp new file mode 100644 index 0000000..2aa2ec6 --- /dev/null +++ b/docs/include/libtorrent/union_endpoint.hpp @@ -0,0 +1,115 @@ +/* + +Copyright (c) 2010, 2013, 2015-2019, Arvid Norberg +Copyright (c) 2015, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UNION_ENDPOINT_HPP_INCLUDED +#define TORRENT_UNION_ENDPOINT_HPP_INCLUDED + +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" + +namespace libtorrent { + + struct union_address + { + union_address() { *this = address(); } + explicit union_address(address const& a) { *this = a; } + union_address& operator=(address const& a) & + { + v4 = a.is_v4(); + if (v4) + addr.v4 = a.to_v4().to_bytes(); + else + addr.v6 = a.to_v6().to_bytes(); + return *this; + } + + bool operator==(union_address const& rh) const + { + if (v4 != rh.v4) return false; + if (v4) + return addr.v4 == rh.addr.v4; + else + return addr.v6 == rh.addr.v6; + } + + bool operator!=(union_address const& rh) const + { + return !(*this == rh); + } + + operator address() const + { + if (v4) return address(address_v4(addr.v4)); + else return address(address_v6(addr.v6)); + } + + union addr_t + { + address_v4::bytes_type v4; + address_v6::bytes_type v6; + }; + addr_t addr; + bool v4:1; + }; + + struct union_endpoint + { + explicit union_endpoint(tcp::endpoint const& ep) { *this = ep; } + explicit union_endpoint(udp::endpoint const& ep) { *this = ep; } + union_endpoint() { *this = tcp::endpoint(); } + + union_endpoint& operator=(udp::endpoint const& ep) & + { + addr = ep.address(); + port = ep.port(); + return *this; + } + + operator udp::endpoint() const { return udp::endpoint(addr, port); } + + union_endpoint& operator=(tcp::endpoint const& ep) & + { + addr = ep.address(); + port = ep.port(); + return *this; + } + + libtorrent::address address() const { return addr; } + operator tcp::endpoint() const { return tcp::endpoint(addr, port); } + + union_address addr; + std::uint16_t port; + }; +} + +#endif diff --git a/docs/include/libtorrent/units.hpp b/docs/include/libtorrent/units.hpp new file mode 100644 index 0000000..131775a --- /dev/null +++ b/docs/include/libtorrent/units.hpp @@ -0,0 +1,186 @@ +/* + +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2016-2021, Arvid Norberg +Copyright (c) 2019, Steven Siloti +Copyright (c) 2020, Silver Zachara +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UNITS_HPP +#define TORRENT_UNITS_HPP + +#include +#include +#include +#include +#include + +#include "libtorrent/config.hpp" + +namespace libtorrent { +namespace aux { + template + struct difference_tag; + +#if TORRENT_USE_IOSTREAM + template + struct type_to_print_as + { + using type = typename std::conditional::type; + }; +#endif + + + template::value>::type> + struct strong_typedef + { + using underlying_type = UnderlyingType; + using diff_type = strong_typedef>; + + constexpr strong_typedef(strong_typedef const& rhs) noexcept = default; + constexpr strong_typedef(strong_typedef&& rhs) noexcept = default; + strong_typedef() noexcept = default; +#if TORRENT_ABI_VERSION == 1 + constexpr strong_typedef(UnderlyingType val) : m_val(val) {} + constexpr operator UnderlyingType() const { return m_val; } +#else + constexpr explicit strong_typedef(UnderlyingType val) : m_val(val) {} + constexpr explicit operator UnderlyingType() const { return m_val; } + constexpr bool operator==(strong_typedef const& rhs) const { return m_val == rhs.m_val; } + constexpr bool operator!=(strong_typedef const& rhs) const { return m_val != rhs.m_val; } + constexpr bool operator<(strong_typedef const& rhs) const { return m_val < rhs.m_val; } + constexpr bool operator>(strong_typedef const& rhs) const { return m_val > rhs.m_val; } + constexpr bool operator>=(strong_typedef const& rhs) const { return m_val >= rhs.m_val; } + constexpr bool operator<=(strong_typedef const& rhs) const { return m_val <= rhs.m_val; } +#endif + strong_typedef& operator++() { ++m_val; return *this; } + strong_typedef& operator--() { --m_val; return *this; } + + strong_typedef operator++(int) & { return strong_typedef{m_val++}; } + strong_typedef operator--(int) & { return strong_typedef{m_val--}; } + + friend diff_type operator-(strong_typedef lhs, strong_typedef rhs) + { return diff_type{lhs.m_val - rhs.m_val}; } + friend strong_typedef operator+(strong_typedef lhs, diff_type rhs) + { return strong_typedef{lhs.m_val + static_cast(rhs)}; } + friend strong_typedef operator+(diff_type lhs, strong_typedef rhs) + { return strong_typedef{static_cast(lhs) + rhs.m_val}; } + friend strong_typedef operator-(strong_typedef lhs, diff_type rhs) + { return strong_typedef{lhs.m_val - static_cast(rhs)}; } + + strong_typedef& operator+=(diff_type rhs) & + { m_val += static_cast(rhs); return *this; } + strong_typedef& operator-=(diff_type rhs) & + { m_val -= static_cast(rhs); return *this; } + + strong_typedef& operator=(strong_typedef const& rhs) & noexcept = default; + strong_typedef& operator=(strong_typedef&& rhs) & noexcept = default; + +#if TORRENT_USE_IOSTREAM + friend std::ostream& operator<<(std::ostream& os, strong_typedef val) + { return os << static_cast::type>(static_cast(val)); } +#endif + + private: + UnderlyingType m_val; + }; + + // meta function to return the underlying type of a strong_typedef or enumeration + // , or the type itself if it isn't a strong_typedef + template + struct underlying_index_t { using type = T; }; + + template + struct underlying_index_t::value>::type> + { using type = typename std::underlying_type::type; }; + + template + struct underlying_index_t> { using type = U; }; + + struct piece_index_tag; + struct file_index_tag; + + template + std::string to_string(strong_typedef const t) + { return std::to_string(static_cast(t)); } + + template + strong_typedef next(strong_typedef v) + { return ++v;} + + template + strong_typedef prev(strong_typedef v) + { return --v;} + +} // namespace libtorrent::aux + + // this type represents a piece index in a torrent. + using piece_index_t = aux::strong_typedef; + + // this type represents an index to a file in a torrent. Any specific torrent + // file has a well defined and immutable file list, and a file index into it + // always refers to the same file. + using file_index_t = aux::strong_typedef; + +} // namespace libtorrent + +namespace std { + + template + class numeric_limits> : public std::numeric_limits + { + using type = libtorrent::aux::strong_typedef; + public: + + static constexpr type (min)() + { return type((std::numeric_limits::min)()); } + + static constexpr type (max)() + { return type((std::numeric_limits::max)()); } + }; + + template + struct hash> : std::hash + { + using base = std::hash; + using argument_type = libtorrent::aux::strong_typedef; +#if __cplusplus < 201402 + // this was deprecated in C++17 + using result_type = typename base::result_type; +#else + using result_type = std::size_t; +#endif + result_type operator()(argument_type const& s) const + { return this->base::operator()(static_cast(s)); } + }; +} + +#endif diff --git a/docs/include/libtorrent/upnp.hpp b/docs/include/libtorrent/upnp.hpp new file mode 100644 index 0000000..42f33c0 --- /dev/null +++ b/docs/include/libtorrent/upnp.hpp @@ -0,0 +1,406 @@ +/* + +Copyright (c) 2007-2010, 2013-2020, 2022, Arvid Norberg +Copyright (c) 2016-2017, 2020, Alden Torres +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UPNP_HPP +#define TORRENT_UPNP_HPP + +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/aux_/resolver.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/aux_/portmap.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/listen_socket_handle.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/ssl.hpp" + +#include +#include +#include + +namespace libtorrent { + struct http_connection; + class http_parser; + +namespace aux { + + struct socket_package + { + socket_package(io_context& ios) : socket(ios) {} + udp::socket socket; + std::array buffer; + udp::endpoint remote; + }; +} + +namespace upnp_errors { + // error codes for the upnp_error_category. They hold error codes + // returned by UPnP routers when mapping ports + enum error_code_enum + { + // No error + no_error = 0, + // One of the arguments in the request is invalid + invalid_argument = 402, + // The request failed + action_failed = 501, + // The specified value does not exist in the array + value_not_in_array = 714, + // The source IP address cannot be wild-carded, but + // must be fully specified + source_ip_cannot_be_wildcarded = 715, + // The external port cannot be a wildcard, but must + // be specified + external_port_cannot_be_wildcarded = 716, + // The port mapping entry specified conflicts with a + // mapping assigned previously to another client + port_mapping_conflict = 718, + // Internal and external port value must be the same + internal_port_must_match_external = 724, + // The NAT implementation only supports permanent + // lease times on port mappings + only_permanent_leases_supported = 725, + // RemoteHost must be a wildcard and cannot be a + // specific IP address or DNS name + remote_host_must_be_wildcard = 726, + // ExternalPort must be a wildcard and cannot be a + // specific port + external_port_must_be_wildcard = 727 + }; + + // hidden + TORRENT_EXPORT boost::system::error_code make_error_code(error_code_enum e); +} // namespace upnp_errors + + // the boost.system error category for UPnP errors + TORRENT_EXPORT boost::system::error_category& upnp_category(); + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + inline boost::system::error_category& get_upnp_category() + { return upnp_category(); } +#endif + +struct parse_state +{ + bool in_service = false; + std::vector tag_stack; + std::string control_url; + std::string service_type; + std::string model; + std::string url_base; + bool top_tags(string_view str1, string_view str2) + { + auto i = tag_stack.rbegin(); + if (i == tag_stack.rend()) return false; + if (!string_equal_no_case(*i, str2)) return false; + ++i; + if (i == tag_stack.rend()) return false; + if (!string_equal_no_case(*i, str1)) return false; + return true; + } +}; + +struct error_code_parse_state +{ + bool in_error_code = false; + bool exit = false; + int error_code = -1; +}; + +struct ip_address_parse_state: error_code_parse_state +{ + bool in_ip_address = false; + std::string ip_address; +}; + +TORRENT_EXTRA_EXPORT void find_control_url(int type, string_view, parse_state& state); + +TORRENT_EXTRA_EXPORT void find_error_code(int type, string_view string + , error_code_parse_state& state); + +TORRENT_EXTRA_EXPORT void find_ip_address(int type, string_view string + , ip_address_parse_state& state); + +// TODO: support using the windows API for UPnP operations as well +struct TORRENT_EXTRA_EXPORT upnp final + : std::enable_shared_from_this + , single_threaded +{ + upnp(io_context& ios + , aux::session_settings const& settings + , aux::portmap_callback& cb + , address_v4 listen_address + , address_v4 netmask + , std::string listen_device + , aux::listen_socket_handle ls); + ~upnp(); + + void start(); + + // Attempts to add a port mapping for the specified protocol. Valid protocols are + // ``upnp::tcp`` and ``upnp::udp`` for the UPnP class and ``natpmp::tcp`` and + // ``natpmp::udp`` for the NAT-PMP class. + // + // ``external_port`` is the port on the external address that will be mapped. This + // is a hint, you are not guaranteed that this port will be available, and it may + // end up being something else. In the portmap_alert_ notification, the actual + // external port is reported. + // + // ``local_port`` is the port in the local machine that the mapping should forward + // to. + // + // The return value is an index that identifies this port mapping. This is used + // to refer to mappings that fails or succeeds in the portmap_error_alert_ and + // portmap_alert_ respectively. If The mapping fails immediately, the return value + // is -1, which means failure. There will not be any error alert notification for + // mappings that fail with a -1 return value. + port_mapping_t add_mapping(portmap_protocol p, int external_port, tcp::endpoint local_ep + , std::string const& device); + + // This function removes a port mapping. ``mapping_index`` is the index that refers + // to the mapping you want to remove, which was returned from add_mapping(). + void delete_mapping(port_mapping_t mapping_index); + + bool get_mapping(port_mapping_t mapping_index, tcp::endpoint& local_ep, int& external_port + , portmap_protocol& protocol) const; + + void close(); + + // This is only available for UPnP routers. If the model is advertised by + // the router, it can be queried through this function. + std::string router_model() + { + TORRENT_ASSERT(is_single_thread()); + return m_model; + } + +private: + + std::shared_ptr self() { return shared_from_this(); } + + void open_multicast_socket(aux::socket_package& s, error_code& ec); + void open_unicast_socket(aux::socket_package& s, error_code& ec); + + void map_timer(error_code const& ec); + void try_map_upnp(); + void discover_device_impl(); + + void resend_request(error_code const& e); + void on_reply(aux::socket_package& s, error_code const& ec, std::size_t len); + + struct rootdevice; + void next(rootdevice& d, port_mapping_t i); + void update_map(rootdevice& d, port_mapping_t i); + + int lease_duration(rootdevice const& d) const; + + void connect(rootdevice& d); + + void on_upnp_xml(error_code const& e + , libtorrent::http_parser const& p, rootdevice& d + , http_connection& c); + void on_upnp_get_ip_address_response(error_code const& e + , libtorrent::http_parser const& p, rootdevice& d + , http_connection& c); + void on_upnp_map_response(error_code const& e + , libtorrent::http_parser const& p, rootdevice& d + , port_mapping_t mapping, http_connection& c); + void on_upnp_unmap_response(error_code const& e + , libtorrent::http_parser const& p, rootdevice& d + , port_mapping_t mapping, http_connection& c); + void on_expire(error_code const& e); + + void disable(error_code const& ec); + void return_error(port_mapping_t mapping, int code); +#ifndef TORRENT_DISABLE_LOGGING + bool should_log() const; + void log(char const* fmt, ...) const TORRENT_FORMAT(2,3); +#endif + + void get_ip_address(rootdevice& d); + void delete_port_mapping(rootdevice& d, port_mapping_t i); + void create_port_mapping(http_connection& c, rootdevice& d, port_mapping_t i); + void post(upnp::rootdevice const& d, char const* soap + , char const* soap_action); + + int num_mappings() const { return int(m_mappings.size()); } + + struct global_mapping_t + { + portmap_protocol protocol = portmap_protocol::none; + int external_port = 0; + tcp::endpoint local_ep; + // may be set to a device name, if this mapping is for a network bound + // to a specific network device + std::string device; + }; + + struct mapping_t : aux::base_mapping + { + // the local port for this mapping. If this is set + // to 0, the mapping is not in use + tcp::endpoint local_ep; + + // may be set to a network device name to bind to + std::string device; + + // the number of times this mapping has failed + int failcount = 0; + }; + + struct rootdevice + { + rootdevice(); + ~rootdevice(); + rootdevice(rootdevice const&); + rootdevice& operator=(rootdevice const&) &; + rootdevice(rootdevice&&) noexcept; + rootdevice& operator=(rootdevice&&) &; + + // the interface url, through which the list of + // supported interfaces are fetched + std::string url; + + // the url to the WANIP or WANPPP interface + std::string control_url; + // either the WANIP namespace or the WANPPP namespace + std::string service_namespace; + + aux::noexcept_movable> mapping; + + // this is the hostname, port and path + // component of the url or the control_url + // if it has been found + std::string hostname; + int port = 0; + std::string path; + aux::noexcept_movable
    external_ip; + + // set to false if the router doesn't support lease durations + bool use_lease_duration = true; + + // true if the device supports specifying a + // specific external port, false if it doesn't + bool supports_specific_external = true; + + bool disabled = false; + + mutable std::shared_ptr upnp_connection; + +#if TORRENT_USE_ASSERTS + int magic = 1337; +#endif + + bool operator<(rootdevice const& rhs) const + { return url < rhs.url; } + }; + + struct upnp_state_t + { + aux::vector mappings; + std::set devices; + }; + + aux::vector m_mappings; + + aux::session_settings const& m_settings; + + // the set of devices we've found + std::set m_devices; + + aux::portmap_callback& m_callback; + + // current retry count + int m_retry_count = 0; + + io_context& m_io_service; + + aux::resolver m_resolver; + + // the udp socket used to send and receive + // multicast messages on the network + aux::socket_package m_multicast; + aux::socket_package m_unicast; + + // used to resend udp packets in case + // they time out + deadline_timer m_broadcast_timer; + + // timer used to refresh mappings + deadline_timer m_refresh_timer; + + // this timer fires one second after the last UPnP response. This is the + // point where we assume we have received most or all SSDP responses. If we + // are ignoring non-routers and at this point we still haven't received a + // response from a router UPnP device, we override the ignoring behavior and + // map them anyway. + deadline_timer m_map_timer; + + bool m_disabled = false; + bool m_closing = false; + + std::string m_model; + + // the network this UPnP mapper is associated with. Don't talk to any other + // network + address_v4 m_listen_address; + address_v4 m_netmask; + std::string m_device; + +#if TORRENT_USE_SSL + ssl::context m_ssl_ctx; +#endif + + aux::listen_socket_handle m_listen_handle; +}; + +} // namespace libtorrent + +namespace boost { +namespace system { + + template<> struct is_error_code_enum + { static const bool value = true; }; + +} +} + +#endif diff --git a/docs/include/libtorrent/utf8.hpp b/docs/include/libtorrent/utf8.hpp new file mode 100644 index 0000000..9c0596e --- /dev/null +++ b/docs/include/libtorrent/utf8.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2005, 2008-2009, 2013, 2016-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UTF8_HPP_INCLUDED +#define TORRENT_UTF8_HPP_INCLUDED + +#include "libtorrent/aux_/export.hpp" + +#include +#include + +#include "libtorrent/string_view.hpp" + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT std::pair + parse_utf8_codepoint(string_view str); + + TORRENT_EXTRA_EXPORT void append_utf8_codepoint(std::string&, std::int32_t); + +} // namespace libtorrent + +#endif diff --git a/docs/include/libtorrent/vector_utils.hpp b/docs/include/libtorrent/vector_utils.hpp new file mode 100644 index 0000000..01fc3bf --- /dev/null +++ b/docs/include/libtorrent/vector_utils.hpp @@ -0,0 +1,59 @@ +/* + +Copyright (c) 2010, 2013-2014, 2016, 2018-2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_VECTOR_UTILS_HPP_INCLUDE +#define TORRENT_VECTOR_UTILS_HPP_INCLUDE + +#include +#include + +namespace libtorrent { + + template + auto sorted_find(Container& container, T const& v) + -> decltype(container.begin()) + { + auto i = std::lower_bound(container.begin(), container.end(), v); + if (i == container.end()) return container.end(); + if (*i != v) return container.end(); + return i; + } + + template + void sorted_insert(std::vector& container, U v) + { + auto i = std::lower_bound(container.begin(), container.end(), v); + container.insert(i, v); + } +} + +#endif diff --git a/docs/include/libtorrent/version.hpp b/docs/include/libtorrent/version.hpp new file mode 100644 index 0000000..15ee5bd --- /dev/null +++ b/docs/include/libtorrent/version.hpp @@ -0,0 +1,72 @@ +/* + +Copyright (c) 2004, 2006, 2010, 2015, 2017-2020, 2022, Arvid Norberg +Copyright (c) 2016, Jan Berkel +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_VERSION_HPP_INCLUDED +#define TORRENT_VERSION_HPP_INCLUDED + +#include "libtorrent/aux_/export.hpp" +#include + +#define LIBTORRENT_VERSION_MAJOR 2 +#define LIBTORRENT_VERSION_MINOR 0 +#define LIBTORRENT_VERSION_TINY 11 + +// the format of this version is: MMmmtt +// M = Major version, m = minor version, t = tiny version +#define LIBTORRENT_VERSION_NUM ((LIBTORRENT_VERSION_MAJOR * 10000) + (LIBTORRENT_VERSION_MINOR * 100) + LIBTORRENT_VERSION_TINY) + +#define LIBTORRENT_VERSION "2.0.11.0" +#define LIBTORRENT_REVISION "6e1587799" + +namespace libtorrent { + + // the major, minor and tiny versions of libtorrent + constexpr int version_major = 2; + constexpr int version_minor = 0; + constexpr int version_tiny = 11; + + // the libtorrent version in string form + constexpr char const* version_str = "2.0.11.0"; + + // the git commit of this libtorrent version + constexpr std::uint64_t version_revision = 0x6e1587799; + + // returns the libtorrent version as string form in this format: + // "..." + TORRENT_EXPORT char const* version(); + +} + +namespace lt = libtorrent; + +#endif diff --git a/docs/include/libtorrent/web_connection_base.hpp b/docs/include/libtorrent/web_connection_base.hpp new file mode 100644 index 0000000..99eb566 --- /dev/null +++ b/docs/include/libtorrent/web_connection_base.hpp @@ -0,0 +1,137 @@ +/* + +Copyright (c) 2010, 2012, 2014-2020, Arvid Norberg +Copyright (c) 2016, 2019, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef WEB_CONNECTION_BASE_HPP_INCLUDED +#define WEB_CONNECTION_BASE_HPP_INCLUDED + +#include +#include +#include + +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/http_parser.hpp" + +namespace libtorrent { + + class TORRENT_EXTRA_EXPORT web_connection_base + : public peer_connection + { + friend struct invariant_access; + public: + + // this is the constructor where the we are the active part. + // The peer_connection should handshake and verify that the + // other end has the correct id + web_connection_base(peer_connection_args& pack + , web_seed_t const& web); + + int timeout() const override; + void start() override; + + ~web_connection_base() override; + + // called from the main loop when this connection has any + // work to do. + void on_sent(error_code const& error + , std::size_t bytes_transferred) override; + + virtual std::string const& url() const = 0; + + bool in_handshake() const override; + + peer_id our_pid() const override { return peer_id(); } + + // the following functions appends messages + // to the send buffer + void write_choke() override {} + void write_unchoke() override {} + void write_interested() override {} + void write_not_interested() override {} + void write_request(peer_request const&) override = 0; + void write_cancel(peer_request const&) override {} + void write_have(piece_index_t) override {} + void write_dont_have(piece_index_t) override {} + void write_piece(peer_request const&, disk_buffer_holder) override + { TORRENT_ASSERT_FAIL(); } + void write_keepalive() override {} + void on_connected() override; + void write_reject_request(peer_request const&) override {} + void write_allow_fast(piece_index_t) override {} + void write_suggest(piece_index_t) override {} + void write_bitfield() override {} + void write_upload_only(bool) override {} + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + void get_specific_peer_info(peer_info& p) const override; + + protected: + + virtual void add_headers(std::string& request + , aux::session_settings const& sett, bool using_proxy) const; + + // the first request will contain a little bit more data + // than subsequent ones, things that aren't critical are left + // out to save bandwidth. + bool m_first_request; + + // true if we're using ssl + bool m_ssl; + + // this has one entry per bittorrent request + std::deque m_requests; + + std::string m_server_string; + std::string m_basic_auth; + std::string m_host; + std::string m_path; + + std::string m_external_auth; + web_seed_entry::headers_t m_extra_headers; + + http_parser m_parser; + + int m_port; + + // the number of bytes into the receive buffer where + // current read cursor is. + int m_body_start; + }; +} + +#endif // TORRENT_WEB_CONNECTION_BASE_HPP_INCLUDED diff --git a/docs/include/libtorrent/web_peer_connection.hpp b/docs/include/libtorrent/web_peer_connection.hpp new file mode 100644 index 0000000..f319a52 --- /dev/null +++ b/docs/include/libtorrent/web_peer_connection.hpp @@ -0,0 +1,147 @@ +/* + +Copyright (c) 2006-2007, 2009-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_WEB_PEER_CONNECTION_HPP_INCLUDED +#define TORRENT_WEB_PEER_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/web_connection_base.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/operations.hpp" // for operation_t enum +#include "libtorrent/aux_/vector.hpp" + +namespace libtorrent { + + class TORRENT_EXTRA_EXPORT web_peer_connection + : public web_connection_base + { + friend struct invariant_access; + public: + + // this is the constructor where the we are the active part. + // The peer_connection should handshake and verify that the + // other end has the correct id + web_peer_connection(peer_connection_args& pack + , web_seed_t& web); + + void on_connected() override; + + connection_type type() const override + { return connection_type::url_seed; } + + // called from the main loop when this connection has any + // work to do. + void on_receive(error_code const& error + , std::size_t bytes_transferred) override; + + std::string const& url() const override { return m_url; } + + void get_specific_peer_info(peer_info& p) const override; + void disconnect(error_code const& ec + , operation_t op, disconnect_severity_t error = peer_connection_interface::normal) override; + + void write_request(peer_request const& r) override; + + bool received_invalid_data(piece_index_t index, bool single_peer) override; + + private: + + void on_receive_padfile(); + void incoming_payload(char const* buf, int len); + void incoming_zeroes(int len); + void handle_redirect(int bytes_left); + void handle_error(int bytes_left); + void maybe_harvest_piece(); + void disable(error_code const& ec); + + // returns the block currently being + // downloaded. And the progress of that + // block. If the peer isn't downloading + // a piece for the moment, the boost::optional + // will be invalid. + piece_block_progress downloading_piece_progress() const override; + + void handle_padfile(); + + // this has one entry per http-request + // (might be more than the bt requests) + struct file_request_t + { + file_index_t file_index; + int length; + std::int64_t start; + }; + std::deque m_file_requests; + + std::string m_url; + + web_seed_t* m_web; + + // this is used for intermediate storage of pieces to be delivered to the + // bittorrent engine + // TODO: 3 if we make this be a disk_buffer_holder instead + // we would save a copy + // use allocate_disk_receive_buffer and release_disk_receive_buffer + aux::vector m_piece; + + // the number of bytes we've forwarded to the incoming_payload() function + // in the current HTTP response. used to know where in the buffer the + // next response starts + int m_received_body; + + // this is the offset inside the current receive + // buffer where the next chunk header will be. + // this is updated for each chunk header that's + // parsed. It does not necessarily point to a valid + // offset in the receive buffer, if we haven't received + // it yet. This offset never includes the HTTP header + int m_chunk_pos; + + // this is the number of bytes we've already received + // from the next chunk header we're waiting for + int m_partial_chunk_header; + + // the number of responses we've received so far on + // this connection + int m_num_responses; + }; +} + +#endif // TORRENT_WEB_PEER_CONNECTION_HPP_INCLUDED diff --git a/docs/include/libtorrent/write_resume_data.hpp b/docs/include/libtorrent/write_resume_data.hpp new file mode 100644 index 0000000..e1ff682 --- /dev/null +++ b/docs/include/libtorrent/write_resume_data.hpp @@ -0,0 +1,84 @@ +/* + +Copyright (c) 2017-2018, 2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_WRITE_RESUME_DATA_HPP_INCLUDE +#define TORRENT_WRITE_RESUME_DATA_HPP_INCLUDE + +#include + +#include "libtorrent/fwd.hpp" +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/flags.hpp" + +namespace libtorrent { + + // this function turns the resume data in an ``add_torrent_params`` object + // into a bencoded structure + TORRENT_EXPORT entry write_resume_data(add_torrent_params const& atp); + TORRENT_EXPORT std::vector write_resume_data_buf(add_torrent_params const& atp); + + // hidden + using write_torrent_flags_t = flags::bitfield_flag; + + namespace write_flags + { + // this makes write_torrent_file() not fail when attempting to write a + // v2 torrent file that does not have all the piece layers + constexpr write_torrent_flags_t allow_missing_piece_layer = 0_bit; + + // don't include http seeds in the torrent file, even if some are + // present in the add_torrent_params object + constexpr write_torrent_flags_t no_http_seeds = 1_bit; + + // When set, DHT nodes from the add_torrent_params objects are included + // in the resulting .torrent file + constexpr write_torrent_flags_t include_dht_nodes = 2_bit; + } + + // writes only the fields to create a .torrent file. This function may fail + // with a ``std::system_error`` exception if: + // + // * The add_torrent_params object passed to this function does not contain the + // info dictionary (the ``ti`` field) + // * The piece layers are not complete for all files that need them + // + // The ``write_torrent_file_buf()`` overload returns the torrent file in + // bencoded buffer form. This overload may be faster at the expense of lost + // flexibility to add custom fields. + TORRENT_EXPORT entry write_torrent_file(add_torrent_params const& atp); + TORRENT_EXPORT entry write_torrent_file(add_torrent_params const& atp, write_torrent_flags_t flags); + TORRENT_EXPORT std::vector write_torrent_file_buf(add_torrent_params const& atp + , write_torrent_flags_t flags); +} + +#endif diff --git a/docs/include/libtorrent/xml_parse.hpp b/docs/include/libtorrent/xml_parse.hpp new file mode 100644 index 0000000..014dc01 --- /dev/null +++ b/docs/include/libtorrent/xml_parse.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2007, 2011-2017, 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_XML_PARSE_HPP +#define TORRENT_XML_PARSE_HPP + +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/string_view.hpp" + +namespace libtorrent { + + enum + { + xml_start_tag, + xml_end_tag, + xml_empty_tag, + xml_declaration_tag, + xml_string, + xml_attribute, + xml_comment, + xml_parse_error, + // used for tags that don't follow the convention of + // key-value pairs inside the tag brackets. Like !DOCTYPE + xml_tag_content + }; + + // callback(int type, char const* name, int name_len + // , char const* val, int val_len) + // name is element or attribute name + // val is attribute value + // neither string is 0-terminated, but their lengths are specified via + // name_len and val_len respectively + TORRENT_EXTRA_EXPORT void xml_parse(string_view input + , std::function callback); +} + + +#endif diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..9e78b8b --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,225 @@ +.. raw:: html + +
    + +Getting started + +* download_ +* building_ +* tutorial_ +* overview_ +* examples_ +* features_ + +-------- + +Documentation + +* reference_ +* `blog`_ +* `upgrade to 2.0`_ +* `upgrade to 1.2`_ +* contributing_ +* troubleshooting_ +* tuning_ +* fuzzing_ +* `security audit (2020)`_ +* `projects using libtorrent`_ + +-------- + +Contact + +* `mailing list`_ (archive_) +* `report bugs`_ +* `github page`_ + +-------- + +Extensions + +* uTP_ +* `extensions protocol`_ +* `libtorrent plugins`_ +* `streaming`_ +* `DHT extensions`_ +* `DHT security extension`_ +* `DHT store extension`_ +* `UDP tracker protocol`_ +* `HTTP seed`_ +* multi-tracker_ + +-------- + +Bindings + +* python_ +* Java_ +* golang_ +* node_ + +-------- + +* `Introduction, slides`_ + +.. raw:: html + +
    +
    + +.. _download: https://github.com/arvidn/libtorrent/releases +.. _features: features-ref.html +.. _tutorial: tutorial-ref.html +.. _contributing: contributing.html +.. _building: building.html +.. _examples: examples.html +.. _overview: manual-ref.html +.. _reference: reference.html +.. _`upgrade to 2.0`: upgrade_to_2.0-ref.html +.. _`upgrade to 1.2`: upgrade_to_1.2-ref.html +.. _troubleshooting: troubleshooting.html +.. _tuning: tuning-ref.html +.. _fuzzing: fuzzing.html +.. _`security audit (2020)`: security-audit.html +.. _uTP: utp.html +.. _`extensions protocol`: extension_protocol.html +.. _`libtorrent plugins`: reference-Plugins.html +.. _`streaming`: streaming.html +.. _`DHT extensions`: dht_extensions.html +.. _`DHT security extension`: dht_sec.html +.. _`DHT store extension`: dht_store.html +.. _`UDP tracker protocol`: udp_tracker_protocol.html +.. _`HTTP seed`: http://www.getright.com/seedtorrent.html +.. _multi-tracker: https://www.bittorrent.org/beps/bep_0012.html +.. _mailing list: https://lists.sourceforge.net/lists/listinfo/libtorrent-discuss +.. _archive: https://sourceforge.net/p/libtorrent/mailman/libtorrent-discuss/ +.. _`projects using libtorrent`: projects.html +.. _`report bugs`: https://github.com/arvidn/libtorrent/issues +.. _`github page`: https://github.com/arvidn/libtorrent +.. _blog: https://blog.libtorrent.org + +.. _Java: https://github.com/frostwire/frostwire-jlibtorrent/ +.. _python: python_binding.html +.. _golang: https://github.com/steeve/libtorrent-go +.. _node: https://github.com/fanatid/node-libtorrent + +.. _`Introduction, slides`: bittorrent.pdf + +introduction +============ + +libtorrent is a feature complete C++ bittorrent implementation focusing +on efficiency and scalability. It runs on embedded devices as well as +desktops. It boasts a well documented library interface that is easy to +use. It comes with a `simple bittorrent client`__ demonstrating the use of +the library. + +__ client_test.html + +.. image:: img/screenshot_thumb.png + :target: client_test.html + :alt: screenshot of libtorrent's client_test + :class: front-page-screenshot + :width: 400 + :height: 239 + +The main goals of libtorrent are: + +* to be CPU efficient +* to be memory efficient +* to be very easy to use + +getting started +=============== + +The tutorial_ is an introduction to using libtorrent (C++). Also see the +`reference documentation`_. + +.. _`reference documentation`: reference.html + +.. raw:: html + +
    + + bitcoin address for libtorrent donations + +contribute +========== + +If your organization uses libtorrent, please consider supporting its development. +See the contributing_ page for other ways to help out. + +.. raw:: html + +
    + + +
    + + + + + + + +
    +
    + + + +support +======= + +Please direct questions to the `mailing list`__, general libtorrent discussion. + +__ https://lists.sourceforge.net/lists/listinfo/libtorrent-discuss + +You can usually find me as hydri in ``#libtorrent`` on ``irc.freenode.net``. + +license +======= + +libtorrent is released under the BSD-license_. + +.. _BSD-license: https://opensource.org/licenses/bsd-license.php + +This means that you can use the library in your project without having to +release its source code. The only requirement is that you give credit +to the author of the library by including the libtorrent license in your +software or documentation. + +It is however greatly appreciated if additional features are contributed +back to the open source project. Patches can be emailed to the mailing +list or posted to the `bug tracker`_. + +.. _`bug tracker`: https://github.com/arvidn/libtorrent/issues + +acknowledgements +================ + +Written by Arvid Norberg. Copyright |copy| 2003-2018 + +Contributions by Steven Siloti, Alden Torres, Magnus Jonsson, Daniel Wallin and Cory Nelson + +Thanks to Reimond Retz for bug fixes, suggestions and testing + +See github__ for full list of contributors. + +__ https://github.com/arvidn/libtorrent/graphs/contributors + +Thanks to `Umeå University`__ for providing development and test hardware. + +__ http://www.cs.umu.se + +Project is hosted by github__. + +__ https://www.github.com/arvidn/libtorrent + +.. |copy| unicode:: 0xA9 .. copyright sign + +.. raw:: html + +
    + diff --git a/docs/makefile b/docs/makefile new file mode 100644 index 0000000..2942584 --- /dev/null +++ b/docs/makefile @@ -0,0 +1,231 @@ +# this makefile assumes that you have docutils and rst2pdf installed +# (python-docutils) as well as aafigure (python-aafigure) + +ifndef WEB_PATH +WEB_PATH = ~/web/products/libtorrent +endif + +ifndef RST2HTML +RST2HTML:=rst2html +endif + +ifndef AAFIGURE +AAFIGURE=aafigure +endif + +REFERENCE_TARGETS = \ + manual-ref \ + tutorial-ref \ + tuning-ref \ + features-ref \ + upgrade_to_1.2-ref \ + upgrade_to_2.0-ref \ + reference \ + reference-Core \ + reference-DHT \ + reference-Session \ + reference-Torrent_Handle \ + reference-Torrent_Info \ + reference-Trackers \ + reference-PeerClass \ + reference-Torrent_Status \ + reference-Stats \ + reference-Resume_Data \ + reference-Add_Torrent \ + reference-Plugins \ + reference-Create_Torrents \ + reference-Error_Codes \ + reference-Storage \ + reference-Custom_Storage \ + reference-Utility \ + reference-Bencoding \ + reference-Alerts \ + reference-Filter \ + reference-Settings \ + reference-Bdecoding \ + reference-ed25519 + +MANUAL_TARGETS = index \ + udp_tracker_protocol \ + dht_rss \ + dht_store \ + client_test \ + building \ + troubleshooting \ + contributing\ + examples \ + extension_protocol \ + dht_extensions \ + dht_sec \ + python_binding \ + projects \ + utp \ + hacking \ + streaming \ + fuzzing \ + security-audit + +TARGETS = single-page-ref \ + $(MANUAL_TARGETS) \ + $(REFERENCE_TARGETS) + +FIGURES = $(addprefix img/, \ + read_disk_buffers \ + write_disk_buffers \ + hacking \ + utp_stack \ + storage \ + troubleshooting \ + troubleshooting_thumb \ + screenshot_thumb \ + logo-color \ + logo-bw \ + our_delay_base_thumb \ + delays_thumb \ + cwnd_thumb \ +) + +STAGE_IMG = $(addprefix img/, \ + logo-color-text \ + logo-color \ + screenshot \ + bitcoin \ + ip_id_v4 \ + ip_id_v6 \ + hash_distribution \ + complete_bit_prefixes \ + our_delay_base \ + delays \ + cwnd \ +) + +ORIG_HEADERS = $(wildcard ../include/libtorrent/*.hpp) +HEADERS = $(ORIG_HEADERS:../%=%) + +html: $(TARGETS:=.html) $(FIGURES:=.png) todo.html favicon.ico $(HEADERS) + +stage: $(addprefix $(WEB_PATH)/, $(TARGETS:=.html) $(FIGURES:=.png) todo.html favicon.ico style.css $(HEADERS) $(STAGE_IMG:=.png)) + +rst: $(TARGETS:=.rst) todo.html + +pdf: $(TARGETS:=.pdf) $(FIGURES:=.png) + +epub: $(TARGETS:=.epub) $(FIGURES:=.png) + +all: html pdf favicon.ico + +img/logo-color-text.png: img/logo-color-text.svg + convert -background transparent $< -resize 400 $@ + +img/%.png: img/%.svg + convert -background transparent $< -resize 128x128 $@ + +favicon.ico: favicon-16.png favicon-32.png favicon-64.png + icotool -o $@ -c $? + +favicon-16.png: img/logo-color.svg + convert -background transparent $< -resize 16x16 $@ + +favicon-32.png: img/logo-color.svg + convert -background transparent $< -resize 32x32 $@ + +favicon-64.png: img/logo-color.svg + convert -background transparent $< -resize 64x64 $@ + +single-page-ref.rst: gen_reference_doc.py ../include/libtorrent/*.hpp ../include/libtorrent/kademlia/*.hpp settings-ref.rst stats_counters.rst + python3 gen_reference_doc.py --single-page + +settings.rst hunspell/settings.dic: ../include/libtorrent/settings_pack.hpp hunspell/libtorrent.dic + python3 gen_settings_doc.py || { rm $@; exit 1; } + cat hunspell/libtorrent.dic >>hunspell/settings.dic + +stats_counters.rst: ../src/session_stats.cpp ../include/libtorrent/performance_counters.hpp + python3 gen_stats_doc.py || { rm $@; exit 1; } + +manual.rst: stats_counters.rst + touch manual.rst + +%_thumb.png: %.png + convert $< -resize 400 $@ + +img/troubleshooting_thumb.png: img/troubleshooting.png + convert $< -resize 800x800 $@ + +todo.html:gen_todo.py ../src/*.cpp ../include/libtorrent/*.hpp + python3 gen_todo.py + +$(REFERENCE_TARGETS:=.rst) plain_text_out.txt settings-ref.rst:gen_reference_doc.py ../include/libtorrent/*.hpp ../include/libtorrent/kademlia/*.hpp manual.rst tuning.rst tutorial.rst features.rst settings.rst stats_counters.rst hunspell/settings.dic + python3 gen_reference_doc.py --plain-output + +spell-check:plain_text_out.txt $(MANUAL_TARGETS:=.html) manual.rst settings.rst tutorial.rst + python3 filter-rst.py manual.rst >manual-plain.txt + python3 filter-rst.py tutorial.rst >tutorial-plain.txt + python3 filter-rst.py tuning.rst >tuning-plain.txt + python3 filter-rst.py settings.rst >settings-plain.txt + python3 filter-rst.py upgrade_to_1.2.rst >upgrade-1_2-plain.txt + python3 filter-rst.py upgrade_to_2.0.rst >upgrade-2_0-plain.txt + hunspell -i UTF-8 -d hunspell/en_US -p hunspell/libtorrent.dic -l plain_text_out.txt >hunspell-report.txt + hunspell -i UTF-8 -d hunspell/en_US -p hunspell/libtorrent.dic -l manual-plain.txt >>hunspell-report.txt + hunspell -i UTF-8 -d hunspell/en_US -p hunspell/libtorrent.dic -l tutorial-plain.txt >>hunspell-report.txt + hunspell -i UTF-8 -d hunspell/en_US -p hunspell/libtorrent.dic -l tuning-plain.txt >>hunspell-report.txt + hunspell -i UTF-8 -d hunspell/en_US -p hunspell/libtorrent.dic -l upgrade-1_2-plain.txt >>hunspell-report.txt + hunspell -i UTF-8 -d hunspell/en_US -p hunspell/libtorrent.dic -l upgrade-2_0-plain.txt >>hunspell-report.txt + hunspell -i UTF-8 -d hunspell/en_US -p hunspell/settings.dic -l settings-plain.txt >>hunspell-report.txt + hunspell -i UTF-8 -d hunspell/en_US -p hunspell/libtorrent.dic -H -l $(MANUAL_TARGETS:=.html) >>hunspell-report.txt + @if [ -s hunspell-report.txt ]; then echo 'spellcheck failed, fix words or add to dictionary:'; cat hunspell-report.txt; false; fi; + +%.epub:%.rst + rst2epub --exit-status=2 $< $@ + +%.pdf:%.rst + rst2pdf $< -o $@ --stylesheets stylesheet + +%.html:%.rst template.txt template2.txt + $(RST2HTML) --title=libtorrent --exit-status=2 --template=template.txt --stylesheet-path=style.css --link-stylesheet --no-toc-backlinks $< > $@ || { rm $@; exit 1; } + +%.png:%.dot + dot -Tpng $< >$@ || { rm $@; exit 1; } + +%.png:%.diagram + $(AAFIGURE) --scale 0.6 -o $@ $< || { rm $@; exit 1; } + +include/libtorrent/%.hpp: ../include/libtorrent/%.hpp + mkdir -p include/libtorrent >/dev/null + cp $< $@ + +# stage rules + +$(WEB_PATH)/%.html:%.rst template.txt template2.txt + mkdir -p $(WEB_PATH) >/dev/null + $(RST2HTML) --title=libtorrent --exit-status=2 --template=template2.txt --stylesheet-path=style.css --link-stylesheet --no-toc-backlinks $< > $@ || { rm $@; exit 1; } + +$(WEB_PATH)/img/%.png: img/%.png + mkdir -p $(WEB_PATH)/img >/dev/null + cp $< $@ + +$(WEB_PATH)/%.png: %.png + cp $< $@ + +$(WEB_PATH)/%.css: %.css + cp $< $@ + +$(WEB_PATH)/%.svg: %.svg + cp $< $@ + +$(WEB_PATH)/%.html: %.html + cp $< $@ + +$(WEB_PATH)/%.ico: %.ico + cp $< $@ + +$(WEB_PATH)/include/libtorrent/%.hpp: ../include/libtorrent/%.hpp + mkdir -p $(WEB_PATH)/include/libtorrent + cp $< $@ + +$(WEB_PATH)/img/%.png: img/%.png + mkdir -p $(WEB_PATH)/img >/dev/null + cp $< $@ + +clean: + rm -f $(TARGETS:=.html) $(TARGETS:=.pdf) $(FIGURES:=.png) $(REFERENCE_TARGETS:=.rst) settings.rst todo.html reference*.html stats_counters.rst hunspell/settings.dic favicon-16.png favicon-32.png favicon-64.png favicon.ico + diff --git a/docs/manual.rst b/docs/manual.rst new file mode 100644 index 0000000..57531a5 --- /dev/null +++ b/docs/manual.rst @@ -0,0 +1,1325 @@ +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +overview +======== + +The interface of libtorrent consists of a few classes. The main class is +the ``session``, it contains the main loop that serves all torrents. + +The basic usage is as follows: + +* construct a session, possibly passing in the state from a previous session. + use read_session_params() and pass in the resulting session_params object to + the session constructor. +* start extensions (see add_extension()). +* start DHT, LSD, UPnP, NAT-PMP etc (see start_dht(), start_lsd(), start_upnp() + and start_natpmp()). +* parse .torrent-files and add them to the session (see torrent_info, + async_add_torrent() and add_torrent()) +* main loop (see session) + + * poll for alerts (see wait_for_alert(), pop_alerts()) + * handle updates to torrents, (see state_update_alert). + * handle other alerts, (see alert). + * query the session for information (see session::status()). + * add and remove torrents from the session (remove_torrent()) + +* save resume data for all torrent_handles (optional, see + save_resume_data()) +* save session state (see session_state() and write_session_params()) +* destruct session object + +Each class and function is described in this manual, you may want to have a +look at the tutorial_ as well. + +.. _tutorial: tutorial-ref.html + +For a description on how to create torrent files, see create_torrent. + +.. _make_torrent: make_torrent.html + +forward declarations +==================== + +Forward declaring types from the libtorrent namespace is discouraged as it may +break in future releases. Instead include ``libtorrent/fwd.hpp`` for forward +declarations of all public types in libtorrent. + +trouble shooting +================ + +A common problem developers are facing is torrents stopping without explanation. +Here is a description on which conditions libtorrent will stop your torrents, +how to find out about it and what to do about it. + +Make sure to keep track of the paused state, the error state and the upload +mode of your torrents. By default, torrents are auto-managed, which means +libtorrent will pause, resume, scrape them and take them out +of upload-mode automatically. + +Whenever a torrent encounters a fatal error, it will be stopped, and the +``torrent_status::error`` will describe the error that caused it. If a torrent +is auto managed, it is scraped periodically and paused or resumed based on +the number of downloaders per seed. This will effectively seed torrents that +are in the greatest need of seeds. + +If a torrent hits a disk write error, it will be put into upload mode. This +means it will not download anything, but only upload. The assumption is that +the write error is caused by a full disk or write permission errors. If the +torrent is auto-managed, it will periodically be taken out of the upload +mode, trying to write things to the disk again. This means torrent will recover +from certain disk errors if the problem is resolved. If the torrent is not +auto managed, you have to call set_upload_mode() to turn +downloading back on again. + +For a more detailed guide on how to trouble shoot performance issues, see +troubleshooting_ + +.. _troubleshooting: troubleshooting.html + +ABI considerations +================== + +libtorrent maintains a stable ABI for versions with the same major and minor versions. + +e.g. libtorrent-1.2.0 is ABI compatible with libtorrent-1.2.1 but not with libtorrent-1.1 + +network primitives +================== + +There are a few typedefs in the ``libtorrent`` namespace which pulls +in network types from the ``boost::asio`` namespace. These are:: + + using address = boost::asio::ip::address; + using address_v4 = boost::asio::ip::address_v4; + using address_v6 = boost::asio::ip::address_v6; + using boost::asio::ip::tcp; + using boost::asio::ip::udp; + +These are declared in the ```` header. + +The ``using`` statements will give easy access to:: + + tcp::endpoint + udp::endpoint + +Which are the endpoint types used in libtorrent. An endpoint is an address +with an associated port. + +For documentation on these types, please refer to the `asio documentation`_. + +.. _`asio documentation`: https://www.boost.org/doc/libs/1_66_0/doc/html/boost_asio.html + +exceptions +========== + +Many functions in libtorrent have two versions, one that throws exceptions on +errors and one that takes an ``error_code`` reference which is filled with the +error code on errors. + +On exceptions, libtorrent will throw ``boost::system::system_error`` exceptions +carrying an ``error_code`` describing the underlying error. + +translating error codes +----------------------- + +The error_code::message() function will typically return a localized error string, +for system errors. That is, errors that belong to the generic or system category. + +Errors that belong to the libtorrent error category are not localized however, they +are only available in English. In order to translate libtorrent errors, compare the +error category of the ``error_code`` object against ``lt::libtorrent_category()``, +and if matches, you know the error code refers to the list above. You can provide +your own mapping from error code to string, which is localized. In this case, you +cannot rely on ``error_code::message()`` to generate your strings. + +The numeric values of the errors are part of the API and will stay the same, although +new error codes may be appended at the end. + +Here's a simple example of how to translate error codes: + +.. code:: c++ + + std::string error_code_to_string(boost::system::error_code const& ec) + { + if (ec.category() != lt::libtorrent_category()) + { + return ec.message(); + } + // the error is a libtorrent error + + int code = ec.value(); + static const char const* swedish[] = + { + "inget fel", + "en fil i torrenten kolliderar med en fil fran en annan torrent", + "hash check misslyckades", + "torrentfilen ar inte en dictionary", + "'info'-nyckeln saknas eller ar korrupt i torrentfilen", + "'info'-faltet ar inte en dictionary", + "'piece length' faltet saknas eller ar korrupt i torrentfilen", + "torrentfilen saknar namnfaltet", + "ogiltigt namn i torrentfilen (kan vara en attack)", + // ... more strings here + }; + + // use the default error string in case we don't have it + // in our translated list + if (code < 0 || code >= sizeof(swedish)/sizeof(swedish[0])) + return ec.message(); + + return swedish[code]; + } + +magnet links +============ + +Magnet links are URIs that includes an info-hash, a display name and optionally +a tracker url. The idea behind magnet links is that an end user can click on a +link in a browser and have it handled by a bittorrent application, to start a +download, without any .torrent file. + +The format of the magnet URI is: + +**magnet:?xt=urn:btih:** *Base16 encoded info-hash* [ **&dn=** *name of download* ] [ **&tr=** *tracker URL* ]* + +In order to download *just* the metadata (.torrent file) from a magnet link, set +the torrent_flags::upload_mode flag in add_torrent_params before adding the it. + +In this case, when the metadata is received from the swarm, the torrent will +still be running, but it will disconnect the majority of peers (since connections +to peers that already have the metadata are redundant). It will keep seeding the +*metadata* only. + +Note that this doesn't prevent empty files from being created, if the torrent +contains any. If you need to prevent that, you can either +set ``file_priority`` to a long list of zeros (since the number of files is not known +in advance), or set ``save_path`` to an invalid path. + +.torrent file +------------- + +To save a .torrent file from a torrent that was added by magnet link (or added any way really): + +* call save_resume_data() on the torrent_handle, make sure to pass in the ``save_info_dict`` flag +* wait for resume_data_alert +* call write_torrent_file() passing in the add_torrent_params object from the alert. + +The resume data format is very similar to the .torrent file format, and when +including the info-dict in the resume data, the resume file can be used as a +.torrent file (with just a few minor exceptions). + +BitTorrent v2 torrents +====================== + +BitTorrent v2 introduces a number of features outlined in `this blog post`_ as +well as `BEP 52`_. The v2 protocol introduces the possibility to use a merkle +hash tree instead of a flat list of piece hashes. It also supports *hybrid* torrents, +that are both valid classing torrents (v1) as well as valid v2 torrents. Hybrid +torrents contain both a flat list of piece hashes as well as a merkle hash tree. + +.. _`this blog post`: https://blog.libtorrent.org/2020/09/bittorrent-v2/ +.. _`BEP 52`: https://www.bittorrent.org/beps/bep_0052.html + +This introduces a few new error cases. A hybrid torrent may have mismatching v1 +and v2 hashes. Since v2 torrents use SHA-256 and v1 uses SHA-1 the fact that the +hashes are mismatching won't be detectable until the piece has been downloaded. +It results in a ``torrent_inconsistent_hashes`` error. + +A magnet link may contain just a v1 info-hash or a v2 info-hash. If two separate +magnet links, one v1-only and one v2-only, end up resolving to the same hybrid torrent, +both torrent_handle objects are put into an error state of ``duplicate_torrent``. +In this state, one of them has to be removed, and the other one can be resumed, +in order to download the metadata again. + +When a conflict between two torrents occur, a torrent_conflict_alert is posted. +This alert derives from torrent_alert, so is associated with a torrent_handle. +It contains a second torrent_handle referring to the other torrent in the +conflict as well as the metadata that was downloaded. One way to resolve the +conflict is to remove both torrents and add it back using the metadata supplied +in the torrent_conflict_alert. + +When a v1-only or v2-only magnet link resolves to a hybrid torrent, the +info_hash_t object associated with the torrent will be updated to include both +the v1 and v2 info hash. This applies both to torrent_handle::info_hashes() as +well as torrent_info::info_hashes(). + +queuing +======= + +libtorrent supports *queuing*. Queuing is a mechanism to automatically pause and +resume torrents based on certain criteria. The criteria depends on the overall +state the torrent is in (checking, downloading or seeding). + +To opt-out of the queuing logic, make sure your torrents are added with the +torrent_flags::auto_managed bit *cleared* from ``add_torrent_params::flags``. +Or call torrent_handle::unset_flags() and pass in torrent_flags::auto_managed on +the torrent handle. + +The overall purpose of the queuing logic is to improve performance under arbitrary +torrent downloading and seeding load. For example, if you want to download 100 +torrents on a limited home connection, you improve performance by downloading +them one at a time (or maybe two at a time), over downloading them all in +parallel. The benefits are: + +* the average completion time of a torrent is half of what it would be if all + downloaded in parallel. +* The amount of upload capacity is more likely to reach the *reciprocation rate* + of your peers, and is likely to improve your *return on investment* (download + to upload ratio) +* your disk I/O load is likely to be more local which may improve I/O + performance and decrease fragmentation. + +There are fundamentally 3 separate queues: + +* checking torrents +* downloading torrents +* seeding torrents + +Every torrent that is not seeding has a queue number associated with it, this is +its place in line to be started. See torrent_status::queue_position. + +On top of the limits of each queue, there is an over arching limit, set in +settings_pack::active_limit. The auto manager will never start more than this +number of torrents (with one exception described below). Non-auto-managed +torrents are exempt from this logic, and not counted. + +At a regular interval, torrents are checked if there needs to be any +re-ordering of which torrents are active and which are queued. This interval +can be controlled via settings_pack::auto_manage_interval. + +For queuing to work, resume data needs to be saved and restored for all +torrents. See torrent_handle::save_resume_data(). + +queue position +-------------- + +The torrents in the front of the queue are started and the rest are ordered by +their queue position. Any newly added torrent is placed at the end of the queue. +Once a torrent is removed or turns into a seed, its queue position is -1 and all +torrents that used to be after it in the queue, decreases their position in +order to fill the gap. + +The queue positions are always contiguous, in a sequence without any gaps. + +Lower queue position means closer to the front of the queue, and will be +started sooner than torrents with higher queue positions. + +To query a torrent for its position in the queue, or change its position, see: +torrent_handle::queue_position(), torrent_handle::queue_position_up(), +torrent_handle::queue_position_down(), torrent_handle::queue_position_top() +and torrent_handle::queue_position_bottom(). + +checking queue +-------------- + +The checking queue affects torrents in the torrent_status::checking or +torrent_status::allocating state that are auto-managed. + +The checking queue will make sure that (of the torrents in its queue) no more than +settings_pack::active_checking_limit torrents are started at any given time. +Once a torrent completes checking and moves into a different state, the next in +line will be started for checking. + +Any torrent added force-started or force-stopped (i.e. the auto managed flag is +*not* set), will not be subject to this limit and they will all check +independently and in parallel. + +Once a torrent completes the checking of its files, or resume data, it will +be put in the queue for downloading and potentially start downloading immediately. +In order to add a torrent and check its files without starting the download, it +can be added in ``stop_when_ready`` mode. +See add_torrent_params::flag_stop_when_ready. This flag will stop the torrent +once it is ready to start downloading. + +This is conceptually the same as waiting for the ``torrent_checked_alert`` and +then call:: + + h.set_flags(torrent_flags::paused, torrent_flags::paused | torrent_flags::auto_managed); + +With the important distinction that it entirely avoids the brief window where +the torrent is in downloading state. + +downloading queue +----------------- + +Similarly to the checking queue, the downloading queue will make sure that no +more than settings_pack::active_downloads torrents are in the downloading +state at any given time. + +The torrent_status::queue_position is used again here to determine who is next +in line to be started once a downloading torrent completes or is stopped/removed. + +seeding queue +------------- + +The seeding queue does not use torrent_status::queue_position to determine which +torrent to seed. Instead, it estimates the *demand* for the torrent to be +seeded. A torrent with few other seeds and many downloaders is assumed to have a +higher demand of more seeds than one with many seeds and few downloaders. + +It limits the number of started seeds to settings_pack::active_seeds. + +On top of this basic bias, *seed priority* can be controller by specifying a +seed ratio (the upload to download ratio), a seed-time ratio (the download +time to seeding time ratio) and a seed-time (the absolute time to be seeding a +torrent). Until all those targets are hit, the torrent will be prioritized for +seeding. + +Among torrents that have met their seed target, torrents where we don't know of +any other seed take strict priority. + +In order to avoid flapping, torrents that were started less than 30 minutes ago +also have priority to keep seeding. + +Finally, for torrents where none of the above apply, they are prioritized based +on the download to seed ratio. + +The relevant settings to control these limits are +settings_pack::share_ratio_limit, settings_pack::seed_time_ratio_limit and +settings_pack::seed_time_limit. + +queuing options +--------------- + +In addition to simply starting and stopping torrents, the queuing mechanism can +have more fine grained control of the resources used by torrents. + +half-started torrents +..................... + +In addition to the downloading and seeding limits, there are limits on *actions* +torrents perform. The downloading and seeding limits control whether peers are +allowed at all, and if peers are not allowed, torrents are stopped and don't do +anything. If peers are allowed, torrents may: + +1. announce to trackers +2. announce to the DHT +3. announce to local peer discovery (local service discovery) + +Each of those actions are associated with a cost and hence may need a separate +limit. These limits are controlled by settings_pack::active_tracker_limit, +settings_pack::active_dht_limit and settings_pack::active_lsd_limit +respectively. + +Specifically, announcing to a tracker is typically cheaper than +announcing to the DHT. settings_pack::active_dht_limit will limit the number of +torrents that are allowed to announce to the DHT. The highest priority ones +will, and the lower priority ones won't. The will still be considered started +though, and any incoming peers will still be accepted. + +If you do not wish to impose such limits (basically, if you do not wish to have +half-started torrents) make sure to set these limits to -1 (infinite). + +prefer seeds +............ + +In the case where ``active_downloads`` + ``active_seeds`` > ``active_limit``, +there's an ambiguity whether the downloads should be satisfied first or the +seeds. To disambiguate this case, the settings_pack::auto_manage_prefer_seeds +determines whether seeds are preferred or not. + +inactive torrents +................. + +Torrents that are not transferring any bytes (downloading or uploading) have a +relatively low cost to be started. It's possible to exempt such torrents from +the download and seed queues by setting settings_pack::dont_count_slow_torrents +to true. + +Since it sometimes may take a few minutes for a newly started torrent to find +peers and be unchoked, or find peers that are interested in requesting data, +torrents are not considered inactive immediately. There must be an extended +period of no transfers before it is considered inactive and exempt from the +queuing limits. + +fast resume +=========== + +The fast resume mechanism is a way to remember which pieces are downloaded +and where they are put between sessions. You can generate fast resume data by: + +* calling save_resume_data() on torrent_handle. Pass in the ``save_info_dict`` flag. +* wait for resume_data_alert +* save the add_torrent_params object using write_resume_data() + +When adding a torrent using resume data, load it using read_resume_data(). This +populates an add_torrent_params object, which can be passed directly to +add_torrent() or async_add_torrent() on the session object. libtorrent will not +check the piece hashes then, and rely on the information given in the +fast-resume data. The fast-resume data also contains information about which +blocks, in the unfinished pieces, were downloaded, so it will not have to start +from scratch on the partially downloaded pieces. + +To use the fast-resume data you pass it to read_resume_data(), which will return +an add_torrent_params object. Fields of this object can then be altered before +passing it to async_add_torrent() or add_torrent(). +The session will then skip the time consuming checks. It may have to do +the checking anyway, if the fast-resume data is corrupt or doesn't fit the +storage for that torrent. + +file format +----------- + +The file format is a bencoded dictionary containing the following fields: + ++--------------------------+--------------------------------------------------------------+ +| ``file-format`` | string: "libtorrent resume file" | +| | | ++--------------------------+--------------------------------------------------------------+ +| ``file-version`` | integer: 1 | +| | | ++--------------------------+--------------------------------------------------------------+ +| ``info-hash`` | string, the info hash of the torrent this data is saved for. | +| | This is a 20 byte SHA-1 hash of the info section of the | +| | torrent if this is a v1 or v1+v2-hybrid torrent. | ++--------------------------+--------------------------------------------------------------+ +| ``info-hash2`` | string, the v2 info hash of the torrent this data is saved. | +| | for, in case it is a v2 or v1+v2-hybrid torrent. This is a | +| | 32 byte SHA-256 hash of the info section of the torrent. | ++--------------------------+--------------------------------------------------------------+ +| ``pieces`` | A string with piece flags, one character per piece. | +| | Bit 1 means we have that piece. | +| | Bit 2 means we have verified that this piece is correct. | +| | This only applies when the torrent is in seed_mode. | ++--------------------------+--------------------------------------------------------------+ +| ``total_uploaded`` | integer. The number of bytes that have been uploaded in | +| | total for this torrent. | ++--------------------------+--------------------------------------------------------------+ +| ``total_downloaded`` | integer. The number of bytes that have been downloaded in | +| | total for this torrent. | ++--------------------------+--------------------------------------------------------------+ +| ``active_time`` | integer. The number of seconds this torrent has been active. | +| | i.e. not paused. | ++--------------------------+--------------------------------------------------------------+ +| ``seeding_time`` | integer. The number of seconds this torrent has been active | +| | and seeding. | ++--------------------------+--------------------------------------------------------------+ +| ``last_upload`` | integer. The number of seconds since epoch when we last | +| | uploaded payload to a peer on this torrent. | ++--------------------------+--------------------------------------------------------------+ +| ``last_download`` | integer. The number of seconds since epoch when we last | +| | downloaded payload from a peer on this torrent. | ++--------------------------+--------------------------------------------------------------+ +| ``upload_rate_limit`` | integer. In case this torrent has a per-torrent upload rate | +| | limit, this is that limit. In bytes per second. | ++--------------------------+--------------------------------------------------------------+ +| ``download_rate_limit`` | integer. The download rate limit for this torrent in case | +| | one is set, in bytes per second. | ++--------------------------+--------------------------------------------------------------+ +| ``max_connections`` | integer. The max number of peer connections this torrent | +| | may have, if a limit is set. | ++--------------------------+--------------------------------------------------------------+ +| ``max_uploads`` | integer. The max number of unchoked peers this torrent may | +| | have, if a limit is set. | ++--------------------------+--------------------------------------------------------------+ +| ``file_priority`` | list of integers. One entry per file in the torrent. Each | +| | entry is the priority of the file with the same index. | ++--------------------------+--------------------------------------------------------------+ +| ``piece_priority`` | string of bytes. Each byte is interpreted as an integer and | +| | is the priority of that piece. | ++--------------------------+--------------------------------------------------------------+ +| ``seed_mode`` | integer. 1 if the torrent is in seed mode, 0 otherwise. | ++--------------------------+--------------------------------------------------------------+ +| ``upload_mode`` | integer. 1 if the torrent_flags::upload_mode is set. | ++--------------------------+--------------------------------------------------------------+ +| ``share_mode`` | integer. 1 if the torrent_flags::share_mode is set. | ++--------------------------+--------------------------------------------------------------+ +| ``apply_ip_filter`` | integer. 1 if the torrent_flags::apply_ip_filter is set. | ++--------------------------+--------------------------------------------------------------+ +| ``paused`` | integer. 1 if the torrent is paused, 0 otherwise. | ++--------------------------+--------------------------------------------------------------+ +| ``auto_managed`` | integer. 1 if the torrent is auto managed, otherwise 0. | ++--------------------------+--------------------------------------------------------------+ +| ``super_seeding`` | integer. 1 if the torrent_flags::super_seeding is set. | ++--------------------------+--------------------------------------------------------------+ +| ``sequential_download`` | integer. 1 if the torrent is in sequential download mode, | +| | 0 otherwise. | ++--------------------------+--------------------------------------------------------------+ +| ``stop_when_ready`` | integer. 1 if the torrent_flags::stop_when_ready is set. | ++--------------------------+--------------------------------------------------------------+ +| ``disable_dht`` | integer. 1 if the torrent_flags::disable_dht is set. | ++--------------------------+--------------------------------------------------------------+ +| ``disable_lsd`` | integer. 1 if the torrent_flags::disable_lsd is set. | ++--------------------------+--------------------------------------------------------------+ +| ``disable_pex`` | integer. 1 if the torrent_flags::disable_pex is set. | ++--------------------------+--------------------------------------------------------------+ +| ``trackers`` | list of lists of strings. The top level list lists all | +| | tracker tiers. Each second level list is one tier of | +| | trackers. | ++--------------------------+--------------------------------------------------------------+ +| ``mapped_files`` | list of strings. If any file in the torrent has been | +| | renamed, this entry contains a list of all the filenames. | +| | In the same order as in the torrent file. | ++--------------------------+--------------------------------------------------------------+ +| ``url-list`` | list of strings. List of url-seed URLs used by this torrent. | +| | The URLs are expected to be properly encoded and not contain | +| | any illegal url characters. | ++--------------------------+--------------------------------------------------------------+ +| ``httpseeds`` | list of strings. List of HTTP seed URLs used by this torrent.| +| | The URLs are expected to be properly encoded and not contain | +| | any illegal url characters. | ++--------------------------+--------------------------------------------------------------+ +| ``trees`` | list. In case this is a v2 (or v1+v2-hybrid) torrent, this | +| | is an optional list containing the merkle tree nodes we know | +| | of so far, for all files. It's a list of dictionaries, one | +| | entry for each file in the torrent. The entries have the | +| | following structure: | +| | | +| | +--------------+-------------------------------------------+ | +| | | ``hashes`` | string. Sequence of 32 byte (SHA-256) | | +| | | | hashes, representing the nodes in the | | +| | | | merkle hash tree for this file. Some | | +| | | | hashes may be all zeros, if we haven't | | +| | | | downloaded them yet. | | +| | +--------------+-------------------------------------------+ | +| | | ``mask`` | string. When present, a bitmask (of ``0`` | | +| | | | and ``1`` characters, indicating which | | +| | | | hashes of the full tree are included in | | +| | | | the ``hashes`` key. This is used to avoid | | +| | | | storing large numbers of zeros. | | +| | +--------------+-------------------------------------------+ | +| | | ``verified`` | string. This indicates which leaf nodes | | +| | | | in the tree have been verified correct. | | +| | | | There is one character per leaf, ``0`` | | +| | | | means not verified, ``1`` means verified. | | +| | +--------------+-------------------------------------------+ | +| | | ++--------------------------+--------------------------------------------------------------+ +| ``save_path`` | string. The save path where this torrent was saved. This is | +| | especially useful when moving torrents with move_storage() | +| | since this will be updated. | ++--------------------------+--------------------------------------------------------------+ +| ``peers`` | string. This string contains IPv4 and port pairs of peers we | +| | were connected to last session. The endpoints are in compact | +| | representation. 4 bytes IPv4 address followed by 2 bytes | +| | port. Hence, the length of this string should be divisible | +| | by 6. | ++--------------------------+--------------------------------------------------------------+ +| ``banned_peers`` | string. This string has the same format as ``peers`` but | +| | instead represent IPv4 peers that we have banned. | ++--------------------------+--------------------------------------------------------------+ +| ``peers6`` | string. This string contains IPv6 and port pairs of peers we | +| | were connected to last session. The endpoints are in compact | +| | representation. 16 bytes IPv6 address followed by 2 bytes | +| | port. The length of this string should be divisible by 18. | ++--------------------------+--------------------------------------------------------------+ +| ``banned_peers6`` | string. This string has the same format as ``peers6`` but | +| | instead represent IPv6 peers that we have banned. | ++--------------------------+--------------------------------------------------------------+ +| ``info`` | If this field is present, it should be the info-dictionary | +| | of the torrent this resume data is for. Its SHA-1 hash must | +| | match the one in the ``info-hash`` field. When present, | +| | the torrent is loaded from here, meaning the torrent can be | +| | added purely from resume data (no need to load the .torrent | +| | file separately). This may have performance advantages. | ++--------------------------+--------------------------------------------------------------+ +| ``unfinished`` | list of dictionaries. Each dictionary represents an | +| | piece, and has the following layout: | +| | | +| | +-------------+--------------------------------------------+ | +| | | ``piece`` | integer, the index of the piece this entry | | +| | | | refers to. | | +| | +-------------+--------------------------------------------+ | +| | | ``bitmask`` | string, a binary bitmask representing the | | +| | | | blocks that have been downloaded in this | | +| | | | piece. | | +| | +-------------+--------------------------------------------+ | +| | | ``adler32`` | The adler32 checksum of the data in the | | +| | | | blocks specified by ``bitmask``. | | +| | | | | | +| | +-------------+--------------------------------------------+ | +| | | ++--------------------------+--------------------------------------------------------------+ +| ``allocation`` | The allocation mode for the storage. Can be either | +| | ``allocate`` or ``sparse``. | ++--------------------------+--------------------------------------------------------------+ + +storage allocation +================== + +There are two modes in which storage (files on disk) are allocated in libtorrent. + +1. The traditional *full allocation* mode, where the entire files are filled up + with zeros before anything is downloaded. Files are allocated on demand, the + first time anything is written to them. The main benefit of this mode is that + it avoids creating heavily fragmented files. + +2. The *sparse allocation*, sparse files are used, and pieces are downloaded + directly to where they belong. This is the recommended (and default) mode. + +sparse allocation +----------------- + +On filesystems that supports sparse files, this allocation mode will only use +as much space as has been downloaded. + +The main drawback of this mode is that it may create heavily fragmented files. + + * It does not require an allocation pass on startup. + +full allocation +--------------- + +When a torrent is started in full allocation mode, the disk-io thread +will make sure that the entire storage is allocated, and fill any gaps with zeros. +It will of course still check for existing pieces and fast resume data. The main +drawbacks of this mode are: + + * It may take longer to start the torrent, since it will need to fill the files + with zeros. This delay is linear to the size of the download. + + * The download may occupy unnecessary disk space between download sessions. + + * Disk caches usually perform poorly with random access to large files + and may slow down the download some. + +The benefits of this mode are: + + * Downloaded pieces are written directly to their final place in the files and + the total number of disk operations will be fewer and may also play nicer to + the filesystem file allocation, and reduce fragmentation. + + * No risk of a download failing because of a full disk during download, once + all files have been created. + +HTTP seeding +============ + +There are two kinds of HTTP seeding. One with that assumes a smart (and polite) +client and one that assumes a smart server. These are specified in `BEP 19`_ +and `BEP 17`_ respectively. + +libtorrent supports both. In the libtorrent source code and API, BEP 19 URLs +are typically referred to as *url seeds* and BEP 17 URLs are typically referred +to as *HTTP seeds*. + +The libtorrent implementation of `BEP 19`_ assumes that, if the URL ends with a +slash ('/'), the filename should be appended to it in order to request pieces +from that file. The way this works is that if the torrent is a single-file +torrent, only that filename is appended. If the torrent is a multi-file +torrent, the torrent's name '/' the file name is appended. This is the same +directory structure that libtorrent will download torrents into. + +There is limited support for HTTP redirects. In case some files are redirected +to *different hosts*, the files must be piece aligned or padded to be piece +aligned. + +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html + +piece picker +============ + +The piece picker in libtorrent has the following features: + +* rarest first +* sequential download +* random pick +* reverse order picking +* parole mode +* prioritize partial pieces +* prefer whole pieces +* piece affinity by speed category +* piece priorities + +internal representation +----------------------- + +It is optimized by, at all times, keeping a list of pieces ordered by rarity, +randomly shuffled within each rarity class. This list is organized as a single +vector of contiguous memory in RAM, for optimal memory locality and to eliminate +heap allocations and frees when updating rarity of pieces. + +Expensive events, like a peer joining or leaving, are evaluated lazily, since +it's cheaper to rebuild the whole list rather than updating every single piece +in it. This means as long as no blocks are picked, peers joining and leaving is +no more costly than a single peer joining or leaving. Of course the special +cases of peers that have all or no pieces are optimized to not require +rebuilding the list. + +picker strategy +--------------- + +The normal mode of the picker is of course *rarest first*, meaning pieces that +few peers have are preferred to be downloaded over pieces that more peers have. +This is a fundamental algorithm that is the basis of the performance of +bittorrent. However, the user may set the piece picker into sequential download +mode. This mode simply picks pieces sequentially, always preferring lower piece +indices. + +When a torrent starts out, picking the rarest pieces means increased risk that +pieces won't be completed early (since there are only a few peers they can be +downloaded from), leading to a delay of having any piece to offer to other +peers. This lack of pieces to trade, delays the client from getting started +into the normal tit-for-tat mode of bittorrent, and will result in a long +ramp-up time. The heuristic to mitigate this problem is to, for the first few +pieces, pick random pieces rather than rare pieces. The threshold for when to +leave this initial picker mode is determined by +settings_pack::initial_picker_threshold. + +reverse order +------------- + +An orthogonal setting is *reverse order*, which is used for *snubbed* peers. +Snubbed peers are peers that appear very slow, and might have timed out a piece +request. The idea behind this is to make all snubbed peers more likely to be +able to do download blocks from the same piece, concentrating slow peers on as +few pieces as possible. The reverse order means that the most common pieces are +picked, instead of the rarest pieces (or in the case of sequential download, +the last pieces, instead of the first). + +parole mode +----------- + +Peers that have participated in a piece that failed the hash check, may be put +in *parole mode*. This means we prefer downloading a full piece from this +peer, in order to distinguish which peer is sending corrupt data. Whether to do +this is or not is controlled by settings_pack::use_parole_mode. + +In parole mode, the piece picker prefers picking one whole piece at a time for +a given peer, avoiding picking any blocks from a piece any other peer has +contributed to (since that would defeat the purpose of parole mode). + +prioritize partial pieces +------------------------- + +This setting determines if partially downloaded or requested pieces should +always be preferred over other pieces. The benefit of doing this is that the +number of partial pieces is minimized (and hence the turn-around time for +downloading a block until it can be uploaded to others is minimized). It also +puts less stress on the disk cache, since fewer partial pieces need to be kept +in the cache. Whether or not to enable this is controlled by +setting_pack::prioritize_partial_pieces. + +The main benefit of not prioritizing partial pieces is that the rarest first +algorithm gets to have more influence on which pieces are picked. The picker is +more likely to truly pick the rarest piece, and hence improving the performance +of the swarm. + +This setting is turned on automatically whenever the number of partial pieces +in the piece picker exceeds the number of peers we're connected to times 1.5. +This is in order to keep the waste of partial pieces to a minimum, but still +prefer rarest pieces. + +prefer whole pieces +------------------- + +The *prefer whole pieces* setting makes the piece picker prefer picking entire +pieces at a time. This is used by web connections (both http seeding +standards), in order to be able to coalesce the small bittorrent requests to +larger HTTP requests. This significantly improves performance when downloading +over HTTP. + +It is also used by peers that are downloading faster than a certain threshold. +The main advantage is that these peers will better utilize the other peer's +disk cache, by requesting all blocks in a single piece, from the same peer. + +This threshold is controlled by the settings_pack::whole_pieces_threshold +setting. + +*TODO: piece priorities* + +Multi-homed hosts +================= + +The settings_pack::listen_interfaces setting is used to specify which interfaces/IP addresses +to listen on, and accept incoming connections via. + +Each item in ``listen_interfaces`` is an IP address or a device name, followed +by a listen port number. Each item (called ``listen_socket_t``) will have the +following objects associated with it: + +* a listen socket accepting incoming TCP connections +* a UDP socket: + 1. to accept incoming uTP connections + 2. to run a DHT instance on + 3. to announce to UDP trackers from + 4. a SOCKS5 UDP tunnel (if applicable) +* a listen address and netmask, describing the network the sockets are bound to +* a Local service discovery object, broadcasting to the specified subnet +* a NAT-PMP/PCP port mapper (if applicable), to map ports on the gateway + for the specified subnet. +* a UPnP port mapper (if applicable), to map ports on any +* ``InternetGatewayDevice`` found on the specified local subnet. + +A ``listen_socket_t`` item may be specified to only be a local network (with +the ``l`` suffix). Such listen socket will only be used to talk to peers and +trackers within the same local network. The netmask defining the network is +queried from the operating system by enumerating network interfaces. + +An item that's considered to be "local network" will not be used to announce to +trackers outside of that network. For example, ``10.0.0.2:6881l`` is marked as "local +network" and it will only be used as the source address announcing to a tracker +if the tracker is also within the same local network (e.g. ``10.0.0.0/8``). + +The NAT-PMP/PCP and UPnP port mapper objects are only created for networks that +are expected to be externally available (i.e. not "local network"). If there are +multiple subnets connected to the internet, they will have separate port mappings. + +expanding device names +---------------------- + +If a device name is specified, libtorrent will expand it to the IP addresses +associated with that device, but also retain the device name in order to attempt +to bind the listen sockets to that specific device. + +expanding unspecified addresses +------------------------------- + +If an IP address is the *unspecified* address (i.e. ``0.0.0.0`` or ``::``), +libtorrent will expand it to specific IP addresses. This expansion will +enumerate all addresses it can find for the corresponding address family. +The expanded IP addresses are considered "local network" if any of the following +conditions are met: + +* the IP address is in a known link-local range +* the IP address is in a known loopback range +* the item the IP address was expanded from was marked local (``l``) +* the network interface has the ``loopback`` flag set +* NONE of the following conditions are met: + 1. the IP address is in a globally reachable IP address range + 2. the network interface has the ``point-to-point`` flag set + 3. the routing table contains a route for at least one global internet address + (e.g. a default route) for the address family of the expanded IP that points to + the network interface of the expanded IP. + +routing +------- + +A ``listen_socket_t`` item is considered able to route to a destination address +if any of these hold: + +* the destination address falls inside its subnet (i.e. interface address masked + by netmask is the same as the destination address masked by the netmask). +* the ``listen_socket_t`` does not have the "local network" flag set, and the + address family matches the destination address. + +The ability to route to an address is used when determining whether to announce +to a tracker from a ``listen_socket_t`` and whether to open a SOCKS5 UDP tunnel +for a ``listen_socket_t``. + +Note that the actual IP stack routing table is not considered for this purpose. +This mechanism is to determine which IP addresses should be announced to trackers. + +tracker announces +----------------- + +Trackers are announced to from all network interfaces listening for incoming +connections. However, interfaces that cannot be used to reach the tracker, such +as loopback, are not used as the source address for announces. A +``listen_socket_t`` item that can route to at least one of the tracker IP +addresses will be used as the source address for an announce. Each such item +will also have an announce_endpoint item associated with it, in the tracker +list. + +If a tracker can be reached on a loopback address, then the loopback interface +*will* be used to announce to that tracker. But under normal circumstances, +loopback will not be used for announcing to trackers. + +For more details, see `BEP 7`_. + +.. _`BEP 7`: https://www.bittorrent.org/beps/bep_0007.html + +SOCKS5 UDP tunnels +------------------ + +When using a SOCKS5 proxy, each interface that can route to one of the SOCKS5 +proxy's addresses will be used to open a UDP tunnel, via that proxy. For +example, if a client has both IPv4 and IPv6 connectivity, but the socks5 proxy +only resolves to IPv4, only the IPv4 address will have a UDP tunnel. In that case, +the IPv6 connection will not be used, since it cannot use the proxy. + +rate based choking +================== + +libtorrent supports a choking algorithm that automatically determines the number +of upload slots (unchoke slots) based on the upload rate to peers. It is +controlled by the settings_pack::choking_algorithm setting. The +settings_pack::unchoke_slots_limit is ignored in this mode. + +The algorithm is designed to stay stable, and not oscillate the number of upload +slots. + +The initial rate threshold is set to settings_pack::rate_choker_initial_threshold. + +It sorts all peers by on the rate at which we are uploading to them. + +1. Compare the fastest peer against the initial threshold. +2. Increment the threshold by 2 kiB/s. +3. The next fastest peer is compared against the threshold. + If the peer rate is higher than the threshold. goto 2 +4. Terminate. The number of peers visited is the number of unchoke slots, but + never less than 2. + +In other words, the more upload slots you have, the higher rate does the slowest +unchoked peer upload at in order to open another slot. + +predictive piece announce +========================= + +In order to improve performance, libtorrent supports a feature called +``predictive piece announce``. When enabled, it will make libtorrent announce +that we have pieces to peers, before we truly have them. The most important +case is to announce a piece as soon as it has been downloaded and passed the +hash check, but not yet been written to disk. In this case, there is a risk the +piece will fail to be written to disk, in which case we won't have the piece +anymore, even though we announced it to peers. + +The other case is when we're very close to completing the download of a piece +and assume it will pass the hash check, we can announce it to peers to make it +available one round-trip sooner than otherwise. This lets libtorrent start +uploading the piece to interested peers immediately when the piece complete, +instead of waiting one round-trip for the peers to request it. + +This makes for the implementation slightly more complicated, since piece will +have more states and more complicated transitions. For instance, a piece could +be: + +1. hashed but not fully written to disk +2. fully written to disk but not hashed +3. not fully downloaded +4. downloaded and hash checked + +Once a piece is fully downloaded, the hash check could complete before any of +the write operations or it could complete after all write operations are +complete. + +peer classes +============ + +The peer classes feature in libtorrent allows a client to define custom groups +of peers and rate limit them individually. Each such group is called a *peer +class*. There are a few default peer classes that are always created: + +* global - all peers belong to this class, except peers on the local network +* local peers - all peers on the local network belongs to this class TCP peers +* tcp class - all peers connected over TCP belong to this class + +The TCP peers class is used by the uTP/TCP balancing logic, if it's enabled, to +throttle TCP peers. The global and local classes are used to adjust the global +rate limits. + +When the rate limits are adjusted for a specific torrent, a class is created +implicitly for that torrent. + +The default peer class IDs are defined as enums in the ``session`` class: + +.. code:: c++ + + enum { + global_peer_class_id, + tcp_peer_class_id, + local_peer_class_id + }; + +The default peer classes are automatically created on session startup, and +configured to apply to each respective type of connection. There's nothing +preventing a client from reconfiguring the peer class ip- and type filters +to disable or customize which peers they apply to. See set_peer_class_filter() +and set_peer_class_type_filter(). + +A peer class can be considered a more general form of *labels* that some +clients have. Peer classes however are not just applied to torrents, but +ultimately the peers. + +Peer classes can be created with the create_peer_class() call (on the session +object), and deleted with the delete_peer_class() call. + +Peer classes are configured with the set_peer_class() get_peer_class() calls. + +Custom peer classes can be assigned based on the peer's IP address or the type +of transport protocol used. See set_peer_class_filter() and +set_peer_class_type_filter() for more information. + +peer class examples +------------------- + +Here are a few examples of common peer class operations. + +To make the global rate limit apply to local peers as well, update the IP-filter +based peer class assignment: + +.. code:: c++ + + std::uint32_t const mask = 1 << lt::session::global_peer_class_id; + ip_filter f; + + // for every IPv4 address, assign the global peer class + f.add_rule(make_address("0.0.0.0"), make_address("255.255.255.255"), mask); + + // for every IPv6 address, assign the global peer class + f.add_rule(make_address("::") + , make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + , mask); + ses.set_peer_class_filter(f); + +To make uTP sockets exempt from rate limiting: + +.. code:: c++ + + peer_class_type_filter flt = ses.get_peer_class_type_filter(); + // filter out the global and local peer class for uTP sockets, if these + // classes are set by the IP filter + flt.disallow(peer_class_type_filter::utp_socket, session::global_peer_class_id); + flt.disallow(peer_class_type_filter::utp_socket, session::local_peer_class_id); + + // this filter should not add the global or local peer class to utp sockets + flt.remove(peer_class_type_filter::utp_socket, session::global_peer_class_id); + flt.remove(peer_class_type_filter::utp_socket, session::local_peer_class_id); + + ses.set_peer_class_type_filter(flt); + +To make all peers on the internal network not subject to throttling: + +.. code:: c++ + + std::uint32_t const mask = 1 << lt::session::global_peer_class_id; + ip_filter f; + + // for every IPv4 address, assign the global peer class + f.add_rule(make_address("0.0.0.0"), make_address("255.255.255.255"), mask); + + // for every address on the local metwork, set the mask to 0 + f.add_rule(make_address("10.0.0.0"), make_address("10.255.255.255"), 0); + ses.set_peer_class_filter(f); + +SSL torrents +============ + +Torrents may have an SSL root (CA) certificate embedded in them. Such torrents +are called *SSL torrents*. An SSL torrent talks to all bittorrent peers over +SSL. The protocols are layered like this: + +.. image:: img/utp_stack.png + :class: bw + :align: right + +During the SSL handshake, both peers need to authenticate by providing a +certificate that is signed by the CA certificate found in the .torrent file. +These peer certificates are expected to be provided to peers through some other +means than bittorrent. Typically by a peer generating a certificate request +which is sent to the publisher of the torrent, and the publisher returning a +signed certificate. + +In libtorrent, set_ssl_certificate() in torrent_handle is used to tell +libtorrent where to find the peer certificate and the private key for it. When +an SSL torrent is loaded, the torrent_need_cert_alert is posted to remind the +user to provide a certificate. + +A peer connecting to an SSL torrent MUST provide the *SNI* TLS extension +(server name indication). The server name is the hex encoded info-hash of the +torrent to connect to. This is required for the client accepting the connection +to know which certificate to present. + +SSL connections are accepted on a separate socket from normal bittorrent +connections. To enable support for SSL torrents, add a listen interface to the +settings_pack::listen_interfaces setting with the ``s`` suffix. For example:: + + 0.0.0.0:6881,0.0.0.0:6882s + +That will listen for normal bittorrent connections on port 6881 and for SSL +torrent connections on port 6882. + +This feature is only available if libtorrent is built with SSL torrent support +(``TORRENT_SSL_PEERS``) and requires at least OpenSSL version 1.0, since it +needs SNI support. + +Peer certificates must have at least one *SubjectAltName* field of type +DNSName. At least one of the fields must *exactly* match the name of the +torrent. This is a byte-by-byte comparison, the UTF-8 encoding must be +identical (i.e. there's no unicode normalization going on). This is the +recommended way of verifying certificates for HTTPS servers according to `RFC +2818`_. Note the difference that for torrents only *DNSName* fields are taken +into account (not IP address fields). The most specific (i.e. last) *Common +Name* field is also taken into account if no *SubjectAltName* did not match. + +If any of these fields contain a single asterisk ("*"), the certificate is +considered covering any torrent, allowing it to be reused for any torrent. + +The purpose of matching the torrent name with the fields in the peer +certificate is to allow a publisher to have a single root certificate for all +torrents it distributes, and issue separate peer certificates for each torrent. +A peer receiving a certificate will not necessarily be able to access all +torrents published by this root certificate (only if it has a "star cert"). + +.. _`RFC 2818`: https://www.ietf.org/rfc/rfc2818.txt + +testing +------- + +To test incoming SSL connections to an SSL torrent, one can use the following +*openssl* command:: + + openssl s_client -cert .pem -key .pem -CAfile \ + .pem -debug -connect 127.0.0.1:4433 -tls1 -servername + +To create a root certificate, the Distinguished Name (*DN*) is not taken into +account by bittorrent peers. You still need to specify something, but from +libtorrent's point of view, it doesn't matter what it is. libtorrent only makes +sure the peer certificates are signed by the correct root certificate. + +One way to create the certificates is to use the ``CA.sh`` script that comes +with openssl, like this (don't forget to enter a common Name for the +certificate):: + + CA.sh -newca + CA.sh -newreq + CA.sh -sign + +The torrent certificate is located in ``./demoCA/private/demoCA/cacert.pem``, +this is the pem file to include in the .torrent file. + +The peer's certificate is located in ``./newcert.pem`` and the certificate's +private key in ``./newkey.pem``. + +session statistics +================== + +libtorrent provides a mechanism to query performance and statistics counters +from its internals. + +The statistics consists of two fundamental types. *counters* and *gauges*. A +counter is a monotonically increasing value, incremented every time some event +occurs. For example, every time the network thread wakes up because a socket +became readable will increment a counter. Another example is every time a +socket receives *n* bytes, a counter is incremented by *n*. + +*Counters* are the most flexible of metrics. It allows the program to sample +the counter at any interval, and calculate average rates of increments to the +counter. Some events may be rare and need to be sampled over a longer period in +order to get useful rates, where other events may be more frequent and evenly +distributed that sampling it frequently yields useful values. Counters also +provides accurate overall counts. For example, converting samples of a download +rate into a total transfer count is not accurate and takes more samples. +Converting an increasing counter into a rate is easy and flexible. + +*Gauges* measure the instantaneous state of some kind. This is used for metrics +that are not counting events or flows, but states that can fluctuate. For +example, the number of torrents that are currently being downloaded. + +It's important to know whether a value is a counter or a gauge in order to +interpret it correctly. In order to query libtorrent for which counters and +gauges are available, call session_stats_metrics(). This will return metadata +about the values available for inspection in libtorrent. It will include +whether a value is a counter or a gauge. The key information it includes is the +index used to extract the actual measurements for a specific counter or gauge. + +In order to take a sample, call post_session_stats() in the session object. +This will result in a session_stats_alert being posted. In this alert object, +there is an array of values, these values make up the sample. The value index +in the stats metric indicates which index the metric's value is stored in. + +The mapping between metric and value is not stable across versions of +libtorrent. Always query the metrics first, to find out the index at which the +value is stored, before interpreting the values array in the +session_stats_alert. The mapping will *not* change during the runtime of your +process though, it's tied to a specific libtorrent version. You only have to +query the mapping once on startup (or every time ``libtorrent.so`` is loaded, +if it's done dynamically). + +The available stats metrics are: + +.. include:: stats_counters.rst + +glossary +======== + +The libtorrent documentation use words that are bittorrent terms of art. This +section defines some of these words. For an overview of what bittorrent is and +how it works, see these slides_. For an introduction to the bittorrent DHT, see +`this presentation`_. + +.. _slides: bittorrent.pdf +.. _`this presentation`: https://vimeo.com/56044595 + +announce + The act of telling a tracker or the DHT network about the existence of + oneself and how other peers can connect, by specifying port one is listening + on. + +block + A subset of a piece. Almost always 16 kiB of payload, unless the piece size is + smaller. This is the granularity file payload is requested from peers on the + network. + +DHT + The distributed hash table is a cross-swarm, world-wide network of bittorrent + peers. It's loosely connected, implementing the Kademlia protocol. Its purpose + is to act as a tracker. Peers can announce their presence to nodes on the DHT + and other peers can discover them to join the swarm. + +HTTP tracker + A tracker that uses the HTTP protocol for announces. + +info dictionary + The subset of a torrent file that describes piece hashes and file names. This + is the only mandatory part necessary to join the swarm (network of peers) for + the torrent. + +info hash + The hash of the info dictionary. This uniquely identifies a torrent and is + used by the protocol to ensure peers talking to each other agree on which swarm + they are participating in. Sometimes spelled info-hash. + +leecher + A peer that is still interested in downloading more pieces for the torrent. + It is not a seed. + +magnet link + A URI containing the info hash for a torrent, allowing peers to join its + swarm. May optionally contain a display name, trackers and web seeds. + Typically magnet links rely on peers joining the swarm via the DHT. + +metadata + Synonymous to a torrent file + +peer + A computer running bittorrent client software that participates in the network + for a particular torrent/set of files. + +piece + The smallest number of bytes that can be validated when downloading (no + longer the case in bittorrent V2). The smallest part of the files that can be + advertised to other peers. The size of a piece is determined by the info + dictionary inside the torrent file. + +seed + A computer running bittorrent client software that has the complete files for + a specific torrent, able to share any piece for that file with other peers in + the network + +swarm + The network of peers participating in sharing and downloading of a specific torrent. + +torrent + May refer to a torrent file or the swarm (network of peers) created around + the torrent file. + +torrent file + A file ending in .torrent describing the content of a set of files (but not + containing the content). Importantly, it contains hashes of all files, split + up into pieces. It may optionally contain references to trackers and nodes on + the DHT network to aid peers in joining the network of peers sharing + these files. + +tracker + A server peers can announce to and receive other peers back belonging to the + same swarm. Trackers are used to introduce peers to each other, within a swarm. + When announcing, the info hash of the torrent is included. Trackers can + introduce peers to any info-hash that's specified, given other peers also use + the same tracker. Some trackers restrict which info hashes they support based + on a white list. + +UDP tracker + A tracker that uses a UDP based protocol for announces. + +web seed + A web server that is acting a seed, providing access to all pieces of all + files over HTTP. This is an extension that client software may or may not + support. + diff --git a/docs/plain_text_out.txt b/docs/plain_text_out.txt new file mode 100644 index 0000000..fbd64f0 --- /dev/null +++ b/docs/plain_text_out.txt @@ -0,0 +1,6862 @@ + +Not an error + +expected digit in bencoded string + +expected colon in bencoded string + +unexpected end of file in bencoded string + +expected value (list, dict, int or string) in bencoded string + +bencoded recursion depth limit exceeded + +bencoded item count limit exceeded + +integer overflow + +the number of error codes + +libtorrent uses boost.system's ``error_code`` class to represent +errors. libtorrent has its own error category bdecode_category() +with the error codes defined by error_code_enum. + +creates a default constructed node, it will have the type ``none_t``. + +For owning nodes, the copy will create a copy of the tree, but the +underlying buffer remains the same. + +uninitialized or default constructed. This is also used +to indicate that a node was not found in some cases. + +a dictionary node. The ``dict_find_`` functions are valid. + +a list node. The ``list_`` functions are valid. + +a string node, the ``string_`` functions are valid. + +an integer node. The ``int_`` functions are valid. + +the types of bdecoded nodes + +the type of this node. See type_t. + +returns true if type() != none_t. + +return a non-owning reference to this node. This is useful to refer to +the root node without copying it in assignments. + +returns the buffer and length of the section in the original bencoded +buffer where this node is defined. For a dictionary for instance, this +starts with ``d`` and ends with ``e``, and has all the content of the +dictionary in between. +the ``data_offset()`` function returns the byte-offset to this node in, +starting from the beginning of the buffer that was parsed. + +functions with the ``list_`` prefix operate on lists. These functions are +only valid if ``type()`` == ``list_t``. ``list_at()`` returns the item +in the list at index ``i``. ``i`` may not be greater than or equal to the +size of the list. ``size()`` returns the size of the list. + +Functions with the ``dict_`` prefix operates on dictionaries. They are +only valid if ``type()`` == ``dict_t``. In case a key you're looking up +contains a 0 byte, you cannot use the 0-terminated string overloads, +but have to use ``string_view`` instead. ``dict_find_list`` will return a +valid ``bdecode_node`` if the key is found _and_ it is a list. Otherwise +it will return a default-constructed bdecode_node. + +Functions with the ``_value`` suffix return the value of the node +directly, rather than the nodes. In case the node is not found, or it has +a different type, a default value is returned (which can be specified). + +``dict_at()`` returns the (key, value)-pair at the specified index in a +dictionary. Keys are only allowed to be strings. ``dict_at_node()`` also +returns the (key, value)-pair, but the key is returned as a +``bdecode_node`` (and it will always be a string). + +this function is only valid if ``type()`` == ``int_t``. It returns the +value of the integer. + +these functions are only valid if ``type()`` == ``string_t``. They return +the string values. Note that ``string_ptr()`` is *not* 0-terminated. +``string_length()`` returns the number of bytes in the string. +``string_offset()`` returns the byte offset from the start of the parsed +bencoded buffer this string can be found. + +resets the ``bdecoded_node`` to a default constructed state. If this is +an owning node, the tree is freed and all child nodes are invalidated. + +Swap contents. + +preallocate memory for the specified numbers of tokens. This is +useful if you know approximately how many tokens are in the file +you are about to parse. Doing so will save realloc operations +while parsing. You should only call this on the root node, before +passing it in to bdecode(). + +this buffer *MUST* be identical to the one originally parsed. This +operation is only defined on owning root nodes, i.e. the one passed in to +decode(). + +returns true if there is a non-fatal error in the bencoding of this node +or its children + +Sometimes it's important to get a non-owning reference to the root node ( +to be able to copy it as a reference for instance). For that, use the +non_owning() member function. + +There are 5 different types of nodes, see type_t. + +print the bencoded structure in a human-readable format to a string +that's returned. + +This function decodes/parses bdecoded data (for example a .torrent file). +The data structure is returned in the ``ret`` argument. the buffer to parse +is specified by the ``start`` of the buffer as well as the ``end``, i.e. one +byte past the end. If the buffer fails to parse, the function returns a +non-zero value and fills in ``ec`` with the error code. The optional +argument ``error_pos``, if set to non-nullptr, will be set to the byte offset +into the buffer where the parse failure occurred. + +``depth_limit`` specifies the max number of nested lists or dictionaries are +allowed in the data structure. (This affects the stack usage of the +function, be careful not to set it too high). + +``token_limit`` is the max number of tokens allowed to be parsed from the +buffer. This is simply a sanity check to not have unbounded memory usage. + +The resulting ``bdecode_node`` is an *owning* node. That means it will +be holding the whole parsed tree. When iterating lists and dictionaries, +those ``bdecode_node`` objects will simply have references to the root or +owning ``bdecode_node``. If the root node is destructed, all other nodes +that refer to anything in that tree become invalid. + +However, the underlying buffer passed in to this function (``start``, ``end``) +must also remain valid while the bdecoded tree is used. The parsed tree +produced by this function does not copy any data out of the buffer, but +simply produces references back into it. + +This function will encode data to bencoded form. + +The entry_ class is the internal representation of the bencoded data +and it can be used to retrieve information, an entry_ can also be build by +the program and given to ``bencode()`` to encode it into the ``OutIt`` +iterator. + +``OutIt`` is an OutputIterator_. It's a template and usually +instantiated as ostream_iterator_ or back_insert_iterator_. This +function assumes the value_type of the iterator is a ``char``. +In order to encode entry ``e`` into a buffer, do:: + + std::vector buf; + bencode(std::back_inserter(buf), e); + + + + + + + + + + + + + + + + +See RFC 6887 Section 7.4 + + +returns true if this handle refers to a valid session object. If the +session has been destroyed, all session_handle objects will expire and +not be valid. + +saves settings (i.e. the settings_pack) + +saves dht state such as nodes and node-id, possibly accelerating +joining the DHT if provided at next session startup. + +load or save state from plugins + +load or save the IP filter set on the session + +returns the current session state. This can be passed to +write_session_params() to save the state to disk and restored using +read_session_params() when constructing a new session. The kind of +state that's included is all settings, the DHT routing table, possibly +plugin-specific state. +the flags parameter can be used to only save certain parts of the +session state + + these calls are potentially expensive and won't scale well with + lots of torrents. If you're concerned about performance, consider + using ``post_torrent_updates()`` instead. + +``get_torrent_status`` returns a vector of the torrent_status for +every torrent which satisfies ``pred``, which is a predicate function +which determines if a torrent should be included in the returned set +or not. Returning true means it should be included and false means +excluded. The ``flags`` argument is the same as to +torrent_handle::status(). Since ``pred`` is guaranteed to be +called for every torrent, it may be used to count the number of +torrents of different categories as well. + +``refresh_torrent_status`` takes a vector of torrent_status structs +(for instance the same vector that was returned by +get_torrent_status() ) and refreshes the status based on the +``handle`` member. It is possible to use this function by first +setting up a vector of default constructed ``torrent_status`` objects, +only initializing the ``handle`` member, in order to request the +torrent status for multiple torrents in a single call. This can save a +significant amount of time if you have a lot of torrents. + +Any torrent_status object whose ``handle`` member is not referring to +a valid torrent are ignored. + +The intended use of these functions is to start off by calling +``get_torrent_status()`` to get a list of all torrents that match your +criteria. Then call ``refresh_torrent_status()`` on that list. This +will only refresh the status for the torrents in your list, and thus +ignore all other torrents you might be running. This may save a +significant amount of time, especially if the number of torrents you're +interested in is small. In order to keep your list of interested +torrents up to date, you can either call ``get_torrent_status()`` from +time to time, to include torrents you might have become interested in +since the last time. In order to stop refreshing a certain torrent, +simply remove it from the list. + +This functions instructs the session to post the state_update_alert, +containing the status of all torrents whose state changed since the +last time this function was called. + +Only torrents who has the state subscription flag set will be +included. This flag is on by default. See add_torrent_params. +the ``flags`` argument is the same as for torrent_handle::status(). +see status_flags_t in torrent_handle. + +This function will post a session_stats_alert object, containing a +snapshot of the performance counters from the internals of libtorrent. +To interpret these counters, query the session via +session_stats_metrics(). + +For more information, see the session-statistics_ section. + +This will cause a dht_stats_alert to be posted. + +set the DHT state for the session. This will be taken into account the +next time the DHT is started, as if it had been passed in via the +session_params on startup. + +``find_torrent()`` looks for a torrent with the given info-hash. In +case there is such a torrent in the session, a torrent_handle to that +torrent is returned. In case the torrent cannot be found, an invalid +torrent_handle is returned. + +See ``torrent_handle::is_valid()`` to know if the torrent was found or +not. + +``get_torrents()`` returns a vector of torrent_handles to all the +torrents currently in the session. + +You add torrents through the add_torrent() function where you give an +object with all the parameters. The add_torrent() overloads will block +until the torrent has been added (or failed to be added) and returns +an error code and a torrent_handle. In order to add torrents more +efficiently, consider using async_add_torrent() which returns +immediately, without waiting for the torrent to add. Notification of +the torrent being added is sent as add_torrent_alert. + +The ``save_path`` field in add_torrent_params must be set to a valid +path where the files for the torrent will be saved. Even when using a +custom storage, this needs to be set to something. If the save_path +is empty, the call to add_torrent() will throw a system_error +exception. + +The overload that does not take an error_code throws an exception on +error and is not available when building without exception support. +The torrent_handle returned by add_torrent() can be used to retrieve +information about the torrent's progress, its peers etc. It is also +used to abort a torrent. + +If the torrent you are trying to add already exists in the session (is +either queued for checking, being checked or downloading) +``add_torrent()`` will throw system_error which derives from +``std::exception`` unless duplicate_is_error is set to false. In that +case, add_torrent() will return the handle to the existing torrent. + +The add_torrent_params class has a flags field. It can be used to +control what state the new torrent will be added in. Common flags to +want to control are torrent_flags::paused and +torrent_flags::auto_managed. In order to add a magnet link that will +just download the metadata, but no payload, set the +torrent_flags::upload_mode flag. + +Special consideration has to be taken when adding hybrid torrents +(i.e. torrents that are BitTorrent v2 torrents that are backwards +compatible with v1). For more details, see BitTorrent-v2-torrents_. + +Pausing the session has the same effect as pausing every torrent in +it, except that torrents will not be resumed by the auto-manage +mechanism. Resuming will restore the torrents to their previous paused +state. i.e. the session pause state is separate from the torrent pause +state. A torrent is inactive if it is paused or if the session is +paused. + +``is_dht_running()`` returns true if the DHT support has been started +and false otherwise. + +``set_dht_storage`` set a dht custom storage constructor function +to be used internally when the dht is created. + +Since the dht storage is a critical component for the dht behavior, +this function will only be effective the next time the dht is started. +If you never touch this feature, a default map-memory based storage +is used. + +If you want to make sure the dht is initially created with your +custom storage, create a session with the setting +``settings_pack::enable_dht`` to false, set your constructor function +and call ``apply_settings`` with ``settings_pack::enable_dht`` to true. + +``add_dht_node`` takes a host name and port pair. That endpoint will be +pinged, and if a valid DHT reply is received, the node will be added to +the routing table. + +query the DHT for an immutable item at the ``target`` hash. +the result is posted as a dht_immutable_item_alert. + +query the DHT for a mutable item under the public key ``key``. +this is an ed25519 key. ``salt`` is optional and may be left +as an empty string if no salt is to be used. +if the item is found in the DHT, a dht_mutable_item_alert is +posted. + +store the given bencoded data as an immutable item in the DHT. +the returned hash is the key that is to be used to look the item +up again. It's just the SHA-1 hash of the bencoded form of the +structure. + +store a mutable item. The ``key`` is the public key the blob is +to be stored under. The optional ``salt`` argument is a string that +is to be mixed in with the key when determining where in the DHT +the value is to be stored. The callback function is called from within +the libtorrent network thread once we've found where to store the blob, +possibly with the current value stored under the key. +The values passed to the callback functions are: + +entry& value + the current value stored under the key (may be empty). Also expected + to be set to the value to be stored by the function. + +std::array& signature + the signature authenticating the current value. This may be zeros + if there is currently no value stored. The function is expected to + fill in this buffer with the signature of the new value to store. + To generate the signature, you may want to use the + ``sign_mutable_item`` function. + +std::int64_t& seq + current sequence number. May be zero if there is no current value. + The function is expected to set this to the new sequence number of + the value that is to be stored. Sequence numbers must be monotonically + increasing. Attempting to overwrite a value with a lower or equal + sequence number will fail, even if the signature is correct. + +std::string const& salt + this is the salt that was used for this put call. + +Since the callback function ``cb`` is called from within libtorrent, +it is critical to not perform any blocking operations. Ideally not +even locking a mutex. Pass any data required for this function along +with the function object's context and make the function entirely +self-contained. The only reason data blob's value is computed +via a function instead of just passing in the new value is to avoid +race conditions. If you want to *update* the value in the DHT, you +must first retrieve it, then modify it, then write it back. The way +the DHT works, it is natural to always do a lookup before storing and +calling the callback in between is convenient. + +``dht_get_peers()`` will issue a DHT get_peer request to the DHT for the +specified info-hash. The response (the peers) will be posted back in a +dht_get_peers_reply_alert. + +``dht_announce()`` will issue a DHT announce request to the DHT to the +specified info-hash, advertising the specified port. If the port is +left at its default, 0, the port will be implied by the DHT message's +source port (which may improve connectivity through a NAT). +``dht_announce()`` is not affected by the ``announce_port`` override setting. + +Both these functions are exposed for advanced custom use of the DHT. +All torrents eligible to be announce to the DHT will be automatically, +by libtorrent. + +For possible flags, see announce_flags_t. + +Retrieve all the live DHT (identified by ``nid``) nodes. All the +nodes id and endpoint will be returned in the list of nodes in the +alert ``dht_live_nodes_alert``. +Since this alert is a response to an explicit call, it will always be +posted, regardless of the alert mask. + +Query the DHT node specified by ``ep`` to retrieve a sample of the +info-hashes that the node currently have in their storage. +The ``target`` is included for iterative lookups so that indexing nodes +can perform a key space traversal with a single RPC per node by adjusting +the target value for each RPC. It has no effect on the returned sample value. +The result is posted as a ``dht_sample_infohashes_alert``. + +Send an arbitrary DHT request directly to the specified endpoint. This +function is intended for use by plugins. When a response is received +or the request times out, a dht_direct_response_alert will be posted +with the response (if any) and the userdata pointer passed in here. +Since this alert is a response to an explicit call, it will always be +posted, regardless of the alert mask. + +This function adds an extension to this session. The argument is a +function object that is called with a ``torrent_handle`` and which should +return a ``std::shared_ptr``. To write custom +plugins, see `libtorrent plugins`_. For the typical bittorrent client +all of these extensions should be added. The main plugins implemented +in libtorrent are: + +uTorrent metadata + Allows peers to download the metadata (.torrent files) from the swarm + directly. Makes it possible to join a swarm with just a tracker and + info-hash. + +uTorrent peer exchange + Exchanges peers between clients. + +smart ban plugin + A plugin that, with a small overhead, can ban peers + that sends bad data with very high accuracy. Should + eliminate most problems on poisoned torrents. + + +Sets a filter that will be used to reject and accept incoming as well +as outgoing connections based on their originating ip address. The +default filter will allow connections to any ip address. To build a +set of rules for which addresses are accepted and not, see ip_filter. + +Each time a peer is blocked because of the IP filter, a +peer_blocked_alert is generated. ``get_ip_filter()`` Returns the +ip_filter currently in the session. See ip_filter. + +apply port_filter ``f`` to incoming and outgoing peers. a port filter +will reject making outgoing peer connections to certain remote ports. +The main intention is to be able to avoid triggering certain +anti-virus software by connecting to SMTP, FTP ports. + +built-in peer classes + +``is_listening()`` will tell you whether or not the session has +successfully opened a listening port. If it hasn't, this function will +return false, and then you can set a new +settings_pack::listen_interfaces to try another interface and port to +bind to. + +``listen_port()`` returns the port we ended up listening on. + +Sets the peer class filter for this session. All new peer connections +will take this into account and be added to the peer classes specified +by this filter, based on the peer's IP address. + +The ip-filter essentially maps an IP -> uint32. Each bit in that 32 +bit integer represents a peer class. The least significant bit +represents class 0, the next bit class 1 and so on. + +For more info, see ip_filter. + +For example, to make all peers in the range 200.1.1.0 - 200.1.255.255 +belong to their own peer class, apply the following filter: + +This setting only applies to new connections, it won't affect existing +peer connections. + +This function is limited to only peer class 0-31, since there are only +32 bits in the IP range mapping. Only the set bits matter; no peer +class will be removed from a peer as a result of this call, peer +classes are only added. + +The ``peer_class`` argument cannot be greater than 31. The bitmasks +representing peer classes in the ``peer_class_filter`` are 32 bits. + +The ``get_peer_class_filter()`` function returns the current filter. + +For more information, see peer-classes_. + +Sets and gets the *peer class type filter*. This is controls automatic +peer class assignments to peers based on what kind of socket it is. + +It does not only support assigning peer classes, it also supports +removing peer classes based on socket type. + +The order of these rules being applied are: + +1. peer-class IP filter +2. peer-class type filter, removing classes +3. peer-class type filter, adding classes + +For more information, see peer-classes_. + +Creates a new peer class (see peer-classes_) with the given name. The +returned integer is the new peer class identifier. Peer classes may +have the same name, so each invocation of this function creates a new +class and returns a unique identifier. + +Identifiers are assigned from low numbers to higher. So if you plan on +using certain peer classes in a call to set_peer_class_filter(), +make sure to create those early on, to get low identifiers. + +For more information on peer classes, see peer-classes_. + +This call dereferences the reference count of the specified peer +class. When creating a peer class it's automatically referenced by 1. +If you want to recycle a peer class, you may call this function. You +may only call this function **once** per peer class you create. +Calling it more than once for the same class will lead to memory +corruption. + +Since peer classes are reference counted, this function will not +remove the peer class if it's still assigned to torrents or peers. It +will however remove it once the last peer and torrent drops their +references to it. + +There is no need to call this function for custom peer classes. All +peer classes will be properly destructed when the session object +destructs. + +For more information on peer classes, see peer-classes_. + +These functions queries information from a peer class and updates the +configuration of a peer class, respectively. + +``cid`` must refer to an existing peer class. If it does not, the +return value of ``get_peer_class()`` is undefined. + +``set_peer_class()`` sets all the information in the +peer_class_info object in the specified peer class. There is no +option to only update a single property. + +A peer or torrent belonging to more than one class, the highest +priority among any of its classes is the one that is taken into +account. + +For more information, see peer-classes_. + +delete the files belonging to the torrent from disk. +including the part-file, if there is one + +delete just the part-file associated with this torrent + +when set, the session will start paused. Call +session_handle::resume() to start + +``remove_torrent()`` will close all peer connections associated with +the torrent and tell the tracker that we've stopped participating in +the swarm. This operation cannot fail. When it completes, you will +receive a torrent_removed_alert. + +remove_torrent() is non-blocking, but will remove the torrent from the +session synchronously. Calling session_handle::add_torrent() immediately +afterward with the same torrent will succeed. Note that this creates a +new handle which is not equal to the removed one. + +The optional second argument ``options`` can be used to delete all the +files downloaded by this torrent. To do so, pass in the value +``session_handle::delete_files``. Once the torrent is deleted, a +torrent_deleted_alert is posted. + +The torrent_handle remains valid for some time after remove_torrent() is +called. It will become invalid only after all libtorrent tasks (such as +I/O tasks) release their references to the torrent. Until this happens, +torrent_handle::is_valid() will return true, and other calls such +as torrent_handle::status() will succeed. Because of this, and because +remove_torrent() is non-blocking, the following sequence usually +succeeds (does not throw system_error): +Note that when a queued or downloading torrent is removed, its position +in the download queue is vacated and every subsequent torrent in the +queue has their queue positions updated. This can potentially cause a +large state_update to be posted. When removing all torrents, it is +advised to remove them from the back of the queue, to minimize the +shifting. + +Applies the settings specified by the settings_pack ``s``. This is an +asynchronous operation that will return immediately and actually apply +the settings to the main thread of libtorrent some time later. + +Alerts is the main mechanism for libtorrent to report errors and +events. ``pop_alerts`` fills in the vector passed to it with pointers +to new alerts. The session still owns these alerts and they will stay +valid until the next time ``pop_alerts`` is called. You may not delete +the alert objects. + +It is safe to call ``pop_alerts`` from multiple different threads, as +long as the alerts themselves are not accessed once another thread +calls ``pop_alerts``. Doing this requires manual synchronization +between the popping threads. + +``wait_for_alert`` will block the current thread for ``max_wait`` time +duration, or until another alert is posted. If an alert is available +at the time of the call, it returns immediately. The returned alert +pointer is the head of the alert queue. ``wait_for_alert`` does not +pop alerts from the queue, it merely peeks at it. The returned alert +will stay valid until ``pop_alerts`` is called twice. The first time +will pop it and the second will free it. + +If there is no alert in the queue and no alert arrives within the +specified timeout, ``wait_for_alert`` returns nullptr. + +In the python binding, ``wait_for_alert`` takes the number of +milliseconds to wait as an integer. + +The alert queue in the session will not grow indefinitely. Make sure +to pop periodically to not miss notifications. To control the max +number of alerts that's queued by the session, see +``settings_pack::alert_queue_size``. + +Some alerts are considered so important that they are posted even when +the alert queue is full. Some alerts are considered mandatory and cannot +be disabled by the ``alert_mask``. For instance, +save_resume_data_alert and save_resume_data_failed_alert are always +posted, regardless of the alert mask. + +To control which alerts are posted, set the alert_mask +(settings_pack::alert_mask). + +If the alert queue fills up to the point where alerts are dropped, this +will be indicated by a alerts_dropped_alert, which contains a bitmask +of which types of alerts were dropped. Generally it is a good idea to +make sure the alert queue is large enough, the alert_mask doesn't have +unnecessary categories enabled and to call pop_alert() frequently, to +avoid alerts being dropped. + +the ``set_alert_notify`` function lets the client set a function object +to be invoked every time the alert queue goes from having 0 alerts to +1 alert. This function is called from within libtorrent, it may be the +main thread, or it may be from within a user call. The intention of +of the function is that the client wakes up its main thread, to poll +for more alerts using ``pop_alerts()``. If the notify function fails +to do so, it won't be called again, until ``pop_alerts`` is called for +some other reason. For instance, it could signal an eventfd, post a +message to an HWND or some other main message pump. The actual +retrieval of alerts should not be done in the callback. In fact, the +callback should not block. It should not perform any expensive work. +It really should just notify the main application thread. + +The type of an alert is returned by the polymorphic function +``alert::type()`` but can also be queries from a concrete type via +``T::alert_type``, as a static constant. + +protocols used by add_port_mapping() + +add_port_mapping adds one or more port forwards on UPnP and/or NAT-PMP, +whichever is enabled. A mapping is created for each listen socket +in the session. The return values are all handles referring to the +port mappings that were just created. Pass them to delete_port_mapping() +to remove them. + +This option indicates if the ports are mapped using natpmp +and upnp. If mapping was already made, they are deleted and added +again. This only works if natpmp and/or upnp are configured to be +enable. + +Instructs the session to reopen all listen and outgoing sockets. + +It's useful in the case your platform doesn't support the built in +IP notifier mechanism, or if you have a better more reliable way to +detect changes in the IP routing table. + +This function is intended only for use by plugins. This type does +not have a stable API and should be relied on as little as possible. + +this class provides a non-owning handle to a session and a subset of the +interface of the session class. If the underlying session is destructed +any handle to it will no longer be valid. is_valid() will return false and +any operation on it will throw a system_error exception, with error code +invalid_session_handle. + + + + + +converts a setting integer (from the enums string_types, int_types or +bool_types) to a string, and vice versa. + +returns a settings_pack with every setting set to its default value + + + +the common interface to settings_pack and the internal representation of +settings. + + +set a configuration option in the settings_pack. ``name`` is one of +the enum values from string_types, int_types or bool_types. They must +match the respective type of the set_* function. + +queries whether the specified configuration option has a value set in +this pack. ``name`` can be any enumeration value from string_types, +int_types or bool_types. + +clear the settings pack from all settings + +clear a specific setting from the pack + +queries the current configuration option from the settings_pack. +``name`` is one of the enumeration values from string_types, int_types +or bool_types. The enum value must match the type of the get_* +function. If the specified setting field has not been set, the default +value is returned. + + + + + + +setting names (indices) are 16 bits. The two most significant +bits indicate what type the setting has. (string, int, bool) + + +disable writing to disk via mmap, always use normal write calls + +prefer using memory mapped files for disk writes (at least for +large files where it might make sense) + +determine whether to use pwrite or memory mapped files for disk +writes depending on the kind of storage behind the save path + + + + + +This is the traditional choker with a fixed number of unchoke +slots (as specified by settings_pack::unchoke_slots_limit). + +This opens up unchoke slots based on the upload rate achieved to +peers. The more slots that are opened, the marginal upload rate +required to open up another slot increases. Configure the initial +threshold with settings_pack::rate_choker_initial_threshold. + +For more information, see `rate based choking`_. + + + +which round-robins the peers that are unchoked +when seeding. This distributes the upload bandwidth uniformly and +fairly. It minimizes the ability for a peer to download everything +without redistributing it. + +unchokes the peers we can send to the fastest. This might be a +bit more reliable in utilizing all available capacity. + +prioritizes peers who have just started or are +just about to finish the download. The intention is to force +peers in the middle of the download to trade with each other. +This does not just take into account the pieces a peer is +reporting having downloaded, but also the pieces we have sent +to it. + + + + + + + +disables the mixed mode bandwidth balancing + +does not throttle uTP, throttles TCP to the same proportion +of throughput as there are TCP connections + + +Only encrypted connections are allowed. Incoming connections that +are not encrypted are closed and if the encrypted outgoing +connection fails, a non-encrypted retry will not be made. + +encrypted connections are enabled, but non-encrypted connections +are allowed. An incoming non-encrypted connection will be accepted, +and if an outgoing encrypted connection fails, a non- encrypted +connection will be tried. + +only non-encrypted connections are allowed. + +the encoding policy options for use with +settings_pack::out_enc_policy and settings_pack::in_enc_policy. + +use only plain text encryption + +use only RC4 encryption + +allow both + +the encryption levels, to be used with +settings_pack::allowed_enc_level. + +No proxy server is used and all other fields are ignored. + +The server is assumed to be a `SOCKS4 server`_ that requires a +username. + + +The server is assumed to be a SOCKS5 server (`RFC 1928`_) that does +not require any authentication. The username and password are +ignored. + + +The server is assumed to be a SOCKS5 server that supports plain +text username and password authentication (`RFC 1929`_). The +username and password specified may be sent to the proxy if it +requires. + + +The server is assumed to be an HTTP proxy. If the transport used +for the connection is non-HTTP, the server is assumed to support +the CONNECT_ method. i.e. for web seeds and HTTP trackers, a plain +proxy will suffice. The proxy is assumed to not require +authorization. The username and password will not be used. + + +The server is assumed to be an HTTP proxy that requires user +authorization. The username and password will be sent to the proxy. + + +The ``settings_pack`` struct, contains the names of all settings as +enum values. These values are passed in to the ``set_str()``, +``set_int()``, ``set_bool()`` functions, to specify the setting to +change. + +The ``settings_pack`` only stores values for settings that have been +explicitly set on this object. However, it can still be queried for +settings that have not been set and returns the default value for those +settings. + + + +If ``seed_mode`` is set, libtorrent will assume that all files +are present for this torrent and that they all match the hashes in +the torrent file. Each time a peer requests to download a block, +the piece is verified against the hash, unless it has been verified +already. If a hash fails, the torrent will automatically leave the +seed mode and recheck all the files. The use case for this mode is +if a torrent is created and seeded, or if the user already know +that the files are complete, this is a way to avoid the initial +file checks, and significantly reduce the startup time. + +Setting ``seed_mode`` on a torrent without metadata (a +.torrent file) is a no-op and will be ignored. + +It is not possible to *set* the ``seed_mode`` flag on a torrent after it has +been added to a session. It is possible to *clear* it though. + +If ``upload_mode`` is set, the torrent will be initialized in +upload-mode, which means it will not make any piece requests. This +state is typically entered on disk I/O errors, and if the torrent +is also auto managed, it will be taken out of this state +periodically (see ``settings_pack::optimistic_disk_retry``). + +This mode can be used to avoid race conditions when +adjusting priorities of pieces before allowing the torrent to start +downloading. + +If the torrent is auto-managed (``auto_managed``), the torrent +will eventually be taken out of upload-mode, regardless of how it +got there. If it's important to manually control when the torrent +leaves upload mode, don't make it auto managed. + +determines if the torrent should be added in *share mode* or not. +Share mode indicates that we are not interested in downloading the +torrent, but merely want to improve our share ratio (i.e. increase +it). A torrent started in share mode will do its best to never +download more than it uploads to the swarm. If the swarm does not +have enough demand for upload capacity, the torrent will not +download anything. This mode is intended to be safe to add any +number of torrents to, without manual screening, without the risk +of downloading more than is uploaded. + +A torrent in share mode sets the priority to all pieces to 0, +except for the pieces that are downloaded, when pieces are decided +to be downloaded. This affects the progress bar, which might be set +to "100% finished" most of the time. Do not change file or piece +priorities for torrents in share mode, it will make it not work. + +The share mode has one setting, the share ratio target, see +``settings_pack::share_mode_target`` for more info. + +determines if the IP filter should apply to this torrent or not. By +default all torrents are subject to filtering by the IP filter +(i.e. this flag is set by default). This is useful if certain +torrents needs to be exempt for some reason, being an auto-update +torrent for instance. + +specifies whether or not the torrent is paused. i.e. it won't connect to the tracker or any of the peers +until it's resumed. Note that a paused torrent that also has the +auto_managed flag set can be started at any time by libtorrent's queuing +logic. See queuing_. + +If the torrent is auto-managed (``auto_managed``), the torrent +may be resumed at any point, regardless of how it paused. If it's +important to manually control when the torrent is paused and +resumed, don't make it auto managed. + +If ``auto_managed`` is set, the torrent will be queued, +started and seeded automatically by libtorrent. When this is set, +the torrent should also be started as paused. The default queue +order is the order the torrents were added. They are all downloaded +in that order. For more details, see queuing_. + +used in add_torrent_params to indicate that it's an error to attempt +to add a torrent that's already in the session. If it's not considered an +error, a handle to the existing torrent is returned. +This flag is not saved by write_resume_data(), since it is only meant for +adding torrents. + +on by default and means that this torrent will be part of state +updates when calling post_torrent_updates(). +This flag is not saved by write_resume_data(). + +sets the torrent into super seeding/initial seeding mode. If the torrent +is not a seed, this flag has no effect. + +sets the sequential download state for the torrent. In this mode the +piece picker will pick pieces with low index numbers before pieces with +high indices. The actual pieces that are picked depend on other factors +still, such as which pieces a peer has and whether it is in parole mode +or "prefer whole pieces"-mode. Sequential mode is not ideal for streaming +media. For that, see set_piece_deadline() instead. + +When this flag is set, the torrent will *force stop* whenever it +transitions from a non-data-transferring state into a data-transferring +state (referred to as being ready to download or seed). This is useful +for torrents that should not start downloading or seeding yet, but want +to be made ready to do so. A torrent may need to have its files checked +for instance, so it needs to be started and possibly queued for checking +(auto-managed and started) but as soon as it's done, it should be +stopped. + +*Force stopped* means auto-managed is set to false and it's paused. As +if the auto_manages flag is cleared and the paused flag is set on the torrent. + +Note that the torrent may transition into a downloading state while +setting this flag, and since the logic is edge triggered you may +miss the edge. To avoid this race, if the torrent already is in a +downloading state when this call is made, it will trigger the +stop-when-ready immediately. + +When the stop-when-ready logic fires, the flag is cleared. Any +subsequent transitions between downloading and non-downloading states +will not be affected, until this flag is set again. + +The behavior is more robust when setting this flag as part of adding +the torrent. See add_torrent_params. + +The stop-when-ready flag fixes the inherent race condition of waiting +for the state_changed_alert and then call pause(). The download/seeding +will most likely start in between posting the alert and receiving the +call to pause. + +A downloading state is one where peers are being connected. Which means +just downloading the metadata via the ``ut_metadata`` extension counts +as a downloading state. In order to stop a torrent once the metadata +has been downloaded, instead set all file priorities to dont_download + +when this flag is set, the tracker list in the add_torrent_params +object override any trackers from the torrent file. If the flag is +not set, the trackers from the add_torrent_params object will be +added to the list of trackers used by the torrent. +This flag is set by read_resume_data() if there are trackers present in +the resume data file. This effectively makes the trackers saved in the +resume data take precedence over the original trackers. This includes if +there's an empty list of trackers, to support the case where they were +explicitly removed in the previous session. +This flag is not saved by write_resume_data() + +If this flag is set, the web seeds from the add_torrent_params +object will override any web seeds in the torrent file. If it's not +set, web seeds in the add_torrent_params object will be added to the +list of web seeds used by the torrent. +This flag is set by read_resume_data() if there are web seeds present in +the resume data file. This effectively makes the web seeds saved in the +resume data take precedence over the original ones. This includes if +there's an empty list of web seeds, to support the case where they were +explicitly removed in the previous session. +This flag is not saved by write_resume_data() + +if this flag is set (which it is by default) the torrent will be +considered needing to save its resume data immediately, in the +category if_metadata_changed. See resume_data_flags_t and +save_resume_data() for details. + +This flag is cleared by a successful call to save_resume_data() +This flag is not saved by write_resume_data(), since it represents an +ephemeral state of a running torrent. + +set this flag to disable DHT for this torrent. This lets you have the DHT +enabled for the whole client, and still have specific torrents not +participating in it. i.e. not announcing to the DHT nor picking up peers +from it. + +set this flag to disable local service discovery for this torrent. + +set this flag to disable peer exchange for this torrent. + +if this flag is set, the resume data will be assumed to be correct +without validating it against any files on disk. This may be used when +restoring a session by loading resume data from disk. It will save time +and also delay any hard disk errors until files are actually needed. If +the resume data cannot be trusted, or if a torrent is added for the first +time to some save path that may already have some of the files, this flag +should not be set. + +default all file priorities to dont_download. This is useful for adding +magnet links where the number of files is unknown, but the +file_priorities is still set for some files. Any file not covered by +the file_priorities list will be set to normal download priority, +unless this flag is set, in which case they will be set to 0 +(dont_download). + +this flag makes the torrent be considered an "i2p torrent" for purposes +of the allow_i2p_mixed setting. When mixing regular peers and i2p peers +is disabled, i2p torrents won't add normal peers to its peer list. +Note that non i2p torrents may still allow i2p peers (on the off-chance +that a tracker return them and the session is configured with a SAM +connection). +This flag is set automatically when adding a torrent that has at least +one tracker whose hostname ends with .i2p. +It's also set by parse_magnet_uri() if the tracker list contains such +URL. + +all torrent flags combined. Can conveniently be used when creating masks +for flags + +if this tracker has returned an error or warning message +that message is stored here + +if this tracker failed the last time it was contacted +this error code specifies what error occurred + +if this tracker has returned scrape data, these fields are filled in +with valid numbers. Otherwise they are set to -1. ``incomplete`` counts +the number of current downloaders. ``complete`` counts the number of +current peers completed the download, or "seeds". ``downloaded`` is the +cumulative number of completed downloads. + +the number of times in a row we have failed to announce to this +tracker. + +true while we're waiting for a response from the tracker. + +set to true when we get a valid response from an announce +with event=started. If it is set, we won't send start in the subsequent +announces. + +set to true when we send a event=completed. + + + +the local endpoint of the listen interface associated with this endpoint + +info_hashes[0] is the v1 info hash (SHA1) +info_hashes[1] is the v2 info hash (truncated SHA-256) + +set to false to not announce from this endpoint + +announces are sent to each tracker using every listen socket +this class holds information about one listen socket for one tracker + +constructs a tracker announce entry with ``u`` as the URL. + +tracker URL as it appeared in the torrent file + +the current ``&trackerid=`` argument passed to the tracker. +this is optional and is normally empty (in which case no +trackerid is sent). + +each local listen socket (endpoint) will announce to the tracker. This +list contains state per endpoint. + +the tier this tracker belongs to + +the max number of failures to announce to this tracker in +a row, before this tracker is not used anymore. 0 means unlimited + +the tracker was part of the .torrent file + +the tracker was added programmatically via the add_tracker() function + +the tracker was part of a magnet link + +the tracker was received from the swarm via tracker exchange + +flags for the source bitmask, each indicating where +we heard about this tracker + +a bitmask specifying which sources we got this tracker from. + +set to true the first time we receive a valid response +from this tracker. + +this class holds information about one bittorrent tracker, as it +relates to a specific torrent. + + +this is the same as default constructing followed by a call to +``update(data, len)``. + +append the following bytes to what is being hashed + +returns the SHA-1 digest of the buffers previously passed to +update() and the hasher constructor. + +restore the hasher state to be as if the hasher has just been +default constructed. + +this is a SHA-1 hash class. + +You use it by first instantiating it, then call ``update()`` to feed it +with data. i.e. you don't have to keep the entire buffer of which you want to +create the hash in memory. You can feed the hasher parts of it at a time. When +You have fed the hasher with all the data, you call ``final()`` and it +will return the sha1-hash of the data. + +The constructor that takes a ``char const*`` and an integer will construct the +sha1 context and feed it the data passed in. + +If you want to reuse the hasher object once you have created a hash, you have to +call ``reset()`` to reinitialize it. + +The built-in software version of sha1-algorithm was implemented +by Steve Reid and released as public domain. +For more info, see ``src/sha1.cpp``. + + +this is the same as default constructing followed by a call to +``update(data, len)``. + +append the following bytes to what is being hashed + +returns the SHA-1 digest of the buffers previously passed to +update() and the hasher constructor. + +restore the hasher state to be as if the hasher has just been +default constructed. + + + +The default values of the session settings are set for a regular +bittorrent client running on a desktop system. There are functions that +can set the session settings to pre set settings for other environments. +These can be used for the basis, and should be tweaked to fit your needs +better. + +``min_memory_usage`` returns settings that will use the minimal amount of +RAM, at the potential expense of upload and download performance. It +adjusts the socket buffer sizes, disables the disk cache, lowers the send +buffer watermarks so that each connection only has at most one block in +use at any one time. It lowers the outstanding blocks send to the disk +I/O thread so that connections only have one block waiting to be flushed +to disk at any given time. It lowers the max number of peers in the peer +list for torrents. It performs multiple smaller reads when it hashes +pieces, instead of reading it all into memory before hashing. + +This configuration is intended to be the starting point for embedded +devices. It will significantly reduce memory usage. + +``high_performance_seed`` returns settings optimized for a seed box, +serving many peers and that doesn't do any downloading. It has a 128 MB +disk cache and has a limit of 400 files in its file pool. It support fast +upload rates by allowing large send buffers. + +the constructor function for the default storage. On systems that support +memory mapped files (and a 64 bit address space) the memory mapped storage +will be constructed, otherwise the portable posix storage. + +default constructor, does not refer to any session +implementation object. + +this is a holder for the internal session implementation object. Once the +session destruction is explicitly initiated, this holder is used to +synchronize the completion of the shutdown. The lifetime of this object +may outlive session, causing the session destructor to not block. The +session_proxy destructor will block however, until the underlying session +is done shutting down. + +Constructs the session objects which acts as the container of torrents. +In order to avoid a race condition between starting the session and +configuring it, you can pass in a session_params object. Its settings +will take effect before the session starts up. + +The overloads taking ``flags`` can be used to start a session in +paused mode (by passing in ``session::paused``). Note that +``add_default_plugins`` do not have an affect on constructors that +take a session_params object. It already contains the plugins to use. + +Overload of the constructor that takes an external io_context to run +the session object on. This is primarily useful for tests that may want +to run multiple sessions on a single io_context, or low resource +systems where additional threads are expensive and sharing an +io_context with other events is fine. + + The session object does not cleanly terminate with an external + ``io_context``. The ``io_context::run()`` call *must* have returned + before it's safe to destruct the session. Which means you *MUST* + call session::abort() and save the session_proxy first, then + destruct the session object, then sync with the io_context, then + destruct the session_proxy object. + +The destructor of session will notify all trackers that our torrents +have been shut down. If some trackers are down, they will time out. +All this before the destructor of session returns. So, it's advised +that any kind of interface (such as windows) are closed before +destructing the session object. Because it can take a few second for +it to finish. The timeout can be set with apply_settings(). + +In case you want to destruct the session asynchronously, you can +request a session destruction proxy. If you don't do this, the +destructor of the session object will block while the trackers are +contacted. If you keep one ``session_proxy`` to the session when +destructing it, the destructor will not block, but start to close down +the session, the destructor of the proxy will then synchronize the +threads. So, the destruction of the session is performed from the +``session`` destructor call until the ``session_proxy`` destructor +call. The ``session_proxy`` does not have any operations on it (since +the session is being closed down, no operations are allowed on it). +The only valid operation is calling the destructor:: + + struct session_proxy {}; + +The session holds all state that spans multiple torrents. Among other +things it runs the network loop and manages all torrents. Once it's +created, the session object will spawn the main thread that will do all +the work. The main thread will be idle as long it doesn't have any +torrents to participate in. + +You have some control over session configuration through the +``session_handle::apply_settings()`` member function. To change one or more +configuration options, create a settings_pack. object and fill it with +the settings to be set and pass it in to ``session::apply_settings()``. + +see apply_settings(). + +this function turns the resume data in an ``add_torrent_params`` object +into a bencoded structure + +this makes write_torrent_file() not fail when attempting to write a +v2 torrent file that does not have all the piece layers + +don't include http seeds in the torrent file, even if some are +present in the add_torrent_params object + +When set, DHT nodes from the add_torrent_params objects are included +in the resulting .torrent file + +writes only the fields to create a .torrent file. This function may fail +with a ``std::system_error`` exception if: + +* The add_torrent_params object passed to this function does not contain the + info dictionary (the ``ti`` field) +* The piece layers are not complete for all files that need them + +The ``write_torrent_file_buf()`` overload returns the torrent file in +bencoded buffer form. This overload may be faster at the expense of lost +flexibility to add custom fields. + + + + + + + + + + + + +SOCKS5 error values. If an error_code has the +socks error category (get_socks_category()), these +are the error values. + +returns the error_category for SOCKS5 errors + + +the interface for freeing disk buffers, used by the disk_buffer_holder. +when implementing disk_interface, this must also be implemented in order +to return disk buffers back to libtorrent + + + +construct a buffer holder that will free the held buffer +using a disk buffer pool directly (there's only one +disk_buffer_pool per session) + +default construct a holder that does not own any buffer + +frees disk buffer held by this object + +return a pointer to the held buffer, if any. Otherwise returns nullptr. + +free the held disk buffer, if any, and clear the holder. This sets the +holder object to a default-constructed state + +swap pointers of two disk buffer holders. + +if this returns true, the buffer may not be modified in place + +implicitly convertible to true if the object is currently holding a +buffer + + +The disk buffer holder acts like a ``unique_ptr`` that frees a disk buffer +when it's destructed + +If this buffer holder is moved-from, default constructed or reset, +``data()`` will return nullptr. + +compares if the torrent status objects come from the same torrent. i.e. +only the torrent_handle field is compared. + +a handle to the torrent whose status the object represents. + +The torrent has not started its download yet, and is +currently checking existing files. + +The torrent is trying to download metadata from peers. +This implies the ut_metadata extension is in use. + +The torrent is being downloaded. This is the state +most torrents will be in most of the time. The progress +meter will tell how much of the files that has been +downloaded. + +In this state the torrent has finished downloading but +still doesn't have the entire torrent. i.e. some pieces +are filtered and won't get downloaded. + +In this state the torrent has finished downloading and +is a pure seeder. + +If the torrent was started in full allocation mode, this +indicates that the (disk) storage for the torrent is +allocated. + +The torrent is currently checking the fast resume data and +comparing it to the files on disk. This is typically +completed in a fraction of a second, but if you add a +large number of torrents at once, they will queue up. + +the different overall states a torrent can be in + +may be set to an error code describing why the torrent was paused, in +case it was paused by an error. If the torrent is not paused or if it's +paused but not because of an error, this error_code is not set. +if the error is attributed specifically to a file, error_file is set to +the index of that file in the .torrent file. + +if the torrent is stopped because of an disk I/O error, this field +contains the index of the file in the torrent that encountered the +error. If the error did not originate in a file in the torrent, there +are a few special values this can be set to: error_file_none, +error_file_ssl_ctx, error_file_exception, error_file_partfile or +error_file_metadata; + +special values for error_file to describe which file or component +encountered the error (``errc``). +the error did not occur on a file + +the error occurred setting up the SSL context + +the error occurred while loading the metadata for the torrent + +there was a serious error reported in this torrent. The error code +or a torrent log alert may provide more information. + +the error occurred with the partfile + +the path to the directory where this torrent's files are stored. +It's typically the path as was given to async_add_torrent() or +add_torrent() when this torrent was started. This field is only +included if the torrent status is queried with +``torrent_handle::query_save_path``. + +the name of the torrent. Typically this is derived from the +.torrent file. In case the torrent was started without metadata, +and hasn't completely received it yet, it returns the name given +to it when added to the session. See ``session::add_torrent``. +This field is only included if the torrent status is queried +with ``torrent_handle::query_name``. + +set to point to the ``torrent_info`` object for this torrent. It's +only included if the torrent status is queried with +``torrent_handle::query_torrent_file``. + +the time until the torrent will announce itself to the tracker. + +the URL of the last working tracker. If no tracker request has +been successful yet, it's set to an empty string. + +the number of bytes downloaded and uploaded to all peers, accumulated, +*this session* only. The session is considered to restart when a +torrent is paused and restarted again. When a torrent is paused, these +counters are reset to 0. If you want complete, persistent, stats, see +``all_time_upload`` and ``all_time_download``. + +counts the amount of bytes send and received this session, but only +the actual payload data (i.e the interesting data), these counters +ignore any protocol overhead. The session is considered to restart +when a torrent is paused and restarted again. When a torrent is +paused, these counters are reset to 0. + +the number of bytes that has been downloaded and that has failed the +piece hash test. In other words, this is just how much crap that has +been downloaded since the torrent was last started. If a torrent is +paused and then restarted again, this counter will be reset. + +the number of bytes that has been downloaded even though that data +already was downloaded. The reason for this is that in some situations +the same data can be downloaded by mistake. When libtorrent sends +requests to a peer, and the peer doesn't send a response within a +certain timeout, libtorrent will re-request that block. Another +situation when libtorrent may re-request blocks is when the requests +it sends out are not replied in FIFO-order (it will re-request blocks +that are skipped by an out of order block). This is supposed to be as +low as possible. This only counts bytes since the torrent was last +started. If a torrent is paused and then restarted again, this counter +will be reset. + +a bitmask that represents which pieces we have (set to true) and the +pieces we don't have. It's a pointer and may be set to 0 if the +torrent isn't downloading or seeding. + +a bitmask representing which pieces has had their hash checked. This +only applies to torrents in *seed mode*. If the torrent is not in seed +mode, this bitmask may be empty. + +the total number of bytes of the file(s) that we have. All this does +not necessarily has to be downloaded during this session (that's +``total_payload_download``). + +the total number of bytes to download for this torrent. This +may be less than the size of the torrent in case there are +pad files. This number only counts bytes that will actually +be requested from peers. + +the number of bytes we have downloaded, only counting the pieces that +we actually want to download. i.e. excluding any pieces that we have +but have priority 0 (i.e. not wanted). +Once a torrent becomes seed, any piece- and file priorities are +forgotten and all bytes are considered "wanted". + +The total number of bytes we want to download. This may be smaller +than the total torrent size in case any pieces are prioritized to 0, +i.e. not wanted. +Once a torrent becomes seed, any piece- and file priorities are +forgotten and all bytes are considered "wanted". + +are accumulated upload and download payload byte counters. They are +saved in and restored from resume data to keep totals across sessions. + +the posix-time when this torrent was added. i.e. what ``time(nullptr)`` +returned at the time. + +the posix-time when this torrent was finished. If the torrent is not +yet finished, this is 0. + +the time when we, or one of our peers, last saw a complete copy of +this torrent. + +The allocation mode for the torrent. See storage_mode_t for the +options. For more information, see storage-allocation_. + +a value in the range [0, 1], that represents the progress of the +torrent's current task. It may be checking files or downloading. + +progress parts per million (progress * 1000000) when disabling +floating point operations, this is the only option to query progress + +reflects the same value as ``progress``, but instead in a range [0, +1000000] (ppm = parts per million). When floating point operations are +disabled, this is the only alternative to the floating point value in +progress. + +the position this torrent has in the download +queue. If the torrent is a seed or finished, this is -1. + +the total rates for all peers for this torrent. These will usually +have better precision than summing the rates from all peers. The rates +are given as the number of bytes per second. + +the total transfer rate of payload only, not counting protocol +chatter. This might be slightly smaller than the other rates, but if +projected over a long time (e.g. when calculating ETA:s) the +difference may be noticeable. + +the number of peers that are seeding that this client is +currently connected to. + +the number of peers this torrent currently is connected to. Peer +connections that are in the half-open state (is attempting to connect) +or are queued for later connection attempt do not count. Although they +are visible in the peer list when you call get_peer_info(). + +if the tracker sends scrape info in its announce reply, these fields +will be set to the total number of peers that have the whole file and +the total number of peers that are still downloading. set to -1 if the +tracker did not send any scrape data in its announce reply. + +the number of seeds in our peer list and the total number of peers +(including seeds). We are not necessarily connected to all the peers +in our peer list. This is the number of peers we know of in total, +including banned peers and peers that we have failed to connect to. + +the number of peers in this torrent's peer list that is a candidate to +be connected to. i.e. It has fewer connect attempts than the max fail +count, it is not a seed if we are a seed, it is not banned etc. If +this is 0, it means we don't know of any more peers that we can try. + +the number of pieces that has been downloaded. It is equivalent to: +``std::accumulate(pieces->begin(), pieces->end())``. So you don't have +to count yourself. This can be used to see if anything has updated +since last time if you want to keep a graph of the pieces up to date. +Note that these pieces have not necessarily been written to disk yet, +and there is a risk the write to disk will fail. + +the number of distributed copies of the torrent. Note that one copy +may be spread out among many peers. It tells how many copies there are +currently of the rarest piece(s) among the peers this client is +connected to. + +tells the share of pieces that have more copies than the rarest +piece(s). Divide this number by 1000 to get the fraction. + +For example, if ``distributed_full_copies`` is 2 and +``distributed_fraction`` is 500, it means that the rarest pieces have +only 2 copies among the peers this torrent is connected to, and that +50% of all the pieces have more than two copies. + +If we are a seed, the piece picker is deallocated as an optimization, +and piece availability is no longer tracked. In this case the +distributed copies members are set to -1. + +the number of distributed copies of the file. note that one copy may +be spread out among many peers. This is a floating point +representation of the distributed copies. + +the integer part tells how many copies + there are of the rarest piece(s) + +the fractional part tells the fraction of pieces that + have more copies than the rarest piece(s). + +the size of a block, in bytes. A block is a sub piece, it is the +number of bytes that each piece request asks for and the number of +bytes that each bit in the ``partial_piece_info``'s bitset represents, +see get_download_queue(). This is typically 16 kB, but it may be +smaller, if the pieces are smaller. + +the number of unchoked peers in this torrent. + +the number of peer connections this torrent has, including half-open +connections that hasn't completed the bittorrent handshake yet. This +is always >= ``num_peers``. + +the set limit of upload slots (unchoked peers) for this torrent. + +the set limit of number of connections for this torrent. + +the number of peers in this torrent that are waiting for more +bandwidth quota from the torrent rate limiter. This can determine if +the rate you get from this torrent is bound by the torrents limit or +not. If there is no limit set on this torrent, the peers might still +be waiting for bandwidth quota from the global limiter, but then they +are counted in the ``session_status`` object. + +A rank of how important it is to seed the torrent, it is used to +determine which torrents to seed and which to queue. It is based on +the peer to seed ratio from the tracker scrape. For more information, +see queuing_. Higher value means more important to seed + +the main state the torrent is in. See torrent_status::state_t. + +true if this torrent has unsaved changes +to its download state and statistics since the last resume data +was saved. + +true if all pieces have been downloaded. + +true if all pieces that have a priority > 0 are downloaded. There is +only a distinction between finished and seeding if some pieces or +files have been set to priority 0, i.e. are not downloaded. + +true if this torrent has metadata (either it was started from a +.torrent file or the metadata has been downloaded). The only scenario +where this can be false is when the torrent was started torrent-less +(i.e. with just an info-hash and tracker ip, a magnet link for +instance). + +true if there has ever been an incoming connection attempt to this +torrent. + +this is true if this torrent's storage is currently being moved from +one location to another. This may potentially be a long operation +if a large file ends up being copied from one drive to another. + +these are set to true if this torrent is allowed to announce to the +respective peer source. Whether they are true or false is determined by +the queue logic/auto manager. Torrents that are not auto managed will +always be allowed to announce to all peer sources. + +the info-hash for this torrent + +the timestamps of the last time this torrent uploaded or downloaded +payload to any peer. + +these are cumulative counters of for how long the torrent has been in +different states. active means not paused and added to session. Whether +it has found any peers or not is not relevant. +finished means all selected files/pieces were downloaded and available +to other peers (this is always a subset of active time). +seeding means all files/pieces were downloaded and available to +peers. Being available to peers does not imply there are other peers +asking for the payload. + +reflects several of the torrent's flags. For more +information, see ``torrent_handle::flags()``. + +holds a snapshot of the status of a torrent, as queried by +torrent_handle::status(). + +these functions are used to parse resume data and populate the appropriate +fields in an add_torrent_params object. This object can then be used to add +the actual torrent_info object to and pass to session::add_torrent() or +session::async_add_torrent(). + +If the client wants to override any field that was loaded from the resume +data, e.g. save_path, those fields must be changed after loading resume +data but before adding the torrent. + +The ``piece_limit`` parameter determines the largest number of pieces +allowed in the torrent that may be loaded as part of the resume data, if +it contains an ``info`` field. The overloads that take a flat buffer are +instead configured with limits on torrent sizes via load_torrent limits. + +In order to support large torrents, it may also be necessary to raise the +settings_pack::max_piece_count setting and pass a higher limit to calls +to torrent_info::parse_info_section(). + + + + + + +Kinds of tracker announces. This is typically indicated as the ``&event=`` +HTTP query string parameter to HTTP trackers. + +The index of the piece in which the range starts. + +The byte offset within that piece where the range starts. + +The size of the range, in bytes. + +returns true if the right hand side peer_request refers to the same +range as this does. + +represents a byte range within a piece. Internally this is is used for +incoming piece requests. + +the peer supports protocol encryption + +the peer is a seed + +the peer supports the uTP, transport protocol over UDP. + +the peer supports the holepunch extension If this flag is received from a +peer, it can be used as a rendezvous point in case direct connections to +the peer fail + +protocol v2 +this is not a standard flag, it is only used internally + +constructs a new bitfield. The default constructor creates an empty +bitfield. ``bits`` is the size of the bitfield (specified in bits). +``val`` is the value to initialize the bits to. If not specified +all bits are initialized to 0. + +The constructor taking a pointer ``b`` and ``bits`` copies a bitfield +from the specified buffer, and ``bits`` number of bits (rounded up to +the nearest byte boundary). + +copy bitfield from buffer ``b`` of ``bits`` number of bits, rounded up to +the nearest byte boundary. + +query bit at ``index``. Returns true if bit is 1, otherwise false. + +set bit at ``index`` to 0 (clear_bit) or 1 (set_bit). + +returns true if all bits in the bitfield are set + +returns true if no bit in the bitfield is set + +returns the size of the bitfield in bits. + +returns the number of 32 bit words are needed to represent all bits in +this bitfield. + +returns the number of bytes needed to represent all bits in this +bitfield + +returns true if the bitfield has zero size. + +returns a pointer to the internal buffer of the bitfield, or +``nullptr`` if it's empty. + +swaps the bit-fields two variables refer to + +count the number of bits in the bitfield that are set to 1. + +returns the index of the first set bit in the bitfield, i.e. 1 bit. + +returns the index to the last cleared bit in the bitfield, i.e. 0 bit. + + +The bitfield type stores any number of bits as a bitfield +in a heap allocated array. + + + + + + + + + + + +error values for the i2p_category error_category. + +returns the error category for I2P errors + +the major, minor and tiny versions of libtorrent + +the major, minor and tiny versions of libtorrent + +the major, minor and tiny versions of libtorrent + +the libtorrent version in string form + +the git commit of this libtorrent version + +returns the libtorrent version as string form in this format: +"..." + +This will include the file modification time as part of the torrent. +This is not enabled by default, as it might cause problems when you +create a torrent from separate files with the same content, hoping to +yield the same info-hash. If the files have different modification times, +with this option enabled, you would get different info-hashes for the +files. + +If this flag is set, files that are symlinks get a symlink attribute +set on them and their data will not be included in the torrent. This +is useful if you need to reconstruct a file hierarchy which contains +symlinks. + +Do not generate v1 metadata. The resulting torrent will only be usable by +clients which support v2. This requires setting all v2 hashes, with +set_hash2() before calling generate(). Setting v1 hashes (with +set_hash()) is an error with this flag set. + +do not generate v2 metadata or enforce v2 alignment and padding rules +this is mainly for tests, not recommended for production use. This +requires setting all v1 hashes, with set_hash(), before calling +generate(). Setting v2 hashes (with set_hash2()) is an error with +this flag set. + +This flag only affects v1-only torrents, and is only relevant +together with the v1_only_flag. This flag will force the +same file order and padding as a v2 (or hybrid) torrent would have. +It has the effect of ordering files and inserting pad files to align +them with piece boundaries. + +passing this flag to add_files() will ignore file attributes (such as +executable or hidden) when adding the files to the file storage. +Since not all filesystems and operating systems support all file +attributes the resulting torrent may differ depending on where it's +created. If it's important for torrents to be created consistently +across systems, this flag should be set. + +this flag enforces the file layout to be canonical according to the +bittorrent v2 specification (just like the ``canonical_files`` flag) +with the one exception that tail padding is not added to the last +file. +This behavior deviates from the specification but was the way +libtorrent created torrents in version up to and including 2.0.7. +This flag is here for backwards compatibility. + +The ``piece_size`` is the size of each piece in bytes. It must be a +power of 2 and a minimum of 16 kiB. If a piece size of 0 is +specified, a piece_size will be set automatically. +Piece sizes greater than 128 MiB are considered unreasonable and will +be rejected (with an lt::system_error exception). + +The ``flags`` arguments specifies options for the torrent creation. It can +be any combination of the flags defined by create_flags_t. + +The file_storage (``fs``) parameter defines the files, sizes and +their properties for the torrent to be created. Set this up first, +before passing it to the create_torrent constructor. + +The overload that takes a ``torrent_info`` object will make a verbatim +copy of its info dictionary (to preserve the info-hash). The copy of +the info dictionary will be used by create_torrent::generate(). This means +that none of the member functions of create_torrent that affects +the content of the info dictionary (such as set_hash()), will +have any affect. Instead of using this overload, consider using +write_torrent_file() instead. + + The file_storage and torrent_info objects must stay alive for the + entire duration of the create_torrent object. + + +This function will generate the .torrent file as a bencode tree, or a +bencoded into a buffer. +In order to encode the entry into a flat file, use the bencode() function. + +The function returning an entry may be useful to add custom entries +to the torrent file before bencoding it and saving it to disk. + +Whether the resulting torrent object is v1, v2 or hybrid depends on +whether any of the v1_only or v2_only flags were set on the +constructor. If neither were set, the resulting torrent depends on +which hashes were set. If both v1 and v2 hashes were set, a hybrid +torrent is created. + +Any failure will cause this function to throw system_error, with an +appropriate error message. These are the reasons this call may throw: + +* the file storage has 0 files +* the total size of the file storage is 0 bytes (i.e. it only has + empty files) +* not all v1 hashes (set_hash()) and not all v2 hashes (set_hash2()) + were set +* for v2 torrents, you may not have a directory with the same name as + a file. If that's encountered in the file storage, generate() + fails. + +returns an immutable reference to the file_storage used to create +the torrent from. + +Sets the comment for the torrent. The string ``str`` should be utf-8 encoded. +The comment in a torrent file is optional. + +Sets the creator of the torrent. The string ``str`` should be utf-8 encoded. +This is optional. + +sets the "creation time" field. Defaults to the system clock at the +time of construction of the create_torrent object. The timestamp is +specified in seconds, posix time. If the creation date is set to 0, +the "creation date" field will be omitted from the generated torrent. + +This sets the SHA-1 hash for the specified piece (``index``). You are required +to set the hash for every piece in the torrent before generating it. If you have +the files on disk, you can use the high level convenience function to do this. +See set_piece_hashes(). +A SHA-1 hash of all zeros is internally used to indicate a hash that +has not been set. Setting such hash will not be considered set when +calling generate(). +This function will throw ``std::system_error`` if it is called on an +object constructed with the v2_only flag. + +sets the bittorrent v2 hash for file `file` of the piece `piece`. +`piece` is relative to the first piece of the file, starting at 0. The +first piece in the file can be computed with +file_storage::file_index_at_piece(). +The hash, `h`, is the root of the merkle tree formed by the piece's +16 kiB blocks. Note that piece sizes must be powers-of-2, so all +per-piece merkle trees are complete. +A SHA-256 hash of all zeros is internally used to indicate a hash +that has not been set. Setting such hash will not be considered set +when calling generate(). +This function will throw ``std::system_error`` if it is called on an +object constructed with the v1_only flag. + +This adds a url seed to the torrent. You can have any number of url seeds. For a +single file torrent, this should be an HTTP url, pointing to a file with identical +content as the file of the torrent. For a multi-file torrent, it should point to +a directory containing a directory with the same name as this torrent, and all the +files of the torrent in it. + +The second function, ``add_http_seed()`` adds an HTTP seed instead. + +This adds a DHT node to the torrent. This especially useful if you're creating a +tracker less torrent. It can be used by clients to bootstrap their DHT node from. +The node is a hostname and a port number where there is a DHT node running. +You can have any number of DHT nodes in a torrent. + +Adds a tracker to the torrent. This is not strictly required, but most torrents +use a tracker as their main source of peers. The url should be an http:// or udp:// +url to a machine running a bittorrent tracker that accepts announces for this torrent's +info-hash. The tier is the fallback priority of the tracker. All trackers with tier 0 are +tried first (in any order). If all fail, trackers with tier 1 are tried. If all of those +fail, trackers with tier 2 are tried, and so on. + +This function sets an X.509 certificate in PEM format to the torrent. This makes the +torrent an *SSL torrent*. An SSL torrent requires that each peer has a valid certificate +signed by this root certificate. For SSL torrents, all peers are connecting over SSL +connections. For more information, see the section on ssl-torrents_. + +The string is not the path to the cert, it's the actual content of the +certificate. + +Sets and queries the private flag of the torrent. +Torrents with the private flag set ask the client to not use any other +sources than the tracker for peers, and to not use DHT to advertise itself publicly, +only the tracker. + + +returns the number of pieces in the associated file_storage object. + + +all piece indices in the torrent to be created + + +all file indices in the torrent to be created + +for v2 and hybrid torrents only, the pieces in the +specified file, specified as delta from the first piece in the file. +i.e. the first index is 0. + +the total number of bytes of all files and pad files + +``piece_length()`` returns the piece size of all pieces but the +last one. ``piece_size()`` returns the size of the specified piece. +these functions are just forwarding to the associated file_storage. + +Add similar torrents (by info-hash) or collections of similar torrents. +Similar torrents are expected to share some files with this torrent. +Torrents sharing a collection name with this torrent are also expected +to share files with this torrent. A torrent may have more than one +collection and more than one similar torrents. For more information, +see `BEP 38`_. + +This class holds state for creating a torrent. After having added +all information to it, call create_torrent::generate() to generate +the torrent. The entry that's returned can then be bencoded into a +.torrent file using bencode(). + +Adds the file specified by ``path`` to the file_storage object. In case ``path`` +refers to a directory, files will be added recursively from the directory. + +If specified, the predicate ``p`` is called once for every file and directory that +is encountered. Files for which ``p`` returns true are added, and directories for +which ``p`` returns true are traversed. ``p`` must have the following signature: + +The path that is passed in to the predicate is the full path of the file or +directory. If no predicate is specified, all files are added, and all directories +are traversed. + +The ".." directory is never traversed. + +The ``flags`` argument should be the same as the flags passed to the `create_torrent`_ +constructor. + +This function will assume that the files added to the torrent file exists at path +``p``, read those files and hash the content and set the hashes in the ``create_torrent`` +object. The optional function ``f`` is called in between every hash that is set. ``f`` +must have the following signature: + +The overloads taking a settings_pack may be used to configure the +underlying disk access. Such as ``settings_pack::aio_threads``. + +The overloads that don't take an ``error_code&`` may throw an exception in case of a +file error, the other overloads sets the error code to reflect the error, if any. + +called when the disk cache size has dropped +below the low watermark again and we can +resume downloading from peers + + +No error + +One of the arguments in the request is invalid + +The request failed + +The specified value does not exist in the array + +The source IP address cannot be wild-carded, but +must be fully specified + +The external port cannot be a wildcard, but must +be specified + +The port mapping entry specified conflicts with a +mapping assigned previously to another client + +Internal and external port value must be the same + +The NAT implementation only supports permanent +lease times on port mappings + +RemoteHost must be a wildcard and cannot be a +specific IP address or DNS name + +ExternalPort must be a wildcard and cannot be a +specific port + +error codes for the upnp_error_category. They hold error codes +returned by UPnP routers when mapping ports + +the boost.system error category for UPnP errors + +the error was unexpected and it is unknown which operation caused it + +this is used when the bittorrent logic +determines to disconnect + +a call to iocontrol failed + +a call to ``getpeername()`` failed (querying the remote IP of a +connection) + +a call to getname failed (querying the local IP of a +connection) + +an attempt to allocate a receive buffer failed + +an attempt to allocate a send buffer failed + +writing to a file failed + +reading from a file failed + +a non-read and non-write file operation failed + +a socket write operation failed + +a socket read operation failed + +a call to open(), to create a socket socket failed + +a call to bind() on a socket failed + +an attempt to query the number of bytes available to read from a socket +failed + +a call related to bittorrent protocol encryption failed + +an attempt to connect a socket failed + +establishing an SSL connection failed + +a connection failed to satisfy the bind interface setting + +a call to listen() on a socket + +a call to the ioctl to bind a socket to a specific network device or +adapter + +a call to accept() on a socket + +convert a string into a valid network address + +enumeration network devices or adapters + +invoking stat() on a file + +copying a file + +allocating storage for a file + +creating a hard link + +removing a file + +renaming a file + +opening a file + +creating a directory + +check fast resume data against files on disk + +an unknown exception + +allocate space for a piece in the cache + +move a part-file + +read from a part file + +write to a part-file + +a hostname lookup + +create or read a symlink + +handshake with a peer or server + +set socket option + +enumeration of network routes + +moving read/write position in a file, operation_t::hostname_lookup + +an async wait operation on a timer + +call to mmap() (or windows counterpart) + +call to ftruncate() (or SetEndOfFile() on windows) + +these constants are used to identify the operation that failed, causing a +peer to disconnect + +maps an operation id (from peer_error_alert and peer_disconnected_alert) +to its name. See operation_t for the constants + + + + + + + +the types an entry can have + +returns the concrete type of the entry + +constructors directly from a specific type. +The content of the argument is copied into the +newly constructed entry + + +construct an empty entry of the specified type. +see data_type enum. + +construct from bdecode_node parsed form (see bdecode()) + +copies the structure of the right hand side into this +entry. + + +The ``integer()``, ``string()``, ``list()`` and ``dict()`` functions +are accessors that return the respective type. If the ``entry`` object +isn't of the type you request, the accessor will throw +system_error. You can ask an ``entry`` for its type through the +``type()`` function. + +If you want to create an ``entry`` you give it the type you want it to +have in its constructor, and then use one of the non-const accessors +to get a reference which you then can assign the value you want it to +have. + +The typical code to get info from a torrent file will then look like +this: + +The following code is equivalent, but a little bit shorter: + +To make it easier to extract information from a torrent file, the +class torrent_info exists. + +swaps the content of *this* with ``e``. + +All of these functions requires the entry to be a dictionary, if it +isn't they will throw ``system_error``. + +The non-const versions of the ``operator[]`` will return a reference +to either the existing element at the given key or, if there is no +element with the given key, a reference to a newly inserted element at +that key. + +The const version of ``operator[]`` will only return a reference to an +existing element at the given key. If the key is not found, it will +throw ``system_error``. + +These functions requires the entry to be a dictionary, if it isn't +they will throw ``system_error``. + +They will look for an element at the given key in the dictionary, if +the element cannot be found, they will return nullptr. If an element +with the given key is found, the return a pointer to it. + +returns a pretty-printed string representation +of the bencoded structure, with JSON-style syntax + +The ``entry`` class represents one node in a bencoded hierarchy. It works as a +variant type, it can be either a list, a dictionary (``std::map``), an integer +or a string. + +prints the bencoded structure to the ostream as a JSON-style structure. + + + + + + + +these match the socket types from socket_type.hpp +shifted one down + + + + + + + +``add()`` and ``remove()`` adds and removes a peer class to be added +to new peers based on socket type. + +``disallow()`` and ``allow()`` adds and removes a peer class to be +removed from new peers based on socket type. + +The ``peer_class`` argument cannot be greater than 31. The bitmasks representing +peer classes in the ``peer_class_type_filter`` are 32 bits. + +takes a bitmask of peer classes and returns a new bitmask of +peer classes after the rules have been applied, based on the socket type argument +(``st``). + + +``peer_class_type_filter`` is a simple container for rules for adding and subtracting +peer-classes from peers. It is applied *after* the peer class filter is applied (which +is based on the peer's IP address). + +natpmp can be NAT-PMP or PCP + + + + + + + +Truncates files larger than specified in the file_storage, saved under +the specified save_path. + +user defined alerts should use IDs greater than this + +this constant represents "max_alert_index" + 1 + +the total number of nodes and replacement nodes +in the routing table + +number of seconds since last activity + +struct to hold information about a single DHT routing table bucket + +returns the message associated with this alert + +The torrent_handle pointing to the torrent this +alert is associated with. + + +This is a base class for alerts that are associated with a +specific torrent. It contains a handle to the torrent. + +Note that by the time the client receives a torrent_alert, its +``handle`` member may be invalid. + + +The peer's IP address and port. + +the peer ID, if known. + +The peer alert is a base class for alerts that refer to a specific peer. It includes all +the information to identify the peer. i.e. ``ip`` and ``peer-id``. + + +endpoint of the listen interface being announced + +returns a 0-terminated string of the tracker's URL + +This is a base class used for alerts that are associated with a +specific tracker. It derives from torrent_alert since a tracker +is also associated with a specific torrent. + + + +'`userdata`` as set in add_torrent_params at torrent creation. +This can be used to associate this torrent with related data +in the client application more efficiently than info_hashes. + +The ``torrent_removed_alert`` is posted whenever a torrent is removed. Since +the torrent handle in its base class will usually be invalid (since the torrent +is already removed) it has the info hash as a member, to identify it. +It's posted when the ``alert_category::status`` bit is set in the alert_mask. + +Note that the ``handle`` remains valid for some time after +torrent_removed_alert is posted, as long as some internal libtorrent +task (such as an I/O task) refers to it. Additionally, other alerts like +save_resume_data_alert may be posted after torrent_removed_alert. +To synchronize on whether the torrent has been removed or not, call +torrent_handle::in_session(). This will return true before +torrent_removed_alert is posted, and false afterward. + +Even though the ``handle`` member doesn't point to an existing torrent anymore, +it is still useful for comparing to other handles, which may also no +longer point to existing torrents, but to the same non-existing torrents. + +The ``torrent_handle`` acts as a ``weak_ptr``, even though its object no +longer exists, it can still compare equal to another weak pointer which +points to the same non-existent object. + + + + +This alert is posted when the asynchronous read operation initiated by +a call to torrent_handle::read_piece() is completed. If the read failed, the torrent +is paused and an error state is set and the buffer member of the alert +is 0. If successful, ``buffer`` points to a buffer containing all the data +of the piece. ``piece`` is the piece index that was read. ``size`` is the +number of bytes that was read. + +If the operation fails, ``error`` will indicate what went wrong. + + +refers to the index of the file that completed. + +This is posted whenever an individual file completes its download. i.e. +All pieces overlapping this file have passed their hash check. + + + +returns the new and previous file name, respectively. + +refers to the index of the file that was renamed, + +This is posted as a response to a torrent_handle::rename_file() call, if the rename +operation succeeds. + + + +refers to the index of the file that was supposed to be renamed, +``error`` is the error code returned from the filesystem. + +This is posted as a response to a torrent_handle::rename_file() call, if the rename +operation failed. + +This warning means that the number of bytes queued to be written to disk +exceeds the max disk byte queue setting (``settings_pack::max_queued_disk_bytes``). +This might restrict the download rate, by not queuing up enough write jobs +to the disk I/O thread. When this alert is posted, peer connections are +temporarily stopped from downloading, until the queued disk bytes have fallen +below the limit again. Unless your ``max_queued_disk_bytes`` setting is already +high, you might want to increase it to get better performance. + +This is posted when libtorrent would like to send more requests to a peer, +but it's limited by ``settings_pack::max_out_request_queue``. The queue length +libtorrent is trying to achieve is determined by the download rate and the +assumed round-trip-time (``settings_pack::request_queue_time``). The assumed +round-trip-time is not limited to just the network RTT, but also the remote disk +access time and message handling time. It defaults to 3 seconds. The target number +of outstanding requests is set to fill the bandwidth-delay product (assumed RTT +times download rate divided by number of bytes per request). When this alert +is posted, there is a risk that the number of outstanding requests is too low +and limits the download rate. You might want to increase the ``max_out_request_queue`` +setting. + +This warning is posted when the amount of TCP/IP overhead is greater than the +upload rate limit. When this happens, the TCP/IP overhead is caused by a much +faster download rate, triggering TCP ACK packets. These packets eat into the +rate limit specified to libtorrent. When the overhead traffic is greater than +the rate limit, libtorrent will not be able to send any actual payload, such +as piece requests. This means the download rate will suffer, and new requests +can be sent again. There will be an equilibrium where the download rate, on +average, is about 20 times the upload rate limit. If you want to maximize the +download rate, increase the upload rate limit above 5% of your download capacity. + +This is the same warning as ``upload_limit_too_low`` but referring to the download +limit instead of upload. This suggests that your download rate limit is much lower +than your upload capacity. Your upload rate will suffer. To maximize upload rate, +make sure your download rate limit is above 5% of your upload capacity. + +We're stalled on the disk. We want to write to the socket, and we can write +but our send buffer is empty, waiting to be refilled from the disk. +This either means the disk is slower than the network connection +or that our send buffer watermark is too small, because we can +send it all before the disk gets back to us. +The number of bytes that we keep outstanding, requested from the disk, is calculated +as follows: + + min(512, max(upload_rate * send_buffer_watermark_factor / 100, send_buffer_watermark)) + +If you receive this alert, you might want to either increase your ``send_buffer_watermark`` +or ``send_buffer_watermark_factor``. + +If the half (or more) of all upload slots are set as optimistic unchoke slots, this +warning is issued. You probably want more regular (rate based) unchoke slots. + +If the disk write queue ever grows larger than half of the cache size, this warning +is posted. The disk write queue eats into the total disk cache and leaves very little +left for the actual cache. This causes the disk cache to oscillate in evicting large +portions of the cache before allowing peers to download any more, onto the disk write +queue. Either lower ``max_queued_disk_bytes`` or increase ``cache_size``. + + + +This is generated if outgoing peer connections are failing because of *address in use* +errors, indicating that ``settings_pack::outgoing_ports`` is set and is too small of +a range. Consider not using the ``outgoing_ports`` setting at all, or widen the range to +include more ports. + + + + + + + +This alert is generated when a limit is reached that might have a negative impact on +upload or download rate performance. + + + +the new state of the torrent. + +the previous state. + +Generated whenever a torrent changes its state. + + + +This member says how many times in a row this tracker has failed. + +the error code indicating why the tracker announce failed. If it is +is ``lt::errors::tracker_failure`` the failure_reason() might contain +a more detailed description of why the tracker rejected the request. +HTTP status codes indicating errors are also set in this field. + + +if the tracker sent a "failure reason" string, it will be returned +here. + +the bittorrent protocol version that was announced + +This alert is generated on tracker time outs, premature disconnects, +invalid response or a HTTP response other than "200 OK". From the alert +you can get the handle to the torrent the tracker belongs to. + + + +the message associated with this warning + +the bittorrent protocol version that was announced + +This alert is triggered if the tracker reply contains a warning field. +Usually this means that the tracker announce was successful, but the +tracker has a message to the client. + + + +the data returned in the scrape response. These numbers +may be -1 if the response was malformed. + +the bittorrent protocol version that was scraped + +This alert is generated when a scrape request succeeds. + + + +the error itself. This may indicate that the tracker sent an error +message (``error::tracker_failure``), in which case it can be +retrieved by calling ``error_message()``. + +if the error indicates there is an associated message, this returns +that message. Otherwise and empty string. + +the bittorrent protocol version that was scraped + +If a scrape request fails, this alert is generated. This might be due +to the tracker timing out, refusing connection or returning an http response +code indicating an error. + + + +tells how many peers the tracker returned in this response. This is +not expected to be greater than the ``num_want`` settings. These are not necessarily +all new peers, some of them may already be connected. + +the bittorrent protocol version that was announced + +This alert is only for informational purpose. It is generated when a tracker announce +succeeds. It is generated regardless what kind of tracker was used, be it UDP, HTTP or +the DHT. + + + + +This alert is generated each time the DHT receives peers from a node. ``num_peers`` +is the number of peers we received in this packet. Typically these packets are +received from multiple DHT nodes, and so the alerts are typically generated +a few at a time. + + + +specifies what event was sent to the tracker. See event_t. + +the bittorrent protocol version that is announced + +This alert is generated each time a tracker announce is sent (or attempted to be sent). +There are no extra data members in this alert. The url can be found in the base class +however. + + + + +This alert is generated when a finished piece fails its hash check. You can get the handle +to the torrent which got the failed piece and the index of the piece itself from the alert. + + + +This alert is generated when a peer is banned because it has sent too many corrupt pieces +to us. ``ip`` is the endpoint to the peer that was banned. + + + +This alert is generated when a peer is un-snubbed. Essentially when it was snubbed for stalling +sending data, and now it started sending data again. + + + +This alert is generated when a peer is snubbed, when it stops sending data when we request +it. + + + +a 0-terminated string of the low-level operation that failed, or nullptr if +there was no low level disk operation. + +tells you what error caused this alert. + +This alert is generated when a peer sends invalid data over the peer-peer protocol. The peer +will be disconnected, but you get its ip address from the alert, to identify it. + + + + + + +Tells you if the peer was incoming or outgoing + + +This alert is posted every time an incoming peer connection both +successfully passes the protocol handshake and is associated with a +torrent, or an outgoing peer connection attempt succeeds. For arbitrary +incoming connections, see incoming_connection_alert. + + + +the kind of socket this peer was connected over + +the operation or level where the error occurred. Specified as an +value from the operation_t enum. Defined in operations.hpp. + +tells you what error caused peer to disconnect. + +the reason the peer disconnected (if specified) + +This alert is generated when a peer is disconnected for any reason (other than the ones +covered by peer_error_alert ). + + + +the request we received from the peer + +true if we have this piece + +true if the peer indicated that it was interested to download before +sending the request + +if this is true, the peer is not allowed to download this piece because +of super-seeding rules. + +This is a debug alert that is generated by an incoming invalid piece request. +``ip`` is the address of the peer and the ``request`` is the actual incoming +request from the peer. See peer_request for more info. + + + +This alert is generated when a torrent switches from being a downloader to a seed. +It will only be generated once per torrent. It contains a torrent_handle to the +torrent in question. + + +the index of the piece that finished + +this alert is posted every time a piece completes downloading +and passes the hash check. This alert derives from torrent_alert +which contains the torrent_handle to the torrent the piece belongs to. +Note that being downloaded and passing the hash check may happen before +the piece is also fully flushed to disk. So torrent_handle::have_piece() +may still return false + + + +This alert is generated when a peer rejects or ignores a piece request. + + + +This alert is generated when a block request times out. + + + +This alert is generated when a block request receives a response. + + + +This alert is generated when a block request is sent to a peer. + + + + +This alert is generated when a block is received that was not requested or +whose request timed out. + + + +the path the torrent was moved to and from, respectively. + +The ``storage_moved_alert`` is generated when all the disk IO has +completed and the files have been moved, as an effect of a call to +``torrent_handle::move_storage``. This is useful to synchronize with the +actual disk. The ``storage_path()`` member return the new path of the +storage. + + + + +If the error happened for a specific file, this returns its path. + +this indicates what underlying operation caused the error + +The ``storage_moved_failed_alert`` is generated when an attempt to move the storage, +via torrent_handle::move_storage(), fails. + + + +The info-hash of the torrent that was just deleted. Most of +the time the torrent_handle in the ``torrent_alert`` will be invalid by the time +this alert arrives, since the torrent is being deleted. The ``info_hashes`` member +is hence the main way of identifying which torrent just completed the delete. + +This alert is generated when a request to delete the files of a torrent complete. + +This alert is posted in the ``alert_category::storage`` category, and that bit +needs to be set in the alert_mask. + + + +tells you why it failed. + +the info hash of the torrent whose files failed to be deleted + +This alert is generated when a request to delete the files of a torrent fails. +Just removing a torrent from the session cannot fail + + + +the ``params`` object is populated with the torrent file whose resume +data was saved. It is suitable to be: + +* added to a session with add_torrent() or async_add_torrent() +* saved to disk with write_resume_data() +* turned into a magnet link with make_magnet_uri() +* saved as a .torrent file with write_torrent_file() + +This alert is generated as a response to a ``torrent_handle::save_resume_data`` request. +It is generated once the disk IO thread is done writing the state for this torrent. + + + +the error code from the resume_data failure + +This alert is generated instead of ``save_resume_data_alert`` if there was an error +generating the resume data. ``error`` describes what went wrong. + + + +This alert is generated as a response to a ``torrent_handle::pause`` request. It is +generated once all disk IO is complete and the files in the torrent have been closed. +This is useful for synchronizing with the disk. + + + +This alert is generated as a response to a torrent_handle::resume() request. It is +generated when a torrent goes from a paused state to an active state. + + + +This alert is posted when a torrent completes checking. i.e. when it transitions +out of the ``checking files`` state into a state where it is ready to start downloading + + + +the error the web seed encountered. If this is not set, the server +sent an error message, call ``error_message()``. + +the URL the error is associated with + +in case the web server sent an error message, this function returns +it. + +This alert is generated when a HTTP seed name lookup fails. + + + +the error code describing the error. + +indicates which underlying operation caused the error + +the file that experienced the error + +If the storage fails to read or write files that it needs access to, this alert is +generated and the torrent is paused. + + + +indicates what failed when parsing the metadata. This error is +what's returned from lazy_bdecode(). + +This alert is generated when the metadata has been completely received and the info-hash +failed to match it. i.e. the metadata that was received was corrupt. libtorrent will +automatically retry to fetch it in this case. This is only relevant when running a +torrent-less download, with the metadata extension provided by libtorrent. + + + +This alert is generated when the metadata has been completely received and the torrent +can start downloading. It is not generated on torrents that are started with metadata, but +only those that needs to download it from peers (when utilizing the libtorrent extension). + +There are no additional data members in this alert. + +Typically, when receiving this alert, you would want to save the torrent file in order +to load it back up again when the session is restarted. Here's an example snippet of +code to do that: + +the source address associated with the error (if any) + +the operation that failed + +the error code describing the error + +This alert is posted when there is an error on a UDP socket. The +UDP sockets are used for all uTP, DHT and UDP tracker traffic. They are +global to the session. + + + +the IP address that is believed to be our external IP + +Whenever libtorrent learns about the machines external IP, this alert is +generated. The external IP address can be acquired from the tracker (if it +supports that) or from peers that supports the extension protocol. +The address can be accessed through the ``external_address`` member. + + + +the network device libtorrent attempted to listen on, or the IP address + +the error the system returned + +the underlying operation that failed + +the type of listen socket this alert refers to. + +the address libtorrent attempted to listen on +see alert documentation for validity of this value + +the port libtorrent attempted to listen on +see alert documentation for validity of this value + +This alert is generated when none of the ports, given in the port range, to +session can be opened for listening. The ``listen_interface`` member is the +interface that failed, ``error`` is the error code describing the failure. + +In the case an endpoint was created before generating the alert, it is +represented by ``address`` and ``port``. The combinations of socket type +and operation in which such address and port are not valid are: +accept - i2p +accept - socks5 +enum_if - tcp + +libtorrent may sometimes try to listen on port 0, if all other ports failed. +Port 0 asks the operating system to pick a port that's free). If that fails +you may see a listen_failed_alert with port 0 even if you didn't ask to +listen on it. + + + +the address libtorrent ended up listening on. This address +refers to the local interface. + +the port libtorrent ended up listening on. + +the type of listen socket this alert refers to. + +This alert is posted when the listen port succeeds to be opened on a +particular interface. ``address`` and ``port`` is the endpoint that +successfully was opened for listening. + + + +refers to the mapping index of the port map that failed, i.e. +the index returned from add_mapping(). + +UPnP or NAT-PMP + +the local network the port mapper is running on + +tells you what failed. + +This alert is generated when a NAT router was successfully found but some +part of the port mapping request failed. It contains a text message that +may help the user figure out what is wrong. This alert is not generated in +case it appears the client is not running on a NAT:ed network or if it +appears there is no NAT router that can be remote controlled to add port +mappings. + + + +refers to the mapping index of the port map that failed, i.e. +the index returned from add_mapping(). + +the external port allocated for the mapping. + + + +the local network the port mapper is running on + +This alert is generated when a NAT router was successfully found and +a port was successfully mapped on it. On a NAT:ed network with a NAT-PMP +capable router, this is typically generated once when mapping the TCP +port and, if DHT is enabled, when the UDP port is mapped. + + + + +the local network the port mapper is running on + +the message associated with this log line + +This alert is generated to log informational events related to either +UPnP or NAT-PMP. They contain a log line and the type (0 = NAT-PMP +and 1 = UPnP). Displaying these messages to an end user is only useful +for debugging the UPnP or NAT-PMP implementation. This alert is only +posted if the alert_category::port_mapping_log flag is enabled in +the alert mask. + + + + +If the error happened to a specific file, this returns the path to it. + +the underlying operation that failed + +This alert is generated when a fast resume file has been passed to +add_torrent() but the files on disk did not match the fast resume file. +The error_code explains the reason why the resume file was rejected. + + + + + + + + + + + + +the reason for the peer being blocked. Is one of the values from the +reason_t enum. + +This alert is posted when an incoming peer connection, or a peer that's about to be added +to our peer list, is blocked for some reason. This could be any of: + +* the IP filter +* i2p mixed mode restrictions (a normal peer is not allowed on an i2p swarm) +* the port filter +* the peer has a low port and ``no_connect_privileged_ports`` is enabled +* the protocol of the peer is blocked (uTP/TCP blocking) + + + + +This alert is generated when a DHT node announces to an info-hash on our +DHT node. It belongs to the ``alert_category::dht`` category. + + + + +This alert is generated when a DHT node sends a ``get_peers`` message to +our DHT node. It belongs to the ``alert_category::dht`` category. + + +This alert is posted when the disk cache has been flushed for a specific +torrent as a result of a call to torrent_handle::flush_cache(). This +alert belongs to the ``alert_category::storage`` category, which must be +enabled to let this alert through. The alert is also posted when removing +a torrent from the session, once the outstanding cache flush is complete +and the torrent does no longer have any files open. + + + +This alert is generated when we receive a local service discovery message +from a peer for a torrent we're currently participating in. + + + +The tracker ID returned by the tracker + +This alert is posted whenever a tracker responds with a ``trackerid``. +The tracker ID is like a cookie. libtorrent will store the tracker ID +for this tracker and repeat it in subsequent announces. + + + +This alert is posted when the initial DHT bootstrap is done. + + + +specifies which error the torrent encountered. + +the filename (or object) the error occurred on. + +This is posted whenever a torrent is transitioned into the error state. +If the error code is duplicate_torrent (error_code_enum) error, it suggests two magnet +links ended up resolving to the same hybrid torrent. For more details, +see BitTorrent-v2-torrents_. + + + +This is always posted for SSL torrents. This is a reminder to the client that +the torrent won't work unless torrent_handle::set_ssl_certificate() is called with +a valid certificate. Valid certificates MUST be signed by the SSL certificate +in the .torrent file. + + + +tells you what kind of socket the connection was accepted + +is the IP address and port the connection came from. + +The incoming connection alert is posted every time we successfully accept +an incoming connection, through any mean. The most straight-forward ways +of accepting incoming connections are through the TCP listen socket and +the UDP listen socket for uTP sockets. However, connections may also be +accepted through a Socks5 or i2p listen socket, or via an SSL listen +socket. + + + +This contains copies of the most important fields from the original +add_torrent_params object, passed to add_torrent() or +async_add_torrent(). Specifically, these fields are copied: + +* version +* ti +* name +* save_path +* userdata +* tracker_id +* flags +* info_hash + +the info_hash field will be updated with the info-hash of the torrent +specified by ``ti``. + +set to the error, if one occurred while adding the torrent. + +This alert is always posted when a torrent was attempted to be added +and contains the return status of the add operation. The torrent handle of the new +torrent can be found as the ``handle`` member in the base class. If adding +the torrent failed, ``error`` contains the error code. + + + +contains the torrent status of all torrents that changed since last +time this message was posted. Note that you can map a torrent status +to a specific torrent via its ``handle`` member. The receiving end is +suggested to have all torrents sorted by the torrent_handle or hashed +by it, for efficient updates. + +This alert is only posted when requested by the user, by calling +session::post_torrent_updates() on the session. It contains the torrent +status of all torrents that changed since last time this message was +posted. Its category is ``alert_category::status``, but it's not subject to +filtering, since it's only manually posted anyway. + + + +An array are a mix of *counters* and *gauges*, which meanings can be +queries via the session_stats_metrics() function on the session. The +mapping from a specific metric to an index into this array is constant +for a specific version of libtorrent, but may differ for other +versions. The intended usage is to request the mapping, i.e. call +session_stats_metrics(), once on startup, and then use that mapping to +interpret these values throughout the process' runtime. + +For more information, see the session-statistics_ section. + +The session_stats_alert is posted when the user requests session statistics by +calling post_session_stats() on the session object. This alert does not +have a category, since it's only posted in response to an API call. It +is not subject to the alert_mask filter. + +the ``message()`` member function returns a string representation of the values that +properly match the line returned in ``session_stats_header_alert::message()``. + +this specific output is parsed by tools/parse_session_stats.py +if this is changed, that parser should also be changed + + + +the error code + +the operation that failed + +posted when something fails in the DHT. This is not necessarily a fatal +error, but it could prevent proper operation + + + +the target hash of the immutable item. This must +match the SHA-1 hash of the bencoded form of ``item``. + +the data for this item + +this alert is posted as a response to a call to session::get_item(), +specifically the overload for looking up immutable items in the DHT. + + + +the public key that was looked up + +the signature of the data. This is not the signature of the +plain encoded form of the item, but it includes the sequence number +and possibly the hash as well. See the dht_store document for more +information. This is primarily useful for echoing back in a store +request. + +the sequence number of this item + +the salt, if any, used to lookup and store this item. If no +salt was used, this is an empty string + +the data for this item + +the last response for mutable data is authoritative. + +this alert is posted as a response to a call to session::get_item(), +specifically the overload for looking up mutable items in the DHT. + + + +the target hash the item was stored under if this was an *immutable* +item. + +if a mutable item was stored, these are the public key, signature, +salt and sequence number the item was stored under. + +DHT put operation usually writes item to k nodes, maybe the node +is stale so no response, or the node doesn't support 'put', or the +token for write is out of date, etc. num_success is the number of +successful responses we got from the puts. + +this is posted when a DHT put operation completes. This is useful if the +client is waiting for a put to complete before shutting down for instance. + + + +the error that occurred in the i2p SAM connection + +this alert is used to report errors in the i2p SAM connection + + + +the info_hash of the torrent we're looking for peers for. + +if this was an obfuscated lookup, this is the info-hash target +actually sent to the node. + +the endpoint we're sending this query to + +This alert is generated when we send a get_peers request +It belongs to the ``alert_category::dht`` category. + + + +returns the log message + +This alert is posted by some session wide event. Its main purpose is +trouble shooting and debugging. It's not enabled by the default alert +mask and is enabled by the ``alert_category::session_log`` bit. +Furthermore, it's by default disabled as a build configuration. + + + +returns the log message + +This alert is posted by torrent wide events. It's meant to be used for +trouble shooting and debugging. It's not enabled by the default alert +mask and is enabled by the ``alert_category::torrent_log`` bit. By +default it is disabled as a build configuration. + + + + + + +describes whether this log refers to in-flow or out-flow of the +peer. The exception is ``info`` which is neither incoming or outgoing. + + + +string literal indicating the kind of event. For messages, this is the +message name. + + +returns the log message + +This alert is posted by events specific to a peer. It's meant to be used +for trouble shooting and debugging. It's not enabled by the default alert +mask and is enabled by the ``alert_category::peer_log`` bit. By +default it is disabled as a build configuration. + + + +the local network the corresponding local service discovery is running +on + +The error code + +posted if the local service discovery socket fails to start properly. +it's categorized as ``alert_category::error``. + +string literal indicating which kind of lookup this is + +the number of outstanding request to individual nodes +this lookup has right now + +the total number of requests that have timed out so far +for this lookup + +the total number of responses we have received for this +lookup so far for this lookup + +the branch factor for this lookup. This is the number of +nodes we keep outstanding requests to in parallel by default. +when nodes time out we may increase this. + +the number of nodes left that could be queries for this +lookup. Many of these are likely to be part of the trail +while performing the lookup and would never end up actually +being queried. + +the number of seconds ago the +last message was sent that's still +outstanding + +the number of outstanding requests +that have exceeded the short timeout +and are considered timed out in the +sense that they increased the branch +factor + +the node-id or info-hash target for this lookup + +holds statistics about a current dht_lookup operation. +a DHT lookup is the traversal of nodes, looking up a +set of target nodes in the DHT for retrieving and possibly +storing information in the DHT + + + +a vector of the currently running DHT lookups. + +contains information about every bucket in the DHT routing +table. + +the node ID of the DHT node instance + +the local socket this DHT node is running on + +contains current DHT state. Posted in response to session::post_dht_stats(). + + + +the request this peer sent to us + +posted every time an incoming request from a peer is accepted and queued +up for being serviced. This alert is only posted if +the alert_category::incoming_request flag is enabled in the alert +mask. + + + + + + + + + +the log message + +the module, or part, of the DHT that produced this log message. + +debug logging of the DHT when alert_category::dht_log is set in the alert +mask. + + + + + + +returns a pointer to the packet buffer and size of the packet, +respectively. This buffer is only valid for as long as the alert itself +is valid, which is owned by libtorrent and reclaimed whenever +pop_alerts() is called on the session. + +whether this is an incoming or outgoing packet. + +the DHT node we received this packet from, or sent this packet to +(depending on ``direction``). + +This alert is posted every time a DHT message is sent or received. It is +only posted if the ``alert_category::dht_log`` alert category is +enabled. It contains a verbatim copy of the message. + + + + + + +Posted when we receive a response to a DHT get_peers request. + + + + + +This is posted exactly once for every call to session_handle::dht_direct_request. +If the request failed, response() will return a default constructed bdecode_node. + + + + +this is a bitmask of which features were enabled for this particular +pick. The bits are defined in the picker_flags_t enum. + + +this is posted when one or more blocks are picked by the piece picker, +assuming the verbose piece picker logging is enabled (see +alert_category::picker_log). + + + +The error code, if one is associated with this error + +this alert is posted when the session encounters a serious error, +potentially fatal + + + +the local DHT node's node-ID this routing table belongs to + +the number of nodes in the routing table and the actual nodes. + +posted in response to a call to session::dht_live_nodes(). It contains the +live nodes from the DHT routing table of one of the DHT nodes running +locally. + + + +The session_stats_header alert is posted the first time +post_session_stats() is called + +the ``message()`` member function returns a string representation of the +header that properly match the stats values string returned in +``session_stats_alert::message()``. + +this specific output is parsed by tools/parse_session_stats.py +if this is changed, that parser should also be changed + + + +id of the node the request was sent to (and this response was received from) + +the node the request was sent to (and this response was received from) + +the interval to wait before making another request to this node + +This field indicates how many info-hash keys are currently in the node's storage. +If the value is larger than the number of returned samples it indicates that the +indexer may obtain additional samples after waiting out the interval. + +returns the number of info-hashes returned by the node, as well as the +actual info-hashes. ``num_samples()`` is more efficient than +``samples().size()``. + +The total number of nodes returned by ``nodes()``. + +This is the set of more DHT nodes returned by the request. + +The information is included so that indexing nodes can perform a key +space traversal with a single RPC per node by adjusting the target +value for each RPC. + +posted as a response to a call to session::dht_sample_infohashes() with +the information from the DHT response message. + + + +This alert is posted when a block intended to be sent to a peer is placed in the +send buffer. Note that if the connection is closed before the send buffer is sent, +the alert may be posted without the bytes having been sent to the peer. +It belongs to the ``alert_category::upload`` category. + + + +a bitmask indicating which alerts were dropped. Each bit represents the +alert type ID, where bit 0 represents whether any alert of type 0 has +been dropped, and so on. + +this alert is posted to indicate to the client that some alerts were +dropped. Dropped meaning that the alert failed to be delivered to the +client. The most common cause of such failure is that the internal alert +queue grew too big (controlled by alert_queue_size). + + + +the error + +the operation that failed + +the endpoint configured as the proxy + +this alert is posted with SOCKS5 related errors, when a SOCKS5 proxy is +configured. It's enabled with the alert_category::error alert category. + + + +the error + +the operation that failed + +posted when a prioritize_files() or file_priority() update of the file +priorities complete, which requires a round-trip to the disk thread. + +If the disk operation fails this alert won't be posted, but a +file_error_alert is posted instead, and the torrent is stopped. + + + +this alert may be posted when the initial checking of resume data and files +on disk (just existence, not piece hashes) completes. If a file belonging +to the torrent is found on disk, but is larger than the file in the +torrent, that's when this alert is posted. +the client may want to call truncate_files() in that case, or perhaps +interpret it as a sign that some other file is in the way, that shouldn't +be overwritten. + + + +the handle to the torrent in conflict. The swarm associated with this +torrent handle did not download the metadata, but the downloaded +metadata collided with this swarm's info-hash. + +the metadata that was received by one of the torrents in conflict. +One way to resolve the conflict is to remove both failing torrents +and re-add it using this metadata + +this alert is posted when two separate torrents (magnet links) resolve to +the same torrent, thus causing the same torrent being added twice. In +that case, both torrents enter an error state with ``duplicate_torrent`` +as the error code. This alert is posted containing the metadata. For more +information, see BitTorrent-v2-torrents_. +The torrent this alert originated from was the one that downloaded the + +metadata (i.e. the `handle` member from the torrent_alert base class). + + + +the list of the currently connected peers + +posted when torrent_handle::post_peer_info() is called + + + +the list of the files in the torrent + +posted when torrent_handle::post_file_progress() is called + + + +info about pieces being downloaded for the torrent + +storage for block_info pointers in partial_piece_info objects + +posted when torrent_handle::post_download_queue() is called + + + +info about pieces being downloaded for the torrent + +posted when torrent_handle::post_piece_availability() is called + + + +list of trackers and their status for the torrent + +posted when torrent_handle::post_trackers() is called + + + + + +the name of the counter or gauge + +the index into the session stats array, where the underlying value of +this counter or gauge is found. The session stats array is part of the +session_stats_alert object. + +describes one statistics metric from the session. For more information, +see the session-statistics_ section. + +This free function returns the list of available metrics exposed by +libtorrent's statistics API. Each metric has a name and a *value index*. +The value index is the index into the array in session_stats_alert where +this metric's value can be found when the session stats is sampled (by +calling post_session_stats()). + +given a name of a metric, this function returns the counter index of it, +or -1 if it could not be found. The counter index is the index into the +values array returned by session_stats_alert. + + +indicates that IPs in this range should not be connected +to nor accepted as incoming connections + +the flags defined for an IP range + +returns true if the filter does not contain any rules + +Adds a rule to the filter. ``first`` and ``last`` defines a range of +ip addresses that will be marked with the given flags. The ``flags`` +can currently be 0, which means allowed, or ``ip_filter::blocked``, which +means disallowed. + +precondition: +``first.is_v4() == last.is_v4() && first.is_v6() == last.is_v6()`` + +postcondition: +``access(x) == flags`` for every ``x`` in the range [``first``, ``last``] + +This means that in a case of overlapping ranges, the last one applied takes +precedence. + +Returns the access permissions for the given address (``addr``). The permission +can currently be 0 or ``ip_filter::blocked``. The complexity of this operation +is O(``log`` n), where n is the minimum number of non-overlapping ranges to describe +the current filter. + +This function will return the current state of the filter in the minimum number of +ranges possible. They are sorted from ranges in low addresses to high addresses. Each +entry in the returned vector is a range with the access control specified in its +``flags`` field. + +The return value is a tuple containing two range-lists. One for IPv4 addresses +and one for IPv6 addresses. + +The ``ip_filter`` class is a set of rules that uniquely categorizes all +ip addresses as allowed or disallowed. The default constructor creates +a single rule that allows all addresses (0.0.0.0 - 255.255.255.255 for +the IPv4 range, and the equivalent range covering all addresses for the +IPv6 range). + +A default constructed ip_filter does not filter any address. + + +this flag indicates that destination ports in the +range should not be connected to + +the defined flags for a port range + +set the flags for the specified port range (``first``, ``last``) to +``flags`` overwriting any existing rule for those ports. The range +is inclusive, i.e. the port ``last`` also has the flag set on it. + +test the specified port (``port``) for whether it is blocked +or not. The returned value is the flags set for this port. +see access_flags. + +the port filter maps non-overlapping port ranges to flags. This +is primarily used to indicate whether a range of ports should +be connected to or not. The default is to have the full port +range (0-65535) set to flag 0. + + + +returns the new value + + + +this is a simple posix disk I/O back-end, used for systems that don't +have a 64 bit virtual address space or don't support memory mapped files. +It's implemented using portable C file functions and is single-threaded. + +The original BitTorrent version, using SHA-1 hashes + +Version 2 of the BitTorrent protocol, using SHA-256 hashes + + +BitTorrent version enumerator + +The default constructor creates an object that has neither a v1 or v2 +hash. + +For backwards compatibility, make it possible to construct directly +from a v1 hash. This constructor allows *implicit* conversion from a +v1 hash, but the implicitness is deprecated. + +returns true if the corresponding info hash is present in this +object. + +returns the has for the specified protocol version + +returns the v2 (truncated) info-hash, if there is one, otherwise +returns the v1 info-hash + + + +calls the function object ``f`` for each hash that is available. +starting with v1. The signature of ``F`` is:: + + void(sha1_hash const&, protocol_version); + + + + +class holding the info-hash of a torrent. It can hold a v1 info-hash +(SHA-1) or a v2 info-hash (SHA-256) or both. + + + If ``has_v2()`` is false then the v1 hash might actually be a truncated + v2 hash + +Enables alerts that report an error. This includes: + +* tracker errors +* tracker warnings +* file errors +* resume data failures +* web seed errors +* .torrent files errors +* listen socket errors +* port mapping errors + +Enables alerts when peers send invalid requests, get banned or +snubbed. + +Enables alerts for port mapping events. For NAT-PMP and UPnP. + +Enables alerts for events related to the storage. File errors and +synchronization events for moving the storage, renaming files etc. + +Enables all tracker events. Includes announcing to trackers, +receiving responses, warnings and errors. + +Low level alerts for when peers are connected and disconnected. + +Enables alerts for when a torrent or the session changes state. + +Alerts when a peer is blocked by the ip blocker or port blocker. + +Alerts when some limit is reached that might limit the download +or upload rate. + +Alerts on events in the DHT node. For incoming searches or +bootstrapping being done etc. + +If you enable these alerts, you will receive a stats_alert +approximately once every second, for every active torrent. +These alerts contain all statistics counters for the interval since +the lasts stats alert. + +Enables debug logging alerts. These are available unless libtorrent +was built with logging disabled (``TORRENT_DISABLE_LOGGING``). The +alerts being posted are log_alert and are session wide. + +Enables debug logging alerts for torrents. These are available +unless libtorrent was built with logging disabled +(``TORRENT_DISABLE_LOGGING``). The alerts being posted are +torrent_log_alert and are torrent wide debug events. + +Enables debug logging alerts for peers. These are available unless +libtorrent was built with logging disabled +(``TORRENT_DISABLE_LOGGING``). The alerts being posted are +peer_log_alert and low-level peer events and messages. + +enables the incoming_request_alert. + +enables dht_log_alert, debug logging for the DHT + +enable events from pure dht operations not related to torrents + +enables port mapping log events. This log is useful +for debugging the UPnP or NAT-PMP implementation + +enables verbose logging from the piece picker. + +alerts when files complete downloading + +alerts when pieces complete downloading or fail hash check + +alerts when we upload blocks to other peers + +alerts on individual blocks being requested, downloading, finished, +rejected, time-out and cancelled. This is likely to post alerts at a +high rate. + +The full bitmask, representing all available categories. + +since the enum is signed, make sure this isn't +interpreted as -1. For instance, boost.python +does that and fails when assigning it to an +unsigned parameter. + + + +a timestamp is automatically created in the constructor + +returns an integer that is unique to this alert type. It can be +compared against a specific alert by querying a static constant called ``alert_type`` +in the alert. It can be used to determine the run-time type of an alert* in +order to cast to that alert type and access specific members. + +e.g: + +returns a string literal describing the type of the alert. It does +not include any information that might be bundled with the alert. + +generate a string describing the alert and the information bundled +with it. This is mainly intended for debug and development use. It is not suitable +to use this for applications that may be localized. Instead, handle each alert +type individually and extract and render the information from the alert depending +on the locale. + +returns a bitmask specifying which categories this alert belong to. + +The ``alert`` class is the base class that specific messages are derived from. +alert types are not copyable, and cannot be constructed by the client. The +pointers returned by libtorrent are short lived (the details are described +under session_handle::pop_alerts()) + +When you get an alert, you can use ``alert_cast<>`` to attempt to cast the +pointer to a specific alert type, in order to query it for more +information. + + ``alert_cast<>`` can only cast to an exact alert type, not a base class + +All pieces will be written to their final position, all files will be +allocated in full when the torrent is first started. This mode minimizes +fragmentation but could be a costly operation. + +All pieces will be written to the place where they belong and sparse files +will be used. This is the recommended, and default mode. + +types of storage allocation used for add_torrent_params::storage_mode. + + + + + +this is not an enum value, but a flag that can be set in the return +from async_check_files, in case an existing file was found larger than +specified in the torrent. i.e. it has garbage at the end +the status_t field is used for this to preserve ABI. + +return values from check_fastresume, and move_storage + +replace any files in the destination when copying +or moving the storage + +if any files that we want to copy exist in the destination +exist, fail the whole operation and don't perform +any copy or move. There is an inherent race condition +in this mode. The files are checked for existence before +the operation starts. In between the check and performing +the copy, the destination files may be created, in which +case they are replaced. + +if any file exist in the target, take those files instead +of the ones we may have in the source. + +don't move any source files, just forget about them +and begin checking files at new save path + +don't move any source files, just change save path +and continue working without any checks + +flags for async_move_storage + + + +a parameter pack used to construct the storage for a torrent, used in +disk_interface + +open the file for reading only + +open the file for writing only + +open the file for reading and writing + +the mask for the bits determining read or write mode + +open the file in sparse mode (if supported by the +filesystem). + +don't update the access timestamps on the file (if +supported by the operating system and filesystem). +this generally improves disk performance. + +When this is not set, the kernel is hinted that access to this file will +be made sequentially. + +the file is memory mapped + +the index of the file this entry refers to into the ``file_storage`` +file list of this torrent. This starts indexing at 0. + +``open_mode`` is a bitmask of the file flags this file is currently +opened with. For possible flags, see file_open_mode_t. + +Note that the read/write mode is not a bitmask. The two least significant bits are used +to represent the read/write mode. Those bits can be masked out using the ``rw_mask`` constant. + +a (high precision) timestamp of when the file was last used. + +this contains information about a file that's currently open by the +libtorrent disk I/O subsystem. It's associated with a single torrent. + +force making a copy of the cached block, rather than getting a +reference to a block already in the cache. This is used the block is +expected to be overwritten very soon, by async_write()`, and we need +access to the previous content. + +hint that there may be more disk operations with sequential access to +the file + +don't keep the read block in cache. This is a hint that this block is +unlikely to be read again anytime soon, and caching it would be +wasteful. + +compute a v1 piece hash. This is only used by the async_hash() call. +If this flag is not set in the async_hash() call, the SHA-1 piece +hash does not need to be computed. + +this flag instructs a hash job that we just completed this piece, and +it should be flushed to disk + +this is called when a new torrent is added. The shared_ptr can be +used to hold the internal torrent object alive as long as there are +outstanding disk operations on the storage. +The returned storage_holder is an owning reference to the underlying +storage that was just created. It is fundamentally a storage_index_t + +remove the storage with the specified index. This is not expected to +delete any files from disk, just to clean up any resources associated +with the specified storage. + +perform a read or write operation from/to the specified storage +index and the specified request. When the operation completes, call +handler possibly with a disk_buffer_holder, holding the buffer with +the result. Flags may be set to affect the read operation. See +disk_job_flags_t. + +The disk_observer is a callback to indicate that +the store buffer/disk write queue is below the watermark to let peers +start writing buffers to disk again. When ``async_write()`` returns +``true``, indicating the write queue is full, the peer will stop +further writes and wait for the passed-in ``disk_observer`` to be +notified before resuming. + +Note that for ``async_read``, the peer_request (``r``) is not +necessarily aligned to blocks (but it is most of the time). However, +all writes (passed to ``async_write``) are guaranteed to be block +aligned. + +Compute hash(es) for the specified piece. Unless the v1_hash flag is +set (in ``flags``), the SHA-1 hash of the whole piece does not need +to be computed. + +The `v2` span is optional and can be empty, which means v2 hashes +should not be computed. If v2 is non-empty it must be at least large +enough to hold all v2 blocks in the piece, and this function will +fill in the span with the SHA-256 block hashes of the piece. + +computes the v2 hash (SHA-256) of a single block. The block at +``offset`` in piece ``piece``. + +called to request the files for the specified storage/torrent be +moved to a new location. It is the disk I/O object's responsibility +to synchronize this with any currently outstanding disk operations to +the storage. Whether files are replaced at the destination path or +not is controlled by ``flags`` (see move_flags_t). + +This is called on disk I/O objects to request they close all open +files for the specified storage/torrent. If file handles are not +pooled/cached, it can be a no-op. For truly asynchronous disk I/O, +this should provide at least one point in time when all files are +closed. It is possible that later asynchronous operations will +re-open some of the files, by the time this completion handler is +called, that's fine. + +this is called when torrents are added to validate their resume data +against the files on disk. This function is expected to do a few things: + +if ``links`` is non-empty, it contains a string for each file in the +torrent. The string being a path to an existing identical file. The +default behavior is to create hard links of those files into the +storage of the new torrent (specified by ``storage``). An empty +string indicates that there is no known identical file. This is part +of the "mutable torrent" feature, where files can be reused from +other torrents. + +The ``resume_data`` points the resume data passed in by the client. + +If the ``resume_data->flags`` field has the seed_mode flag set, all +files/pieces are expected to be on disk already. This should be +verified. Not just the existence of the file, but also that it has +the correct size. + +Any file with a piece set in the ``resume_data->have_pieces`` bitmask +should exist on disk, this should be verified. Pad files and files +with zero priority may be skipped. + +This is called when a torrent is stopped. It gives the disk I/O +object an opportunity to flush any data to disk that's currently kept +cached. This function should at least do the same thing as +async_release_files(). + +This function is called when the name of a file in the specified +storage has been requested to be renamed. The disk I/O object is +responsible for renaming the file without racing with other +potentially outstanding operations against the file (such as read, +write, move, etc.). + +This function is called when some file(s) on disk have been requested +to be removed by the client. ``storage`` indicates which torrent is +referred to. See session_handle for ``remove_flags_t`` flags +indicating which files are to be removed. +e.g. session_handle::delete_files - delete all files +session_handle::delete_partfile - only delete part file. + +This is called to set the priority of some or all files. Changing the +priority from or to 0 may involve moving data to and from the +partfile. The disk I/O object is responsible for correctly +synchronizing this work to not race with any potentially outstanding +asynchronous operations affecting these files. + +``prio`` is a vector of the file priority for all files. If it's +shorter than the total number of files in the torrent, they are +assumed to be set to the default priority. + +This is called when a piece fails the hash check, to ensure there are +no outstanding disk operations to the piece before blocks are +re-requested from peers to overwrite the existing blocks. The disk I/O +object does not need to perform any action other than synchronize +with all outstanding disk operations to the specified piece before +posting the result back. + +update_stats_counters() is called to give the disk storage an +opportunity to update gauges in the ``c`` stats counters, that aren't +updated continuously as operations are performed. This is called +before a snapshot of the counters are passed to the client. + +Return a list of all the files that are currently open for the +specified storage/torrent. This is is just used for the client to +query the currently open files, and which modes those files are open +in. + +this is called when the session is starting to shut down. The disk +I/O object is expected to flush any outstanding write jobs, cancel +hash jobs and initiate tearing down of any internal threads. If +``wait`` is true, this should be asynchronous. i.e. this call should +not return until all threads have stopped and all jobs have either +been aborted or completed and the disk I/O object is ready to be +destructed. + +This will be called after a batch of disk jobs has been issues (via +the ``async_*`` ). It gives the disk I/O object an opportunity to +notify any potential condition variables to wake up the disk +thread(s). The ``async_*`` calls can of course also notify condition +variables, but doing it in this call allows for batching jobs, by +issuing the notification once for a collection of jobs. + +This is called to notify the disk I/O object that the settings have +been updated. In the disk io constructor, a settings_interface +reference is passed in. Whenever these settings are updated, this +function is called to allow the disk I/O object to react to any +changed settings relevant to its operations. + +The disk_interface is the customization point for disk I/O in libtorrent. +implement this interface and provide a factory function to the session constructor +use custom disk I/O. All functions on the disk subsystem (implementing +disk_interface) are called from within libtorrent's network thread. For +disk I/O to be performed in a separate thread, the disk subsystem has to +manage that itself. + +Although the functions are called ``async_*``, they do not technically +*have* to be asynchronous, but they support being asynchronous, by +expecting the result passed back into a callback. The callbacks must be +posted back onto the network thread via the io_context object passed into +the constructor. The callbacks will be run in the network thread. + + + + + + + + +a unique, owning, reference to the storage of a torrent in a disk io +subsystem (class that implements disk_interface). This is held by the +internal libtorrent torrent object to tie the storage object allocated +for a torrent to the lifetime of the internal torrent object. When a +torrent is removed from the session, this holder is destructed and will +inform the disk object. + + + + + + + + + + + + + + + + + + + + + + + + +the peer_connection_handle class provides a handle to the internal peer +connection object, to be used by plugins. This is a low level interface that +may not be stable across libtorrent versions + + + + + + +The bt_peer_connection_handle provides a handle to the internal bittorrent +peer connection object to plugins. It's low level and may not be a stable API +across libtorrent versions. + +This block has not been downloaded or requested form any peer. + +The block has been requested, but not completely downloaded yet. + +The block has been downloaded and is currently queued for being +written to disk. + +The block has been written to disk. + +this is the enum used for the block_info::state field. + +The peer is the ip address of the peer this block was downloaded from. + +the number of bytes that have been received for this block + +the total number of bytes in this block. + +the state this block is in (see block_state_t) + +the number of peers that is currently requesting this block. Typically +this is 0 or 1, but at the end of the torrent blocks may be requested +by more peers in parallel to speed things up. + +holds the state of a block in a piece. Who we requested +it from and how far along we are at downloading it. + +the index of the piece in question. ``blocks_in_piece`` is the number +of blocks in this particular piece. This number will be the same for +most pieces, but +the last piece may have fewer blocks than the standard pieces. + +the number of blocks in this piece + +the number of blocks that are in the finished state + +the number of blocks that are in the writing state + +the number of blocks that are in the requested state + +this is an array of ``blocks_in_piece`` number of +items. One for each block in the piece. + + that's owned by the session object. The next time + get_download_queue() is called, it will be invalidated. + In the case of piece_info_alert, these pointers point into the alert + object itself, and will be invalidated when the alert destruct. + +This class holds information about pieces that have outstanding requests +or outstanding writes + +for std::hash (and to support using this type in unordered_map etc.) + + +constructs a torrent handle that does not refer to a torrent. +i.e. is_valid() will return false. + +instruct libtorrent to overwrite any data that may already have been +downloaded with the data of the new piece being added. Using this +flag when adding a piece that is actively being downloaded from other +peers may have some unexpected consequences, as blocks currently +being downloaded from peers may not be replaced. + +This function will write ``data`` to the storage as piece ``piece``, +as if it had been downloaded from a peer. + +By default, data that's already been downloaded is not overwritten by +this buffer. If you trust this data to be correct (and pass the piece +hash check) you may pass the overwrite_existing flag. This will +instruct libtorrent to overwrite any data that may already have been +downloaded with this data. + +Since the data is written asynchronously, you may know that is passed +or failed the hash check by waiting for piece_finished_alert or +hash_failed_alert. + +Adding pieces while the torrent is being checked (i.e. in +torrent_status::checking_files state) is not supported. + +The overload taking a raw pointer to the data is a blocking call. It +won't return until the libtorrent thread has copied the data into its +disk write buffer. ``data`` is expected to point to a buffer of as +many bytes as the size of the specified piece. See +file_storage::piece_size(). + +The data in the buffer is copied and passed on to the disk IO thread +to be written at a later point. + +The overload taking a ``std::vector`` is not blocking, it will +send the buffer to the main thread and return immediately. + +This function starts an asynchronous read operation of the specified +piece from this torrent. You must have completed the download of the +specified piece before calling this function. + +When the read operation is completed, it is passed back through an +alert, read_piece_alert. Since this alert is a response to an explicit +call, it will always be posted, regardless of the alert mask. + +Note that if you read multiple pieces, the read operations are not +guaranteed to finish in the same order as you initiated them. + +Returns true if this piece has been completely downloaded and written +to disk, and false otherwise. + +Query information about connected peers for this torrent. If the +torrent_handle is invalid, it will throw a system_error exception. + +``post_peer_info()`` is asynchronous and will trigger the posting of +a peer_info_alert. The alert contain a list of peer_info objects, one +for each connected peer. + +``get_peer_info()`` is synchronous and takes a reference to a vector +that will be cleared and filled with one entry for each peer +connected to this torrent, given the handle is valid. Each entry in +the vector contains information about that particular peer. See +peer_info. + +calculates ``distributed_copies``, ``distributed_full_copies`` and +``distributed_fraction``. + +includes partial downloaded blocks in ``total_done`` and +``total_wanted_done``. + +includes ``last_seen_complete``. + +populate the ``pieces`` field in torrent_status. + +includes ``verified_pieces`` (only applies to torrents in *seed +mode*). + +includes ``torrent_file``, which is all the static information from +the .torrent file. + +includes ``name``, the name of the torrent. This is either derived +from the .torrent file, or from the ``&dn=`` magnet link argument +or possibly some other source. If the name of the torrent is not +known, this is an empty string. + +includes ``save_path``, the path to the directory the files of the +torrent are saved to. + +``status()`` will return a structure with information about the status +of this torrent. If the torrent_handle is invalid, it will throw +system_error exception. See torrent_status. The ``flags`` +argument filters what information is returned in the torrent_status. +Some information in there is relatively expensive to calculate, and if +you're not interested in it (and see performance issues), you can +filter them out. + +The ``status()`` function will block until the internal libtorrent +thread responds with the torrent_status object. To avoid blocking, +instead call ``post_status()``. It will trigger posting of a +state_update_alert with a single torrent_status object for this +torrent. + +In order to get regular updates for torrents whose status changes, +consider calling session::post_torrent_updates()`` instead. + +By default everything is included. The flags you can use to decide +what to *include* are defined in this class. + +``post_download_queue()`` triggers a download_queue_alert to be +posted. +``get_download_queue()`` is a synchronous call and returns a vector +with information about pieces that are partially downloaded or not +downloaded but partially requested. See partial_piece_info for the +fields in the returned vector. + +used to ask libtorrent to send an alert once the piece has been +downloaded, by passing alert_when_available. When set, the +read_piece_alert alert will be delivered, with the piece data, when +it's downloaded. + +This function sets or resets the deadline associated with a specific +piece index (``index``). libtorrent will attempt to download this +entire piece before the deadline expires. This is not necessarily +possible, but pieces with a more recent deadline will always be +prioritized over pieces with a deadline further ahead in time. The +deadline (and flags) of a piece can be changed by calling this +function again. + +If the piece is already downloaded when this call is made, nothing +happens, unless the alert_when_available flag is set, in which case it +will have the same effect as calling read_piece() for ``index``. + +``deadline`` is the number of milliseconds until this piece should be +completed. + +``reset_piece_deadline`` removes the deadline from the piece. If it +hasn't already been downloaded, it will no longer be considered a +priority. + +``clear_piece_deadlines()`` removes deadlines on all pieces in +the torrent. As if reset_piece_deadline() was called on all pieces. + +only calculate file progress at piece granularity. This makes +the file_progress() call cheaper and also only takes bytes that +have passed the hash check into account, so progress cannot +regress in this mode. + +This function fills in the supplied vector, or returns a vector, with +the number of bytes downloaded of each file in this torrent. The +progress values are ordered the same as the files in the +torrent_info. + +This operation is not very cheap. Its complexity is *O(n + mj)*. +Where *n* is the number of files, *m* is the number of currently +downloading pieces and *j* is the number of blocks in a piece. + +The ``flags`` parameter can be used to specify the granularity of the +file progress. If left at the default value of 0, the progress will be +as accurate as possible, but also more expensive to calculate. If +``torrent_handle::piece_granularity`` is specified, the progress will +be specified in piece granularity. i.e. only pieces that have been +fully downloaded and passed the hash check count. When specifying +piece granularity, the operation is a lot cheaper, since libtorrent +already keeps track of this internally and no calculation is required. + +This function returns a vector with status about files +that are open for this torrent. Any file that is not open +will not be reported in the vector, i.e. it's possible that +the vector is empty when returning, if none of the files in the +torrent are currently open. + +See open_file_state + +If the torrent is in an error state (i.e. ``torrent_status::error`` is +non-empty), this will clear the error and start the torrent again. + +``trackers()`` returns the list of trackers for this torrent. The +announce entry contains both a string ``url`` which specify the +announce url for the tracker as well as an int ``tier``, which is +specifies the order in which this tracker is tried. If you want +libtorrent to use another list of trackers for this torrent, you can +use ``replace_trackers()`` which takes a list of the same form as the +one returned from ``trackers()`` and will replace it. If you want an +immediate effect, you have to call force_reannounce(). See +announce_entry. + +``post_trackers()`` is the asynchronous version of ``trackers()``. It +will trigger a tracker_list_alert to be posted. + +``add_tracker()`` will look if the specified tracker is already in the +set. If it is, it doesn't do anything. If it's not in the current set +of trackers, it will insert it in the tier specified in the +announce_entry. + +The updated set of trackers will be saved in the resume data, and when +a torrent is started with resume data, the trackers from the resume +data will replace the original ones. + +``add_url_seed()`` adds another url to the torrent's list of url +seeds. If the given url already exists in that list, the call has no +effect. The torrent will connect to the server and try to download +pieces from it, unless it's paused, queued, checking or seeding. +``remove_url_seed()`` removes the given url if it exists already. +``url_seeds()`` return a set of the url seeds currently in this +torrent. Note that URLs that fails may be removed automatically from +the list. + +See http-seeding_ for more information. + +These functions are identical as the ``*_url_seed()`` variants, but +they operate on `BEP 17`_ web seeds instead of `BEP 19`_. + +See http-seeding_ for more information. + +add the specified extension to this torrent. The ``ext`` argument is +a function that will be called from within libtorrent's context +passing in the internal torrent object and the specified userdata +pointer. The function is expected to return a shared pointer to +a torrent_plugin instance. + +``set_metadata`` expects the *info* section of metadata. i.e. The +buffer passed in will be hashed and verified against the info-hash. If +it fails, a ``metadata_failed_alert`` will be generated. If it passes, +a ``metadata_received_alert`` is generated. The function returns true +if the metadata is successfully set on the torrent, and false +otherwise. If the torrent already has metadata, this function will not +affect the torrent, and false will be returned. + +Returns true if this handle refers to a valid torrent and false if it +hasn't been initialized or if the torrent it refers to has been +removed from the session AND destructed. + +To tell if the torrent_handle is in the session, use +torrent_handle::in_session(). This will return true before +session_handle::remove_torrent() is called, and false +afterward. + +Clients should only use is_valid() to determine if the result of +session::find_torrent() was successful. + +Unlike other member functions which return a value, is_valid() +completes immediately, without blocking on a result from the +network thread. Also unlike other functions, it never throws +the system_error exception. + +will delay the disconnect of peers that we're still downloading +outstanding requests from. The torrent will not accept any more +requests and will disconnect all idle peers. As soon as a peer is done +transferring the blocks that were requested from it, it is +disconnected. This is a graceful shut down of the torrent in the sense +that no downloaded bytes are wasted. + +``pause()``, and ``resume()`` will disconnect all peers and reconnect +all peers respectively. When a torrent is paused, it will however +remember all share ratios to all peers and remember all potential (not +connected) peers. Torrents may be paused automatically if there is a +file error (e.g. disk full) or something similar. See +file_error_alert. + +For possible values of the ``flags`` parameter, see pause_flags_t. + +To know if a torrent is paused or not, call +``torrent_handle::flags()`` and check for the +``torrent_status::paused`` flag. + + Torrents that are auto-managed may be automatically resumed again. It + does not make sense to pause an auto-managed torrent without making it + not auto-managed first. Torrents are auto-managed by default when added + to the session. For more information, see queuing_. + + +sets and gets the torrent state flags. See torrent_flags_t. +The ``set_flags`` overload that take a mask will affect all +flags part of the mask, and set their values to what the +``flags`` argument is set to. This allows clearing and +setting flags in a single function call. +The ``set_flags`` overload that just takes flags, sets all +the specified flags and leave any other flags unchanged. +``unset_flags`` clears the specified flags, while leaving +any other flags unchanged. + +The `seed_mode` flag is special, it can only be cleared once the +torrent has been added, and it can only be set as part of the +add_torrent_params flags, when adding the torrent. + +Instructs libtorrent to flush all the disk caches for this torrent and +close all file handles. This is done asynchronously and you will be +notified that it's complete through cache_flushed_alert. + +Note that by the time you get the alert, libtorrent may have cached +more data for the torrent, but you are guaranteed that whatever cached +data libtorrent had by the time you called +``torrent_handle::flush_cache()`` has been written to disk. + +``force_recheck`` puts the torrent back in a state where it assumes to +have no resume data. All peers will be disconnected and the torrent +will stop announcing to the tracker. The torrent will be added to the +checking queue, and will be checked (all the files will be read and +compared to the piece hashes). Once the check is complete, the torrent +will start connecting to peers again, as normal. +The torrent will be placed last in queue, i.e. its queue position +will be the highest of all torrents in the session. + +the disk cache will be flushed before creating the resume data. +This avoids a problem with file timestamps in the resume data in +case the cache hasn't been flushed yet. + +the resume data will contain the metadata from the torrent file as +well. This is useful for clients that don't keep .torrent files +around separately, or for torrents that were added via a magnet link. + +this flag has the same behavior as the combination of: +if_counters_changed | if_download_progress | if_config_changed | +if_state_changed | if_metadata_changed + +save resume data if any counters has changed since the last time +resume data was saved. This includes upload/download counters, active +time counters and scrape data. A torrent that is not paused will have +its active time counters incremented continuously. + +save the resume data if any blocks have been downloaded since the +last time resume data was saved. This includes: +* checking existing files on disk +* downloading a block from a peer + +save the resume data if configuration options changed since last time +the resume data was saved. This includes: +* file- or piece priorities +* upload- and download rate limits +* change max-uploads (unchoke slots) +* change max connection limit +* enable/disable peer-exchange, local service discovery or DHT +* enable/disable apply IP-filter +* enable/disable auto-managed +* enable/disable share-mode +* enable/disable sequential-mode +* files renamed +* storage moved (save_path changed) + +save the resume data if torrent state has changed since last time the +resume data was saved. This includes: +* upload mode +* paused state +* super-seeding +* seed-mode + +save the resume data if any *metadata* changed since the last time +resume data was saved. This includes: +* add/remove web seeds +* add/remove trackers +* receiving metadata for a magnet link + +``save_resume_data()`` asks libtorrent to generate fast-resume data for +this torrent. The fast resume data (stored in an add_torrent_params +object) can be used to resume a torrent in the next session without +having to check all files for which pieces have been downloaded. It +can also be used to save a .torrent file for a torrent_handle. + +This operation is asynchronous, ``save_resume_data`` will return +immediately. The resume data is delivered when it's done through a +save_resume_data_alert. + +The operation will fail, and post a save_resume_data_failed_alert +instead, in the following cases: + + 1. The torrent is in the process of being removed. + 2. No torrent state has changed since the last saving of resume + data, and the only_if_modified flag is set. + metadata (see libtorrent's metadata-from-peers_ extension) + +Note that some counters may be outdated by the time you receive the fast resume data + +When saving resume data because of shutting down, make sure not to +remove_torrent() before you receive the save_resume_data_alert. +There's no need to pause the session or torrent when saving resume +data. + +The paused state of a torrent is saved in the resume data, so pausing +all torrents before saving resume data will all torrents be restored +in a paused state. + + It is typically a good idea to save resume data whenever a torrent + is completed or paused. If you save resume data for torrents when they are + paused, you can accelerate the shutdown process by not saving resume + data again for those torrents. Completed torrents should have their + resume data saved when they complete and on exit, since their + statistics might be updated. + +Example code to pause and save resume data for all torrents and wait +for the alerts: + + Note how ``outstanding_resume_data`` is a global counter in this + example. This is deliberate, otherwise there is a race condition for + torrents that was just asked to save their resume data, they posted + the alert, but it has not been received yet. Those torrents would + report that they don't need to save resume data again, and skipped by + the initial loop, and thwart the counter otherwise. + +This function returns true if anything that is stored in the resume +data has changed since the last time resume data was saved. +The overload that takes ``flags`` let you ask if specific categories +of properties have changed. These flags have the same behavior as in +the save_resume_data() call. + +This is a *blocking* call. It will wait for a response from +libtorrent's main thread. A way to avoid blocking is to instead +call save_resume_data() directly, specifying the conditions under +which resume data should be saved. + + A torrent's resume data is considered saved as soon as the + save_resume_data_alert is posted. It is important to make sure this + alert is received and handled in order for this function to be + meaningful. + +Every torrent that is added is assigned a queue position exactly one +greater than the greatest queue position of all existing torrents. +Torrents that are being seeded have -1 as their queue position, since +they're no longer in line to be downloaded. + +When a torrent is removed or turns into a seed, all torrents with +greater queue positions have their positions decreased to fill in the +space in the sequence. + +``queue_position()`` returns the torrent's position in the download +queue. The torrents with the smallest numbers are the ones that are +being downloaded. The smaller number, the closer the torrent is to the +front of the line to be started. + +The queue position is also available in the torrent_status. + +The ``queue_position_*()`` functions adjust the torrents position in +the queue. Up means closer to the front and down means closer to the +back of the queue. Top and bottom refers to the front and the back of +the queue respectively. + +updates the position in the queue for this torrent. The relative order +of all other torrents remain intact but their numerical queue position +shifts to make space for this torrent's new position + +For SSL torrents, use this to specify a path to a .pem file to use as +this client's certificate. The certificate must be signed by the +certificate in the .torrent file to be valid. + +The set_ssl_certificate_buffer() overload takes the actual certificate, +private key and DH params as strings, rather than paths to files. + +``cert`` is a path to the (signed) certificate in .pem format +corresponding to this torrent. + +``private_key`` is a path to the private key for the specified +certificate. This must be in .pem format. + +``dh_params`` is a path to the Diffie-Hellman parameter file, which +needs to be in .pem format. You can generate this file using the +openssl command like this: ``openssl dhparam -outform PEM -out +dhparams.pem 512``. + +``passphrase`` may be specified if the private key is encrypted and +requires a passphrase to be decrypted. + +Note that when a torrent first starts up, and it needs a certificate, +it will suspend connecting to any peers until it has one. It's +typically desirable to resume the torrent after setting the SSL +certificate. + +If you receive a torrent_need_cert_alert, you need to call this to +provide a valid cert. If you don't have a cert you won't be allowed to +connect to any peers. + +torrent_file() returns a pointer to the torrent_info object +associated with this torrent. The torrent_info object may be a copy +of the internal object. If the torrent doesn't have metadata, the +pointer will not be initialized (i.e. a nullptr). The torrent may be +in a state without metadata only if it was started without a .torrent +file, e.g. by being added by magnet link. + +Note that the torrent_info object returned here may be a different +instance than the one added to the session, with different attributes +like piece layers, dht nodes and trackers. A torrent_info object does +not round-trip cleanly when added to a session. + +If you want to save a .torrent file from the torrent_handle, instead +call save_resume_data() and write_torrent_file() the +add_torrent_params object passed back in the alert. + +torrent_file_with_hashes() returns a *copy* of the internal +torrent_info and piece layer hashes (if it's a v2 torrent). The piece +layers will only be included if they are available. If this torrent +was added from a .torrent file with piece layers or if it's seeding, +the piece layers are available. This function is more expensive than +torrent_file() since it needs to make copies of this information. + +The torrent_file_with_hashes() is here for backwards compatibility +when constructing a create_torrent object from a torrent_info that's +in a session. Prefer save_resume_data() + write_torrent_file(). + +Note that a torrent added from a magnet link may not have the full +merkle trees for all files, and hence not have the complete piece +layers. In that state, you cannot create a .torrent file even from +the torrent_info returned from torrent_file_with_hashes(). Once the +torrent completes downloading all files, becoming a seed, you can +make a .torrent file from it. + +returns the piece layers for all files in the torrent. If this is a +v1 torrent (and doesn't have any piece layers) it returns an empty +vector. This is a blocking call that will synchronize with the +libtorrent network thread. + +The piece availability is the number of peers that we are connected +that has advertised having a particular piece. This is the information +that libtorrent uses in order to prefer picking rare pieces. + +``post_piece_availability()`` will trigger a piece_availability_alert +to be posted. + +``piece_availability()`` fills the specified ``std::vector`` +with the availability for each piece in this torrent. libtorrent does +not keep track of availability for seeds, so if the torrent is +seeding the availability for all pieces is reported as 0. + +These functions are used to set and get the priority of individual +pieces. By default all pieces have priority 4. That means that the +random rarest first algorithm is effectively active for all pieces. +You may however change the priority of individual pieces. There are 8 +priority levels. 0 means not to download the piece at all. Otherwise, +lower priority values means less likely to be picked. Piece priority +takes precedence over piece availability. Every piece with priority 7 +will be attempted to be picked before a priority 6 piece and so on. + +The default priority of pieces is 4. + +Piece priorities can not be changed for torrents that have not +downloaded the metadata yet. Magnet links won't have metadata +immediately. see the metadata_received_alert. + +``piece_priority`` sets or gets the priority for an individual piece, +specified by ``index``. + +``prioritize_pieces`` takes a vector of integers, one integer per +piece in the torrent. All the piece priorities will be updated with +the priorities in the vector. +The second overload of ``prioritize_pieces`` that takes a vector of pairs +will update the priorities of only select pieces, and leave all other +unaffected. Each pair is (piece, priority). That is, the first item is +the piece index and the second item is the priority of that piece. +Invalid entries, where the piece index or priority is out of range, are +not allowed. + +``get_piece_priorities`` returns a vector with one element for each piece +in the torrent. Each element is the current priority of that piece. + +It's possible to cancel the effect of *file* priorities by setting the +priorities for the affected pieces. Care has to be taken when mixing +usage of file- and piece priorities. + +``index`` must be in the range [0, number_of_files). + +``file_priority()`` queries or sets the priority of file ``index``. + +``prioritize_files()`` takes a vector that has at as many elements as +there are files in the torrent. Each entry is the priority of that +file. The function sets the priorities of all the pieces in the +torrent based on the vector. + +``get_file_priorities()`` returns a vector with the priorities of all +files. + +The priority values are the same as for piece_priority(). See +download_priority_t. + +Whenever a file priority is changed, all other piece priorities are +reset to match the file priorities. In order to maintain special +priorities for particular pieces, piece_priority() has to be called +again for those pieces. + +You cannot set the file priorities on a torrent that does not yet have +metadata or a torrent that is a seed. ``file_priority(int, int)`` and +prioritize_files() are both no-ops for such torrents. + +Since changing file priorities may involve disk operations (of moving +files in- and out of the part file), the internal accounting of file +priorities happen asynchronously. i.e. setting file priorities and then +immediately querying them may not yield the same priorities just set. +To synchronize with the priorities taking effect, wait for the +file_prio_alert. + +When combining file- and piece priorities, the resume file will record +both. When loading the resume data, the file priorities will be applied +first, then the piece priorities. + +Moving data from a file into the part file is currently not +supported. If a file has its priority set to 0 *after* it has already +been created, it will not be moved into the partfile. + +by default, force-reannounce will still honor the min-interval +published by the tracker. If this flag is set, it will be ignored +and the tracker is announced immediately. + +``force_reannounce()`` will force this torrent to do another tracker +request, to receive new peers. The ``seconds`` argument specifies how +many seconds from now to issue the tracker announces. + +If the tracker's ``min_interval`` has not passed since the last +announce, the forced announce will be scheduled to happen immediately +as the ``min_interval`` expires. This is to honor trackers minimum +re-announce interval settings. + +The ``tracker_index`` argument specifies which tracker to re-announce. +If set to -1 (which is the default), all trackers are re-announce. + +The ``flags`` argument can be used to affect the re-announce. See +ignore_min_interval. + +``force_dht_announce`` will announce the torrent to the DHT +immediately. + +``force_lsd_announce`` will announce the torrent on LSD +immediately. + +``scrape_tracker()`` will send a scrape request to a tracker. By +default (``idx`` = -1) it will scrape the last working tracker. If +``idx`` is >= 0, the tracker with the specified index will scraped. + +A scrape request queries the tracker for statistics such as total +number of incomplete peers, complete peers, number of downloads etc. + +This request will specifically update the ``num_complete`` and +``num_incomplete`` fields in the torrent_status struct once it +completes. When it completes, it will generate a scrape_reply_alert. +If it fails, it will generate a scrape_failed_alert. + +``set_upload_limit`` will limit the upload bandwidth used by this +particular torrent to the limit you set. It is given as the number of +bytes per second the torrent is allowed to upload. +``set_download_limit`` works the same way but for download bandwidth +instead of upload bandwidth. Note that setting a higher limit on a +torrent then the global limit +(``settings_pack::upload_rate_limit``) will not override the global +rate limit. The torrent can never upload more than the global rate +limit. + +``upload_limit`` and ``download_limit`` will return the current limit +setting, for upload and download, respectively. + +Local peers are not rate limited by default. see peer-classes_. + +``connect_peer()`` is a way to manually connect to peers that one +believe is a part of the torrent. If the peer does not respond, or is +not a member of this torrent, it will simply be disconnected. No harm +can be done by using this other than an unnecessary connection attempt +is made. If the torrent is uninitialized or in queued or checking +mode, this will throw system_error. The second (optional) +argument will be bitwise ORed into the source mask of this peer. +Typically this is one of the source flags in peer_info. i.e. +``tracker``, ``pex``, ``dht`` etc. + +For possible values of ``flags``, see pex_flags_t. + +This will disconnect all peers and clear the peer list for this +torrent. New peers will have to be acquired before resuming, from +trackers, DHT or local service discovery, for example. + +``set_max_uploads()`` sets the maximum number of peers that's unchoked +at the same time on this torrent. If you set this to -1, there will be +no limit. This defaults to infinite. The primary setting controlling +this is the global unchoke slots limit, set by unchoke_slots_limit in +settings_pack. + +``max_uploads()`` returns the current settings. + +``set_max_connections()`` sets the maximum number of connection this +torrent will open. If all connections are used up, incoming +connections may be refused or poor connections may be closed. This +must be at least 2. The default is unlimited number of connections. If +-1 is given to the function, it means unlimited. There is also a +global limit of the number of connections, set by +``connections_limit`` in settings_pack. + +``max_connections()`` returns the current settings. + +Moves the file(s) that this torrent are currently seeding from or +downloading to. If the given ``save_path`` is not located on the same +drive as the original save path, the files will be copied to the new +drive and removed from their original location. This will block all +other disk IO, and other torrents download and upload rates may drop +while copying the file. + +Since disk IO is performed in a separate thread, this operation is +also asynchronous. Once the operation completes, the +``storage_moved_alert`` is generated, with the new path as the +message. If the move fails for some reason, +``storage_moved_failed_alert`` is generated instead, containing the +error message. + +The ``flags`` argument determines the behavior of the copying/moving +of the files in the torrent. see move_flags_t. + +``always_replace_files`` is the default and replaces any file that +exist in both the source directory and the target directory. + +``fail_if_exist`` first check to see that none of the copy operations +would cause an overwrite. If it would, it will fail. Otherwise it will +proceed as if it was in ``always_replace_files`` mode. Note that there +is an inherent race condition here. If the files in the target +directory appear after the check but before the copy or move +completes, they will be overwritten. When failing because of files +already existing in the target path, the ``error`` of +``move_storage_failed_alert`` is set to +``boost::system::errc::file_exists``. + +The intention is that a client may use this as a probe, and if it +fails, ask the user which mode to use. The client may then re-issue +the ``move_storage`` call with one of the other modes. + +``dont_replace`` always keeps the existing file in the target +directory, if there is one. The source files will still be removed in +that case. Note that it won't automatically re-check files. If an +incomplete torrent is moved into a directory with the complete files, +pause, move, force-recheck and resume. Without the re-checking, the +torrent will keep downloading and files in the new download directory +will be overwritten. + +Files that have been renamed to have absolute paths are not moved by +this function. Keep in mind that files that don't belong to the +torrent but are stored in the torrent's directory may be moved as +well. This goes for files that have been renamed to absolute paths +that still end up inside the save path. + +When copying files, sparse regions are not likely to be preserved. +This makes it proportionally more expensive to move a large torrent +when only few pieces have been downloaded, since the files are then +allocated with zeros in the destination directory. + +Renames the file with the given index asynchronously. The rename +operation is complete when either a file_renamed_alert or +file_rename_failed_alert is posted. + +returns the info-hash(es) of the torrent. If this handle is to a +torrent that hasn't loaded yet (for instance by being added) by a +URL, the returned value is undefined. +The ``info_hash()`` returns the SHA-1 info-hash for v1 torrents and a +truncated hash for v2 torrents. For the full v2 info-hash, use +``info_hashes()`` instead. + +comparison operators. The order of the torrents is unspecified +but stable. + +returns a unique identifier for this torrent. It's not a dense index. +It's not preserved across sessions. + +This function is intended only for use by plugins and the alert +dispatch function. This type does not have a stable ABI and should +be relied on as little as possible. Accessing the handle returned by +this function is not thread safe outside of libtorrent's internal +thread (which is used to invoke plugin callbacks). +The ``torrent`` class is not only eligible for changing ABI across +minor versions of libtorrent, its layout is also dependent on build +configuration. This adds additional requirements on a client to be +built with the exact same build configuration as libtorrent itself. +i.e. the ``TORRENT_`` macros must match between libtorrent and the +client builds. + +returns the userdata pointer as set in add_torrent_params + +Returns true if the torrent is in the session. It returns true before +session::remove_torrent() is called, and false afterward. + +Note that this is a blocking function, unlike torrent_handle::is_valid() +which returns immediately. + +You will usually have to store your torrent handles somewhere, since it's +the object through which you retrieve information about the torrent and +aborts the torrent. + + Any member function that returns a value or fills in a value has to be + made synchronously. This means it has to wait for the main thread to + complete the query before it can return. This might potentially be + expensive if done from within a GUI thread that needs to stay + responsive. Try to avoid querying for information you don't need, and + try to do it in as few calls as possible. You can get most of the + interesting information about a torrent from the + torrent_handle::status() call. + +The default constructor will initialize the handle to an invalid state. +Which means you cannot perform any operation on it, unless you first +assign it a valid handle. If you try to perform any operation on an +uninitialized handle, it will throw ``invalid_handle``. + + All operations on a torrent_handle may throw system_error + exception, in case the handle is no longer referring to a torrent. + There is one exception is_valid() will never throw. Since the torrents + are processed by a background thread, there is no guarantee that a + handle will remain valid between two calls. + + +filled in by the constructor and should be left untouched. It is used +for forward binary compatibility. + +torrent_info object with the torrent to add. Unless the +info_hash is set, this is required to be initialized. + +If the torrent doesn't have a tracker, but relies on the DHT to find +peers, the ``trackers`` can specify tracker URLs for the torrent. + +the tiers the URLs in ``trackers`` belong to. Trackers belonging to +different tiers may be treated differently, as defined by the multi +tracker extension. This is optional, if not specified trackers are +assumed to be part of tier 0, or whichever the last tier was as +iterating over the trackers. + +a list of hostname and port pairs, representing DHT nodes to be added +to the session (if DHT is enabled). The hostname may be an IP address. + +in case there's no other name in this torrent, this name will be used. +The name out of the torrent_info object takes precedence if available. + +the path where the torrent is or will be stored. + + On windows this path (and other paths) are interpreted as UNC + paths. This means they must use backslashes as directory separators + and may not contain the special directories "." or "..". + +Setting this to an absolute path performs slightly better than a +relative path. + +One of the values from storage_mode_t. For more information, see +storage-allocation_. + +The ``userdata`` parameter is optional and will be passed on to the +extension constructor functions, if any +(see torrent_handle::add_extension()). It will also be stored in the +torrent object and can be retrieved by calling userdata(). + +can be set to control the initial file priorities when adding a +torrent. The semantics are the same as for +``torrent_handle::prioritize_files()``. The file priorities specified +in here take precedence over those specified in the resume data, if +any. +If this vector of file priorities is shorter than the number of files +in the torrent, the remaining files (not covered by this) will still +have the default download priority. This default can be changed by +setting the default_dont_download torrent_flag. + +the default tracker id to be used when announcing to trackers. By +default this is empty, and no tracker ID is used, since this is an +optional argument. If a tracker returns a tracker ID, that ID is used +instead of this. + +flags controlling aspects of this torrent and how it's added. See +torrent_flags_t for details. + + The ``flags`` field is initialized with default flags by the + constructor. In order to preserve default behavior when clearing or + setting other flags, make sure to bitwise OR or in a flag or bitwise + AND the inverse of a flag to clear it. + +set this to the info hash of the torrent to add in case the info-hash +is the only known property of the torrent. i.e. you don't have a +.torrent file nor a magnet link. +To add a magnet link, use parse_magnet_uri() to populate fields in the +add_torrent_params object. + +``max_uploads``, ``max_connections``, ``upload_limit``, +``download_limit`` correspond to the ``set_max_uploads()``, +``set_max_connections()``, ``set_upload_limit()`` and +``set_download_limit()`` functions on torrent_handle. These values let +you initialize these settings when the torrent is added, instead of +calling these functions immediately following adding it. + +-1 means unlimited on these settings just like their counterpart +functions on torrent_handle + +For fine grained control over rate limits, including making them apply +to local peers, see peer-classes_. + +the upload and download rate limits for this torrent, specified in +bytes per second. -1 means unlimited. + +the total number of bytes uploaded and downloaded by this torrent so +far. + +the number of seconds this torrent has spent in started, finished and +seeding state so far, respectively. + +if set to a non-zero value, this is the posix time of when this torrent +was first added, including previous runs/sessions. If set to zero, the +internal added_time will be set to the time of when add_torrent() is +called. + +if set to non-zero, initializes the time (expressed in posix time) when +we last saw a seed or peers that together formed a complete copy of the +torrent. If left set to zero, the internal counterpart to this field +will be updated when we see a seed or a distributed copies >= 1.0. + +these field can be used to initialize the torrent's cached scrape data. +The scrape data is high level metadata about the current state of the +swarm, as returned by the tracker (either when announcing to it or by +sending a specific scrape request). ``num_complete`` is the number of +peers in the swarm that are seeds, or have every piece in the torrent. +``num_incomplete`` is the number of peers in the swarm that do not have +every piece. ``num_downloaded`` is the number of times the torrent has +been downloaded (not initiated, but the number of times a download has +completed). + +Leaving any of these values set to -1 indicates we don't know, or we +have not received any scrape data. + +URLs can be added to these two lists to specify additional web +seeds to be used by the torrent. If the ``flag_override_web_seeds`` +is set, these will be the _only_ ones to be used. i.e. any web seeds +found in the .torrent file will be overridden. + +http_seeds expects URLs to web servers implementing the original HTTP +seed specification `BEP 17`_. + +url_seeds expects URLs to regular web servers, aka "get right" style, +specified in `BEP 19`_. + +peers to add to the torrent, to be tried to be connected to as +bittorrent peers. + +peers banned from this torrent. The will not be connected to + +this is a map of partially downloaded piece. The key is the piece index +and the value is a bitfield where each bit represents a 16 kiB block. +A set bit means we have that block. + +this is a bitfield indicating which pieces we already have of this +torrent. + +when in seed_mode, pieces with a set bit in this bitfield have been +verified to be valid. Other pieces will be verified the first time a +peer requests it. + +this sets the priorities for each individual piece in the torrent. Each +element in the vector represent the piece with the same index. If you +set both file- and piece priorities, file priorities will take +precedence. + +v2 hashes, if known + +if set, indicates which hashes are included in the corresponding +vector of ``merkle_trees``. These bitmasks always cover the full +tree, a cleared bit means the hash is all zeros (i.e. not set) and +set bit means the next hash in the corresponding vector in +``merkle_trees`` is the hash for that node. This is an optimization +to avoid storing a lot of zeros. + +bit-fields indicating which v2 leaf hashes have been verified +against the root hash. If this vector is empty and merkle_trees is +non-empty it implies that all hashes in merkle_trees are verified. + +this is a map of file indices in the torrent and new filenames to be +applied before the torrent is added. + +the posix time of the last time payload was received or sent for this +torrent, respectively. A value of 0 means we don't know when we last +uploaded or downloaded, or we have never uploaded or downloaded any +payload for this torrent. + +The add_torrent_params contains all the information in a .torrent file +along with all information necessary to add that torrent to a session. +The key fields when adding a torrent are: + +* ti - the immutable info-dict part of the torrent +* info_hash - when you don't have the metadata (.torrent file). This + uniquely identifies the torrent and can validate the info-dict when + received from the swarm. + +In order to add a torrent to a session, one of those fields must be set +in addition to ``save_path``. The add_torrent_params object can then be +passed into one of the ``session::add_torrent()`` overloads or +``session::async_add_torrent()``. + +If you only specify the info-hash, the torrent file will be downloaded +from peers, which requires them to support the metadata extension. For +the metadata extension to work, libtorrent must be built with extensions +enabled (``TORRENT_DISABLE_EXTENSIONS`` must not be defined). It also +takes an optional ``name`` argument. This may be left empty in case no +name should be assigned to the torrent. In case it's not, the name is +used for the torrent as long as it doesn't have metadata. See +``torrent_handle::name``. + +The ``add_torrent_params`` is also used when requesting resume data for a +torrent. It can be saved to and restored from a file and added back to a +new session. For serialization and de-serialization of +``add_torrent_params`` objects, see read_resume_data() and +write_resume_data(). + +The ``add_torrent_params`` is also used to represent a parsed .torrent +file. It can be loaded via load_torrent_file(), load_torrent_buffer() and +load_torrent_parsed(). It can be saved via write_torrent_file(). + +Generates a magnet URI from the specified torrent. + +Several fields from the add_torrent_params objects are recorded in the +magnet link. In order to not include them, they have to be cleared before +calling make_magnet_uri(). These fields are used: + +``ti``, ``info_hashes``, ``url_seeds``, ``dht_nodes``, +``file_priorities``, ``trackers``, ``name``, ``peers``. + +Depending on what the use case for the resulting magnet link is, clearing +``peers`` and ``dht_nodes`` is probably a good idea if the add_torrent_params +came from a running torrent. Those lists may be long and be ephemeral. + +If none of the ``info_hashes`` or ``ti`` fields are set, there is not +info-hash available, and a magnet link cannot be created. In this case +make_magnet_uri() returns an empty string. + +The recommended way to generate a magnet link from a torrent_handle is to +call save_resume_data(), which will post a save_resume_data_alert +containing an add_torrent_params object. This can then be passed to +make_magnet_uri(). + +The overload that takes a torrent_handle will make blocking calls to +query information about the torrent. If the torrent handle is invalid, +an empty string is returned. + +For more information about magnet links, see magnet-links_. + +This function parses out information from the magnet link and populates the +add_torrent_params object. The overload that does not take an +``error_code`` reference will throw a system_error on error +The overload taking an ``add_torrent_params`` reference will fill in the +fields specified in the magnet URI. + +``ignore_unchoke_slots`` determines whether peers should always +unchoke a peer, regardless of the choking algorithm, or if it should +honor the unchoke slot limits. It's used for local peers by default. +If *any* of the peer classes a peer belongs to has this set to true, +that peer will be unchoked at all times. + +adjusts the connection limit (global and per torrent) that applies to +this peer class. By default, local peers are allowed to exceed the +normal connection limit for instance. This is specified as a percent +factor. 100 makes the peer class apply normally to the limit. 200 +means as long as there are fewer connections than twice the limit, we +accept this peer. This factor applies both to the global connection +limit and the per-torrent limit. Note that if not used carefully one +peer class can potentially completely starve out all other over time. + +not used by libtorrent. It's intended as a potentially user-facing +identifier of this peer class. + +transfer rates limits for the whole peer class. They are specified in +bytes per second and apply to the sum of all peers that are members of +this class. + +relative priorities used by the bandwidth allocator in the rate +limiter. If no rate limits are in use, the priority is not used +either. Priorities start at 1 (0 is not a valid priority) and may not +exceed 255. + +holds settings for a peer class. Used in set_peer_class() and +get_peer_class() calls. + +Don't download the file or piece. Partial pieces may still be downloaded when +setting file priorities. + +The default priority for files and pieces. + +The lowest priority for files and pieces. + +The highest priority for files and pieces. + +construct a nullptr client data + + + + + +we don't allow type-unsafe operations + +A thin wrapper around a void pointer used as "user data". i.e. an opaque +cookie passed in to libtorrent and returned on demand. It adds type-safety by +requiring the same type be requested out of it as was assigned to it. + + + + + + + + + + +A type describing kinds of sockets involved in various operations or events. + +These functions load the content of a .torrent file into an +add_torrent_params object. +The immutable part of a torrent file (the info-dictionary) is stored in +the ``ti`` field in the add_torrent_params object (as a torrent_info +object). +The returned object is suitable to be: + + * added to a session via add_torrent() or async_add_torrent() + * saved as a .torrent_file via write_torrent_file() + * turned into a magnet link via make_magnet_uri() + +the index of the file + +the offset from the start of the file, in bytes + +the size of the window, in bytes + +represents a window of a file in a torrent. + +The ``file_index`` refers to the index of the file (in the torrent_info). +To get the path and filename, use ``file_path()`` and give the ``file_index`` +as argument. The ``offset`` is the byte offset in the file where the range +starts, and ``size`` is the number of bytes this range is. The size + offset +will never be greater than the file size. + +returns true if the piece length has been initialized +on the file_storage. This is typically taken as a proxy +of whether the file_storage as a whole is initialized or +not. + +allocates space for ``num_files`` in the internal file list. This can +be used to avoid reallocating the internal file list when the number +of files to be added is known up-front. + +Adds a file to the file storage. The ``add_file_borrow`` version +expects that ``filename`` is the file name (without a path) of +the file that's being added. +This memory is *borrowed*, i.e. it is the caller's +responsibility to make sure it stays valid throughout the lifetime +of this file_storage object or any copy of it. The same thing applies +to ``filehash``, which is an optional pointer to a 20 byte binary +SHA-1 hash of the file. + +if ``filename`` is empty, the filename from ``path`` is used and not +borrowed. + +The ``path`` argument is the full path (in the torrent file) to +the file to add. Note that this is not supposed to be an absolute +path, but it is expected to include the name of the torrent as the +first path element. + +``file_size`` is the size of the file in bytes. + +The ``file_flags`` argument sets attributes on the file. The file +attributes is an extension and may not work in all bittorrent clients. + +For possible file attributes, see file_storage::flags_t. + +The ``mtime`` argument is optional and can be set to 0. If non-zero, +it is the posix time of the last modification time of this file. + +``symlink_path`` is the path the file is a symlink to. To make this a +symlink you also need to set the file_storage::flag_symlink file flag. + +``root_hash`` is an optional pointer to a 32 byte SHA-256 hash, being +the merkle tree root hash for this file. This is only used for v2 +torrents. If the ``root hash`` is specified for one file, it has to +be specified for all, otherwise this function will fail. +Note that the buffer ``root_hash`` points to must out-live the +file_storage object, it will not be copied. This parameter is only +used when *loading* torrents, that already have their file hashes +computed. When creating torrents, the file hashes will be computed by +the piece hashes. + +If more files than one are added, certain restrictions to their paths +apply. In a multi-file file storage (torrent), all files must share +the same root directory. + +That is, the first path element of all files must be the same. +This shared path element is also set to the name of the torrent. It +can be changed by calling ``set_name``. + +The overloads that take an `error_code` reference will report failures +via that variable, otherwise `system_error` is thrown. + +renames the file at ``index`` to ``new_filename``. Keep in mind +that filenames are expected to be UTF-8 encoded. + +returns a list of file_slice objects representing the portions of +files the specified piece index, byte offset and size range overlaps. +this is the inverse mapping of map_file(). + +Preconditions of this function is that the input range is within the +torrents address space. ``piece`` may not be negative and + + ``piece`` * piece_size + ``offset`` + ``size`` + +may not exceed the total size of the torrent. + +returns a peer_request representing the piece index, byte offset +and size the specified file range overlaps. This is the inverse +mapping over map_block(). Note that the ``peer_request`` return type +is meant to hold bittorrent block requests, which may not be larger +than 16 kiB. Mapping a range larger than that may return an overflown +integer. + +returns the number of files in the file_storage + +returns the index of the one-past-end file in the file storage + +returns an implementation-defined type that can be used as the +container in a range-for loop. Where the values are the indices of all +files in the file_storage. + +returns the total number of bytes all the files in this torrent spans + +set and get the number of pieces in the torrent + +returns the index of the one-past-end piece in the file storage + +returns the index of the last piece in the torrent. The last piece is +special in that it may be smaller than the other pieces (and the other +pieces are all the same size). + +returns an implementation-defined type that can be used as the +container in a range-for loop. Where the values are the indices of all +pieces in the file_storage. + +set and get the size of each piece in this torrent. It must be a power of two +and at least 16 kiB. + +returns the piece size of ``index``. This will be the same as piece_length(), except +for the last piece, which may be shorter. + +Returns the size of the given piece. If the piece spans multiple files, +only the first file is considered part of the piece. This is used for +v2 torrents, where all files are piece aligned and padded. i.e. The pad +files are not considered part of the piece for this purpose. + +returns the number of blocks in the specified piece, for v2 torrents. + +returns the number of blocks there are in the typical piece. There +may be fewer in the last piece) + +set and get the name of this torrent. For multi-file torrents, this is also +the name of the root directory all the files are stored in. + +swap all content of *this* with *ti*. + +arrange files and padding to match the canonical form required +by BEP 52 + +These functions are used to query attributes of files at +a given index. + +The ``hash()`` is a SHA-1 hash of the file, or 0 if none was +provided in the torrent file. This can potentially be used to +join a bittorrent network with other file sharing networks. + +``root()`` returns the SHA-256 merkle tree root of the specified file, +in case this is a v2 torrent. Otherwise returns zeros. +``root_ptr()`` returns a pointer to the SHA-256 merkle tree root hash +for the specified file. The pointer points into storage referred to +when the file was added, it is not owned by this object. Torrents +that are not v2 torrents return nullptr. + +The ``mtime()`` is the modification time is the posix +time when a file was last modified when the torrent +was created, or 0 if it was not included in the torrent file. + +``file_path()`` returns the full path to a file. + +``file_size()`` returns the size of a file. + +``pad_file_at()`` returns true if the file at the given +index is a pad-file. + +``file_name()`` returns *just* the name of the file, whereas +``file_path()`` returns the path (inside the torrent file) with +the filename appended. + +``file_offset()`` returns the byte offset within the torrent file +where this file starts. It can be used to map the file to a piece +index (given the piece size). + +Returns the number of pieces or blocks the file at `index` spans, +under the assumption that the file is aligned to the start of a piece. +This is only meaningful for v2 torrents, where files are guaranteed +such alignment. +These numbers are used to size and navigate the merkle hash tree for +each file. + +index of first piece node in the merkle tree + +returns the crc32 hash of file_path(index) + +this will add the CRC32 hash of all directory entries to the table. No +filename will be included, just directories. Every depth of directories +are added separately to allow test for collisions with files at all +levels. i.e. if one path in the torrent is ``foo/bar/baz``, the CRC32 +hashes for ``foo``, ``foo/bar`` and ``foo/bar/baz`` will be added to +the set. + +the file is a pad file. It's required to contain zeros +at it will not be saved to disk. Its purpose is to make +the following file start on a piece boundary. + +this file has the hidden attribute set. This is primarily +a windows attribute + +this file has the executable attribute set. + +this file is a symbolic link. It should have a link +target string associated with it. + +returns a bitmask of flags from file_flags_t that apply +to file at ``index``. + +returns true if the file at the specified index has been renamed to +have an absolute path, i.e. is not anchored in the save path of the +torrent. + +returns the index of the file at the given offset in the torrent + +finds the file with the given root hash and returns its index +if there is no file with the root hash, file_index_t{-1} is returned + +returns the piece index the given file starts at + +validate any symlinks, to ensure they all point to +other files or directories inside this storage. Any invalid symlinks +are updated to point to themselves. + +returns true if this torrent contains v2 metadata. + +The ``file_storage`` class represents a file list and the piece +size. Everything necessary to interpret a regular bittorrent storage +file structure. + +get the ``error_category`` for zip errors + +Not an error + +the supplied gzip buffer has invalid header + +the gzip buffer would inflate to more bytes than the specified +maximum size, and was rejected. + +available inflate data did not terminate + +output space exhausted before completing inflate + +invalid block type (type == 3) + +stored block length did not match one's complement + +dynamic block code description: too many length or distance codes + +dynamic block code description: code lengths codes incomplete + +dynamic block code description: repeat lengths with no first length + +dynamic block code description: repeat more than specified lengths + +dynamic block code description: invalid literal/length code lengths + +dynamic block code description: invalid distance code lengths + +invalid literal/length or distance code in fixed or dynamic block + +distance is too far back in fixed or dynamic block + +an unknown error occurred during gzip inflation + +the number of error codes + +libtorrent uses boost.system's ``error_code`` class to represent errors. libtorrent has +its own error category get_gzip_category() with the error codes defined by error_code_enum. + +include this bit if your plugin needs to alter the order of the +optimistic unchoke of peers. i.e. have the on_optimistic_unchoke() +callback be called. + +include this bit if your plugin needs to have on_tick() called + +include this bit if your plugin needs to have on_dht_request() +called + +include this bit if your plugin needs to have on_alert() +called + +include this bit if your plugin needs to have on_unknown_torrent() +called even if there is no active torrent in the session + +This function is expected to return a bitmask indicating which features +this plugin implements. Some callbacks on this object may not be called +unless the corresponding feature flag is returned here. Note that +callbacks may still be called even if the corresponding feature is not +specified in the return value here. See feature_flags_t for possible +flags to return. + +this is called by the session every time a new torrent is added. +The ``torrent*`` points to the internal torrent object created +for the new torrent. The client_data_t is the userdata pointer as +passed in via add_torrent_params. + +If the plugin returns a torrent_plugin instance, it will be added +to the new torrent. Otherwise, return an empty shared_ptr to a +torrent_plugin (the default). + +called when plugin is added to a session + +called when the session is aborted +the plugin should perform any cleanup necessary to allow the session's +destruction (e.g. cancel outstanding async operations) + +called when a dht request is received. +If your plugin expects this to be called, make sure to include the flag +``dht_request_feature`` in the return value from implemented_features(). + +called when an alert is posted alerts that are filtered are not posted. +If your plugin expects this to be called, make sure to include the flag +``alert_feature`` in the return value from implemented_features(). + +return true if the add_torrent_params should be added + +called once per second. +If your plugin expects this to be called, make sure to include the flag +``tick_feature`` in the return value from implemented_features(). + +called when choosing peers to optimistically unchoke. The return value +indicates the peer's priority for unchoking. Lower return values +correspond to higher priority. Priorities above 2^63-1 are reserved. +If your plugin has no priority to assign a peer it should return 2^64-1. +If your plugin expects this to be called, make sure to include the flag +``optimistic_unchoke_feature`` in the return value from implemented_features(). +If multiple plugins implement this function the lowest return value +(i.e. the highest priority) is used. + + +called on startup while loading settings state from the session_params + +this is the base class for a session plugin. One primary feature +is that it is notified of all torrents that are added to the session, +and can add its own torrent_plugins. + +This function is called each time a new peer is connected to the torrent. You +may choose to ignore this by just returning a default constructed +``shared_ptr`` (in which case you don't need to override this member +function). + +If you need an extension to the peer connection (which most plugins do) you +are supposed to return an instance of your peer_plugin class. Which in +turn will have its hook functions called on event specific to that peer. + +The ``peer_connection_handle`` will be valid as long as the ``shared_ptr`` +is being held by the torrent object. So, it is generally a good idea to not +keep a ``shared_ptr`` to your own peer_plugin. If you want to keep references +to it, use ``weak_ptr``. + +If this function throws an exception, the connection will be closed. + +These hooks are called when a piece passes the hash check or fails the hash +check, respectively. The ``index`` is the piece index that was downloaded. +It is possible to access the list of peers that participated in sending the +piece through the ``torrent`` and the ``piece_picker``. + +This hook is called approximately once per second. It is a way of making it +easy for plugins to do timed events, for sending messages or whatever. + +These hooks are called when the torrent is paused and resumed respectively. +The return value indicates if the event was handled. A return value of +``true`` indicates that it was handled, and no other plugin after this one +will have this hook function called, and the standard handler will also not be +invoked. So, returning true effectively overrides the standard behavior of +pause or resume. + +Note that if you call ``pause()`` or ``resume()`` on the torrent from your +handler it will recurse back into your handler, so in order to invoke the +standard handler, you have to keep your own state on whether you want standard +behavior or overridden behavior. + +This function is called when the initial files of the torrent have been +checked. If there are no files to check, this function is called immediately. + +i.e. This function is always called when the torrent is in a state where it +can start downloading. + +called when the torrent changes state +the state is one of torrent_status::state_t +enum members + +this is the first time we see this peer + +this peer was not added because it was +filtered by the IP filter + +called every time a new peer is added to the peer list. +This is before the peer is connected to. For ``flags``, see +torrent_plugin::flags_t. The ``source`` argument refers to +the source where we learned about this peer from. It's a +bitmask, because many sources may have told us about the same +peer. For peer source flags, see peer_info::peer_source_flags. + +Torrent plugins are associated with a single torrent and have a number +of functions called at certain events. Many of its functions have the +ability to change or override the default libtorrent behavior. + +This function is expected to return the name of +the plugin. + +can add entries to the extension handshake +this is not called for web seeds + +called when the peer is being disconnected. + +called when the peer is successfully connected. Note that +incoming connections will have been connected by the time +the peer plugin is attached to it, and won't have this hook +called. + +this is called when the initial bittorrent handshake is received. +Returning false means that the other end doesn't support this extension +and will remove it from the list of plugins. this is not called for web +seeds + +called when the extension handshake from the other end is received +if this returns false, it means that this extension isn't +supported by this peer. It will result in this peer_plugin +being removed from the peer_connection and destructed. +this is not called for web seeds + +returning true from any of the message handlers +indicates that the plugin has handled the message. +it will break the plugin chain traversing and not let +anyone else handle the message, including the default +handler. + +This function is called when the peer connection is receiving +a piece. ``buf`` points (non-owning pointer) to the data in an +internal immutable disk buffer. The length of the data is specified +in the ``length`` member of the ``piece`` parameter. +returns true to indicate that the piece is handled and the +rest of the logic should be ignored. + + + +called after a choke message has been sent to the peer + +called after piece data has been sent to the peer +this can be used for stats book keeping + +called when libtorrent think this peer should be disconnected. +if the plugin returns false, the peer will not be disconnected. + +called when an extended message is received. If returning true, +the message is not processed by any other plugin and if false +is returned the next plugin in the chain will receive it to +be able to handle it. This is not called for web seeds. +thus function may be called more than once per incoming message, but +only the last of the calls will the ``body`` size equal the ``length``. +i.e. Every time another fragment of the message is received, this +function will be called, until finally the whole message has been +received. The purpose of this is to allow early disconnects for invalid +messages and for reporting progress of receiving large messages. + +this is not called for web seeds + +called when a piece that this peer participated in either +fails or passes the hash_check + +called approximately once every second + +called each time a request message is to be sent. If true +is returned, the original request message won't be sent and +no other plugin will have this function called. + +peer plugins are associated with a specific peer. A peer could be +both a regular bittorrent peer (``bt_peer_connection``) or one of the +web seed connections (``web_peer_connection`` or ``http_seed_connection``). +In order to only attach to certain peers, make your +torrent_plugin::new_connection only return a plugin for certain peer +connection types + + + +decrypt the provided buffers. +returns is a tuple representing the values +(consume, produce, packet_size) + +consume is set to the number of bytes which should be trimmed from the +head of the buffers, default is 0 + +produce is set to the number of bytes of payload which are now ready to +be sent to the upper layer. default is the number of bytes passed in receive_vec + +packet_size is set to the minimum number of bytes which must be read to +advance the next step of decryption. default is 0 + + +A human readable string describing the software at the other end of +the connection. In some cases this information is not available, then +it will contain a string that may give away something about which +software is running in the other end. In the case of a web seed, the +server type and version will be a part of this string. This is UTF-8 +encoded. + +a bitfield, with one bit per piece in the torrent. Each bit tells you +if the peer has that piece (if it's set to 1) or if the peer miss that +piece (set to 0). + +the total number of bytes downloaded from and uploaded to this peer. +These numbers do not include the protocol chatter, but only the +payload data. + +the time since we last sent a request to this peer and since any +transfer occurred with this peer + +the time until all blocks in the request queue will be downloaded + +**we** are interested in pieces from this peer. + +**we** have choked this peer. + +the peer is interested in **us** + +the peer has choked **us**. + +means that this peer supports the +`extension protocol`__. + +__ extension_protocol.html + +The connection was initiated by us, the peer has a +listen port open, and that port is the same as in the +address of this peer. If this flag is not set, this +peer connection was opened by this peer connecting to +us. + +deprecated synonym for outgoing_connection + +The connection is opened, and waiting for the +handshake. Until the handshake is done, the peer +cannot be identified. + +The connection is in a half-open state (i.e. it is +being connected). + +The peer has participated in a piece that failed the +hash check, and is now "on parole", which means we're +only requesting whole pieces from this peer until +it either fails that piece or proves that it doesn't +send bad data. + +This peer is a seed (it has all the pieces). + +This peer is subject to an optimistic unchoke. It has +been unchoked for a while to see if it might unchoke +us in return an earn an upload/unchoke slot. If it +doesn't within some period of time, it will be choked +and another peer will be optimistically unchoked. + +This peer has recently failed to send a block within +the request timeout from when the request was sent. +We're currently picking one block at a time from this +peer. + +This peer has either explicitly (with an extension) +or implicitly (by becoming a seed) told us that it +will not downloading anything more, regardless of +which pieces we have. + +This means the last time this peer picket a piece, +it could not pick as many as it wanted because there +were not enough free ones. i.e. all pieces this peer +has were already requested from other peers. + +This flag is set if the peer was in holepunch mode +when the connection succeeded. This typically only +happens if both peers are behind a NAT and the peers +connect via the NAT holepunch mechanism. + +indicates that this socket is running on top of the +I2P transport. + +indicates that this socket is a uTP socket + +indicates that this socket is running on top of an SSL +(TLS) channel + +this connection is obfuscated with RC4 + +the handshake of this connection was obfuscated +with a Diffie-Hellman exchange + +tells you in which state the peer is in. It is set to +any combination of the peer_flags_t flags above. + +The peer was received from the tracker. + +The peer was received from the kademlia DHT. + +The peer was received from the peer exchange +extension. + +The peer was received from the local service +discovery (The peer is on the local network). + +The peer was added from the fast resume data. + +we received an incoming connection from this peer + +a combination of flags describing from which sources this peer +was received. A combination of the peer_source_flags_t above. + +the current upload and download speed we have to and from this peer +(including any protocol messages). updated about once per second + +The transfer rates of payload data only updated about once per second + +the peer's id as used in the bittorrent protocol. This id can be used +to extract 'fingerprints' from the peer. Sometimes it can tell you +which client the peer is using. See identify_client()_ + +the number of bytes we have requested from this peer, but not yet +received. + +the number of seconds until the current front piece request will time +out. This timeout can be adjusted through +``settings_pack::request_timeout``. +-1 means that there is not outstanding request. + +the number of bytes allocated +and used for the peer's send buffer, respectively. + +the number of bytes +allocated and used as receive buffer, respectively. + +the number of pieces this peer has participated in sending us that +turned out to fail the hash check. + +this is the number of requests we have sent to this peer that we +haven't got a response for yet + +the number of block requests that have timed out, and are still in the +download queue + +the number of busy requests in the download queue. A busy request is a +request for a block we've also requested from a different peer + +the number of requests messages that are currently in the send buffer +waiting to be sent. + +the number of requests that is tried to be maintained (this is +typically a function of download speed) + +the number of piece-requests we have received from this peer +that we haven't answered with a piece yet. + +the number of times this peer has "failed". i.e. failed to connect or +disconnected us. The failcount is decremented when we see this peer in +a tracker response or peer exchange message. + +You can know which piece, and which part of that piece, that is +currently being downloaded from a specific peer by looking at these +four members. ``downloading_piece_index`` is the index of the piece +that is currently being downloaded. This may be set to -1 if there's +currently no piece downloading from this peer. If it is >= 0, the +other three members are valid. ``downloading_block_index`` is the +index of the block (or sub-piece) that is being downloaded. +``downloading_progress`` is the number of bytes of this block we have +received from the peer, and ``downloading_total`` is the total number +of bytes in this block. + +Regular bittorrent connection + +HTTP connection using the `BEP 19`_ protocol + +HTTP connection using the `BEP 17`_ protocol + +the kind of connection this peer uses. See connection_type_t. + +the number of bytes this peer has pending in the disk-io thread. +Downloaded and waiting to be written to disk. This is what is capped +by ``settings_pack::max_queued_disk_bytes``. + +number of outstanding bytes to read +from disk + +the number of bytes this peer has been assigned to be allowed to send +and receive until it has to request more quota from the bandwidth +manager. + +an estimated round trip time to this peer, in milliseconds. It is +estimated by timing the TCP ``connect()``. It may be 0 for +incoming connections. + +the number of pieces this peer has. + +the highest download and upload rates seen on this connection. They +are given in bytes per second. This number is reset to 0 on reconnect. + +the progress of the peer in the range [0, 1]. This is always 0 when +floating point operations are disabled, instead use ``progress_ppm``. + +indicates the download progress of the peer in the range [0, 1000000] +(parts per million). + +the IP-address to this peer. The type is an asio endpoint. For +more info, see the asio_ documentation. This field is not valid for +i2p peers. Instead use the i2p_destination() function. + + +the IP and port pair the socket is bound to locally. i.e. the IP +address of the interface it's going out over. This may be useful for +multi-homed clients with multiple interfaces to the internet. +This field is not valid for i2p peers. + +The peer is not waiting for any external events to +send or receive data. + +The peer is waiting for the rate limiter. + +The peer has quota and is currently waiting for a +network read or write operation to complete. This is +the state all peers are in if there are no bandwidth +limits. + +The peer is waiting for the disk I/O thread to catch +up writing buffers to disk before downloading more. + +bitmasks indicating what state this peer +is in with regards to sending and receiving data. The states are +defined as independent flags of type bandwidth_state_flags_t, in this +class. + +If this peer is an i2p peer, this function returns the destination +address of the peer + +holds information and statistics about one peer +that libtorrent is connected to + + + +http seeds are different from url seeds in the +protocol they use. http seeds follows the original +http seed spec. by John Hoffman + +URL and type comparison + +URL and type less-than comparison + +The URL of the web seed + +Optional authentication. If this is set, it's passed +in as HTTP basic auth to the web seed. The format is: +username:password. + +Any extra HTTP headers that need to be passed to the web seed + +The type of web seed (see type_t) + +the web_seed_entry holds information about a web seed (also known +as URL seed or HTTP seed). It is essentially a URL with some state +associated with it. For more information, see `BEP 17`_ and `BEP 19`_. + +the max size of a .torrent file to load into RAM + +the max number of pieces allowed in the torrent + +the max recursion depth in the bdecoded structure + +the max number of bdecode tokens + +this object holds configuration options for limits to use when loading +torrents. They are meant to prevent loading potentially malicious torrents +that cause excessive memory allocations. + +The constructor that takes an info-hash will initialize the info-hash +to the given value, but leave all other fields empty. This is used +internally when downloading torrents without the metadata. The +metadata will be created by libtorrent as soon as it has been +downloaded from the swarm. + +The constructor that takes a bdecode_node will create a torrent_info +object from the information found in the given torrent_file. The +bdecode_node represents a tree node in an bencoded file. To load an +ordinary .torrent file into a bdecode_node, use bdecode(). + +The version that takes a buffer pointer and a size will decode it as a +.torrent file and initialize the torrent_info object for you. + +The version that takes a filename will simply load the torrent file +and decode it inside the constructor, for convenience. This might not +be the most suitable for applications that want to be able to report +detailed errors on what might go wrong. + +There is an upper limit on the size of the torrent file that will be +loaded by the overload taking a filename. If it's important that even +very large torrent files are loaded, use one of the other overloads. + +The overloads that takes an ``error_code const&`` never throws if an +error occur, they will simply set the error code to describe what went +wrong and not fully initialize the torrent_info object. The overloads +that do not take the extra error_code parameter will always throw if +an error occurs. These overloads are not available when building +without exception support. + +The overload that takes a ``span`` also needs an extra parameter of +type ``from_span_t`` to disambiguate the ``std::string`` overload for +string literals. There is an object in the libtorrent namespace of this +type called ``from_span``. + +frees all storage associated with this torrent_info object + +The file_storage object contains the information on how to map the +pieces to files. It is separated from the torrent_info object because +when creating torrents a storage object needs to be created without +having a torrent file. When renaming files in a storage, the storage +needs to make its own copy of the file_storage in order to make its +mapping differ from the one in the torrent file. + +``orig_files()`` returns the original (unmodified) file storage for +this torrent. This is used by the web server connection, which needs +to request files with the original names. Filename may be changed using +``torrent_info::rename_file()``. + +For more information on the file_storage object, see the separate +document on how to create torrents. + +Renames the file with the specified index to the new name. The new +filename is reflected by the ``file_storage`` returned by ``files()`` +but not by the one returned by ``orig_files()``. + +If you want to rename the base name of the torrent (for a multi file +torrent), you can copy the ``file_storage`` (see files() and +orig_files() ), change the name, and then use `remap_files()`_. + +The ``new_filename`` can both be a relative path, in which case the +file name is relative to the ``save_path`` of the torrent. If the +``new_filename`` is an absolute path (i.e. ``is_complete(new_filename) +== true``), then the file is detached from the ``save_path`` of the +torrent. In this case the file is not moved when move_storage() is +invoked. + + Using `remap_files()` is discouraged as it's incompatible with v2 + torrents. This is because the piece boundaries and piece hashes in + v2 torrents are intimately tied to the file boundaries. Instead, + just rename individual files, or implement a custom disk_interface + to customize how to store files. + +Remaps the file storage to a new file layout. This can be used to, for +instance, download all data in a torrent to a single file, or to a +number of fixed size sector aligned files, regardless of the number +and sizes of the files in the torrent. + +The new specified ``file_storage`` must have the exact same size as +the current one. + +``add_tracker()`` adds a tracker to the announce-list. The ``tier`` +determines the order in which the trackers are to be tried. +The ``trackers()`` function will return a sorted vector of +announce_entry. Each announce entry contains a string, which is +the tracker url, and a tier index. The tier index is the high-level +priority. No matter which trackers that works or not, the ones with +lower tier will always be tried before the one with higher tier +number. For more information, see announce_entry. + +``trackers()`` returns all entries from announce-list. + +``clear_trackers()`` removes all trackers from announce-list. + +These two functions are related to `BEP 38`_ (mutable torrents). The +vectors returned from these correspond to the "similar" and +"collections" keys in the .torrent file. Both info-hashes and +collections from within the info-dict and from outside of it are +included. + +``web_seeds()`` returns all url seeds and http seeds in the torrent. +Each entry is a ``web_seed_entry`` and may refer to either a url seed +or http seed. + +``add_url_seed()`` and ``add_http_seed()`` adds one url to the list of +url/http seeds. + +``set_web_seeds()`` replaces all web seeds with the ones specified in +the ``seeds`` vector. + +The ``extern_auth`` argument can be used for other authorization +schemes than basic HTTP authorization. If set, it will override any +username and password found in the URL itself. The string will be sent +as the HTTP authorization header's value (without specifying "Basic"). + +The ``extra_headers`` argument defaults to an empty list, but can be +used to insert custom HTTP headers in the requests to a specific web +seed. + +See http-seeding_ for more information. + +``total_size()`` returns the total number of bytes the torrent-file +represents. Note that this is the number of pieces times the piece +size (modulo the last piece possibly being smaller). With pad files, +the total size will be larger than the sum of all (regular) file +sizes. + +``piece_length()`` and ``num_pieces()`` returns the number of byte +for each piece and the total number of pieces, respectively. The +difference between ``piece_size()`` and ``piece_length()`` is that +``piece_size()`` takes the piece index as argument and gives you the +exact size of that piece. It will always be the same as +``piece_length()`` except in the case of the last piece, which may be +smaller. + +returns the number of blocks there are in the typical piece. There +may be fewer in the last piece) + +``last_piece()`` returns the index to the last piece in the torrent and +``end_piece()`` returns the index to the one-past-end piece in the +torrent +``piece_range()`` returns an implementation-defined type that can be +used as the container in a range-for loop. Where the values are the +indices of all pieces in the file_storage. + +returns the info-hash of the torrent. For BitTorrent v2 support, use +``info_hashes()`` to get an object that may hold both a v1 and v2 +info-hash + +returns whether this torrent has v1 and/or v2 metadata, respectively. +Hybrid torrents have both. These are shortcuts for +info_hashes().has_v1() and info_hashes().has_v2() calls. + +If you need index-access to files you can use the ``num_files()`` along +with the ``file_path()``, ``file_size()``-family of functions to access +files using indices. + +This function will map a piece index, a byte offset within that piece +and a size (in bytes) into the corresponding files with offsets where +that data for that piece is supposed to be stored. See file_slice. + +This function will map a range in a specific file into a range in the +torrent. The ``file_offset`` parameter is the offset in the file, +given in bytes, where 0 is the start of the file. See peer_request. + +The input range is assumed to be valid within the torrent. +``file_offset`` + ``size`` is not allowed to be greater than the file +size. ``file_index`` must refer to a valid file, i.e. it cannot be >= +``num_files()``. + +Returns the SSL root certificate for the torrent, if it is an SSL +torrent. Otherwise returns an empty string. The certificate is +the public certificate in x509 format. + +returns true if this torrent_info object has a torrent loaded. +This is primarily used to determine if a magnet link has had its +metadata resolved yet or not. + +returns true if this torrent is private. i.e., the client should not +advertise itself on the trackerless network (the Kademlia DHT) for this torrent. + +returns true if this is an i2p torrent. This is determined by whether +or not it has a tracker whose URL domain name ends with ".i2p". i2p +torrents disable the DHT and local peer discovery as well as talking +to peers over anything other than the i2p network. + +returns the piece size of file with ``index``. This will be the same as piece_length(), +except for the last piece, which may be shorter. + +``hash_for_piece()`` takes a piece-index and returns the 20-bytes +sha1-hash for that piece and ``info_hash()`` returns the 20-bytes +sha1-hash for the info-section of the torrent file. +``hash_for_piece_ptr()`` returns a pointer to the 20 byte sha1 digest +for the piece. Note that the string is not 0-terminated. + + +``name()`` returns the name of the torrent. +name contains UTF-8 encoded string. + +``creation_date()`` returns the creation date of the torrent as time_t +(`posix time`_). If there's no time stamp in the torrent file, 0 is +returned. + +``creator()`` returns the creator string in the torrent. If there is +no creator string it will return an empty string. + +``comment()`` returns the comment associated with the torrent. If +there's no comment, it will return an empty string. +comment contains UTF-8 encoded string. + +If this torrent contains any DHT nodes, they are put in this vector in +their original form (host name and port number). + +This is used when creating torrent. Use this to add a known DHT node. +It may be used, by the client, to bootstrap into the DHT network. + +populates the torrent_info by providing just the info-dict buffer. +This is used when loading a torrent from a magnet link for instance, +where we only have the info-dict. The bdecode_node ``e`` points to a +parsed info-dictionary. ``ec`` returns an error code if something +fails (typically if the info dictionary is malformed). +The `max_pieces` parameter allows limiting the amount of memory +dedicated to loading the torrent, and fails for torrents that exceed +the limit. To load large torrents, this limit may also need to be +raised in settings_pack::max_piece_count and in calls to +read_resume_data(). + +This function looks up keys from the info-dictionary of the loaded +torrent file. It can be used to access extension values put in the +.torrent file. If the specified key cannot be found, it returns nullptr. + +returns a the raw info section of the torrent file. +The underlying buffer is still owned by the torrent_info object + +return the bytes of the piece layer hashes for the specified file. If +the file doesn't have a piece layer, an empty span is returned. +The span size is divisible by 32, the size of a SHA-256 hash. +If the size of the file is smaller than or equal to the piece size, +the files "root hash" is the hash of the file and is not saved +separately in the "piece layers" field, but this function still +returns the root hash of the file in that case. + +clears the piece layers from the torrent_info. This is done by the +session when a torrent is added, to avoid storing it twice. The piece +layer (or other hashes part of the merkle tree) are stored in the +internal torrent object. + +the torrent_info class holds the information found in a .torrent file. + +This constructor can be used to start with the default plugins +(ut_metadata, ut_pex and smart_ban). Pass a settings_pack to set the +initial settings when the session starts. + +This constructor helps to configure the set of initial plugins +to be added to the session before it's started. + +The settings to configure the session with + +the plugins to add to the session as it is constructed + +DHT node ID and node addresses to bootstrap the DHT with. + +function object to construct the storage object for DHT items. + +function object to create the disk I/O subsystem. Defaults to +default_disk_io_constructor. + +this container can be used by extensions/plugins to store settings. It's +primarily here to make it convenient to save and restore state across +sessions, using read_session_params() and write_session_params(). + +the IP filter to use for the session. This restricts which peers are allowed +to connect. As if passed to set_ip_filter(). + +The session_params is a parameters pack for configuring the session +before it's started. + +These functions serialize and de-serialize a ``session_params`` object to and +from bencoded form. The session_params object is used to initialize a new +session using the state from a previous one (or by programmatically configure +the session up-front). +The flags parameter can be used to only save and load certain aspects of the +session's state. +The ``_buf`` suffix indicates the function operates on buffer rather than the +bencoded structure. +The torrents in a session are not part of the session_params state, they have +to be restored separately. + +This is a utility function to produce a client ID fingerprint formatted to +the most common convention. The fingerprint can be set via the +``peer_fingerprint`` setting, in settings_pack. + +The name string should contain exactly two characters. These are the +characters unique to your client, used to identify it. Make sure not to +clash with anybody else. Here are some taken id's: + ++----------+-----------------------+ +| id chars | client | ++==========+=======================+ +| LT | libtorrent (default) | ++----------+-----------------------+ +| UT | uTorrent | ++----------+-----------------------+ +| UM | uTorrent Mac | ++----------+-----------------------+ +| qB | qBittorrent | ++----------+-----------------------+ +| BP | BitTorrent Pro | ++----------+-----------------------+ +| BT | BitTorrent | ++----------+-----------------------+ +| DE | Deluge | ++----------+-----------------------+ +| AZ | Azureus | ++----------+-----------------------+ +| TL | Tribler | ++----------+-----------------------+ + +There's an informal directory of client id's here_. + + +The ``major``, ``minor``, ``revision`` and ``tag`` parameters are used to +identify the version of your client. + +Not an error + +Two torrents has files which end up overwriting each other + +A piece did not match its piece hash + +The .torrent file does not contain a bencoded dictionary at +its top level + +The .torrent file does not have an ``info`` dictionary + +The .torrent file's ``info`` entry is not a dictionary + +The .torrent file does not have a ``piece length`` entry + +The .torrent file does not have a ``name`` entry + +The .torrent file's name entry is invalid + +The length of a file, or of the whole .torrent file is invalid. +Either negative or not an integer + +Failed to parse a file entry in the .torrent + +The ``pieces`` field is missing or invalid in the .torrent file + +The ``pieces`` string has incorrect length + +The .torrent file has more pieces than is supported by libtorrent + +The metadata (.torrent file) that was received from the swarm +matched the info-hash, but failed to be parsed + +The file or buffer is not correctly bencoded + +The .torrent file does not contain any files + +The string was not properly url-encoded as expected + +Operation is not permitted since the session is shutting down + +There's already a torrent with that info-hash added to the +session + +The supplied torrent_handle is not referring to a valid torrent + +The type requested from the entry did not match its type + +The specified URI does not contain a valid info-hash + +One of the files in the torrent was unexpectedly small. This +might be caused by files being changed by an external process + +The URL used an unknown protocol. Currently ``http`` and +``https`` (if built with openssl support) are recognized. For +trackers ``udp`` is recognized as well. + +The URL did not conform to URL syntax and failed to be parsed + +The peer sent a piece message of length 0 + +A bencoded structure was corrupt and failed to be parsed + +The fast resume file was missing or had an invalid file version +tag + +The fast resume file was missing or had an invalid info-hash + +The info-hash did not match the torrent + +The URL contained an invalid hostname + +The URL had an invalid port + +The port is blocked by the port-filter, and prevented the +connection + +The IPv6 address was expected to end with "]" + +The torrent is being destructed, preventing the operation to +succeed + +The connection timed out + +The peer is upload only, and we are upload only. There's no point +in keeping the connection + +The peer is upload only, and we're not interested in it. There's +no point in keeping the connection + +The peer sent an unknown info-hash + +The torrent is paused, preventing the operation from succeeding + +The peer sent an invalid have message, either wrong size or +referring to a piece that doesn't exist in the torrent + +The bitfield message had the incorrect size + +The peer kept requesting pieces after it was choked, possible +abuse attempt. + +The peer sent a piece message that does not correspond to a +piece request sent by the client + +memory allocation failed + +The torrent is aborted, preventing the operation to succeed + +The peer is a connection to ourself, no point in keeping it + +The peer sent a piece message with invalid size, either negative +or greater than one block + +The peer has not been interesting or interested in us for too +long, no point in keeping it around + +The peer has not said anything in a long time, possibly dead + +The peer did not send a handshake within a reasonable amount of +time, it might not be a bittorrent peer + +The peer has been unchoked for too long without requesting any +data. It might be lying about its interest in us + +The peer sent an invalid choke message + +The peer send an invalid unchoke message + +The peer sent an invalid interested message + +The peer sent an invalid not-interested message + +The peer sent an invalid piece request message + +The peer sent an invalid hash-list message (this is part of the +merkle-torrent extension) + +The peer sent an invalid hash-piece message (this is part of the +merkle-torrent extension) + +The peer sent an invalid cancel message + +The peer sent an invalid DHT port-message + +The peer sent an invalid suggest piece-message + +The peer sent an invalid have all-message + +The peer sent an invalid have none-message + +The peer sent an invalid reject message + +The peer sent an invalid allow fast-message + +The peer sent an invalid extension message ID + +The peer sent an invalid message ID + +The synchronization hash was not found in the encrypted handshake + +The encryption constant in the handshake is invalid + +The peer does not support plain text, which is the selected mode + +The peer does not support RC4, which is the selected mode + +The peer does not support any of the encryption modes that the +client supports + +The peer selected an encryption mode that the client did not +advertise and does not support + +The pad size used in the encryption handshake is of invalid size + +The encryption handshake is invalid + +The client is set to not support incoming encrypted connections +and this is an encrypted connection + +The client is set to not support incoming regular bittorrent +connections, and this is a regular connection + +The client is already connected to this peer-ID + +Torrent was removed + +The packet size exceeded the upper sanity check-limit + + +The web server responded with an error + +The web server response is missing a location header + +The web seed redirected to a path that no longer matches the +.torrent directory structure + +The connection was closed because it redirected to a different +URL + +The HTTP range header is invalid + +The HTTP response did not have a content length + +The IP is blocked by the IP filter + +At the connection limit + +The peer is marked as banned + +The torrent is stopping, causing the operation to fail + +The peer has sent too many corrupt pieces and is banned + +The torrent is not ready to receive peers + +The peer is not completely constructed yet + +The session is closing, causing the operation to fail + +The peer was disconnected in order to leave room for a +potentially better peer + +The torrent is finished + +No UPnP router found + +The metadata message says the metadata exceeds the limit + +The peer sent an invalid metadata request message + +The peer advertised an invalid metadata size + +The peer sent a message with an invalid metadata offset + +The peer sent an invalid metadata message + +The peer sent a peer exchange message that was too large + +The peer sent an invalid peer exchange message + +The peer sent an invalid tracker exchange message + +The peer sent an pex messages too often. This is a possible +attempt of and attack + +The operation failed because it requires the torrent to have +the metadata (.torrent file) and it doesn't have it yet. +This happens for magnet links before they have downloaded the +metadata, and also torrents added by URL. + +The peer sent an invalid ``dont_have`` message. The don't have +message is an extension to allow peers to advertise that the +no longer has a piece they previously had. + +The peer tried to connect to an SSL torrent without connecting +over SSL. + +The peer tried to connect to a torrent with a certificate +for a different torrent. + +the torrent is not an SSL torrent, and the operation requires +an SSL torrent + +peer was banned because its listen port is within a banned port +range, as specified by the port_filter. + +The session_handle is not referring to a valid session_impl + +the listen socket associated with this request was closed + + + + + + + + + +The resume data file is missing the ``file sizes`` entry + +The resume data file ``file sizes`` entry is empty + +The resume data file is missing the ``pieces`` and ``slots`` entry + +The number of files in the resume data does not match the number +of files in the torrent + +One of the files on disk has a different size than in the fast +resume file + +One of the files on disk has a different timestamp than in the +fast resume file + +The resume data file is not a dictionary + +The ``blocks per piece`` entry is invalid in the resume data file + +The resume file is missing the ``slots`` entry, which is required +for torrents with compact allocation. *DEPRECATED* + +The resume file contains more slots than the torrent + +The ``slot`` entry is invalid in the resume data + +One index in the ``slot`` list is invalid + +The pieces on disk needs to be re-ordered for the specified +allocation mode. This happens if you specify sparse allocation +and the files on disk are using compact storage. The pieces needs +to be moved to their right position. *DEPRECATED* + +this error is returned when asking to save resume data and +specifying the flag to only save when there's anything new to save +(torrent_handle::only_if_modified) and there wasn't anything changed. + +the save_path in add_torrent_params is not valid + +The HTTP header was not correctly formatted + +The HTTP response was in the 300-399 range but lacked a location +header + +The HTTP response was encoded with gzip or deflate but +decompressing it failed + +The URL specified an i2p address, but no i2p router is configured + +i2p acceptor is not available yet, can't announce without endpoint + +The tracker URL doesn't support transforming it into a scrape +URL. i.e. it doesn't contain "announce. + +invalid tracker response + +invalid peer dictionary entry. Not a dictionary + +tracker sent a failure message + +missing or invalid ``files`` entry + +missing or invalid ``hash`` entry + +missing or invalid ``peers`` and ``peers6`` entry + +UDP tracker response packet has invalid size + +invalid transaction id in UDP tracker response + +invalid action field in UDP tracker response + +skipped announce (because it's assumed to be unreachable over the +given source network interface) + +random number generation failed + +blocked by SSRF mitigation + +blocked because IDNA host names are banned + +the torrent file has an unknown meta version + +the v2 torrent file has no file tree + +the torrent contains v2 keys but does not specify meta version 2 + +the v1 and v2 file metadata does not match + +one or more files are missing piece layer hashes + +a piece layer has the wrong size or failed hash check + +a v2 file entry has no root hash + +the v1 and v2 hashes do not describe the same data + +a file in the v2 metadata has the pad attribute set + +the number of error codes + +libtorrent uses boost.system's ``error_code`` class to represent +errors. libtorrent has its own error category +libtorrent_category() with the error codes defined by +error_code_enum. + + + + + + + + + + + + + + + + + + +HTTP errors are reported in the libtorrent::http_category, with error code enums in +the ``libtorrent::errors`` namespace. + +return the instance of the libtorrent_error_category which +maps libtorrent error codes to human readable error messages. + +returns the error_category for HTTP errors + +explicitly converts to true if this object represents an error, and +false if it does not. + +the error that occurred + +set and query the index (in the torrent) of the file this error +occurred on. This may also have special values defined in +torrent_status. + +A code from operation_t enum, indicating what +kind of operation failed. + +used by storage to return errors +also includes which underlying file the +error happened on + +constructs a memory mapped file disk I/O object. + +creates a disk io object that discards all data written to it, and only +returns zero-buffers when read from. May be useful for testing and +benchmarking. + +See documentation of internal random_bytes + +Creates a new key pair from the given seed. + +It's important to clarify that the seed completely determines +the key pair. Then it's enough to save the seed and the +public key as the key-pair in a buffer of 64 bytes. The standard +is (32 bytes seed, 32 bytes public key). + +This function does work with a given seed, giving you a pair of +(64 bytes private key, 32 bytes public key). It's a trade-off between +space and CPU, saving in one format or another. + +The smaller format is not weaker by any means, in fact, it is only +the seed (32 bytes) that determines the point in the curve. + +Creates a signature of the given message with the given key pair. + +Verifies the signature on the given message using ``pk`` + +Adds a scalar to the given key pair where scalar is a 32 byte buffer +(possibly generated with `ed25519_create_seed`), generating a new key pair. + +You can calculate the public key sum without knowing the private key and +vice versa by passing in null for the key you don't know. This is useful +when a third party (an authoritative server for example) needs to enforce +randomness on a key pair while only knowing the public key of the other +side. + +Warning: the last bit of the scalar is ignored - if comparing scalars make +sure to clear it with `scalar[31] &= 127`. + +see http://crypto.stackexchange.com/a/6215/4697 +see test_ed25519 for a practical example + +Performs a key exchange on the given public key and private key, producing a +shared secret. It is recommended to hash the shared secret before using it. + +This is useful when two parties want to share a secret but both only knows +their respective public keys. +see test_ed25519 for a practical example + + +This member function set the counters to zero. + +This structure hold the relevant counters for the storage + +This member function notifies the list of all node's ids +of each DHT running inside libtorrent. It's advisable +that the concrete implementation keeps a copy of this list +for an eventual prioritization when deleting an element +to make room for a new one. + +This function retrieve the peers tracked by the DHT +corresponding to the given info_hash. You can specify if +you want only seeds and/or you are scraping the data. + +For future implementers: +If the torrent tracked contains a name, such a name +must be stored as a string in peers["n"] + +If the scrape parameter is true, you should fill these keys: + + peers["BFpe"] + with the standard bit representation of a + 256 bloom filter containing the downloaders + peers["BFsd"] + with the standard bit representation of a + 256 bloom filter containing the seeders + +If the scrape parameter is false, you should fill the +key peers["values"] with a list containing a subset of +peers tracked by the given info_hash. Such a list should +consider the value of settings_pack::dht_max_peers_reply. +If noseed is true only peers marked as no seed should be included. + +returns true if the maximum number of peers are stored +for this info_hash. + +This function is named announce_peer for consistency with the +upper layers, but has nothing to do with networking. Its only +responsibility is store the peer in such a way that it's returned +in the entry with the lookup_peers. + +The ``name`` parameter is the name of the torrent if provided in +the announce_peer DHT message. The length of this value should +have a maximum length in the final storage. The default +implementation truncate the value for a maximum of 50 characters. + +This function retrieves the immutable item given its target hash. + +For future implementers: +The value should be returned as an entry in the key item["v"]. + +returns true if the item is found and the data is returned +inside the (entry) out parameter item. + +Store the item's data. This layer is only for storage. +The authentication of the item is performed by the upper layer. + +For implementers: +This data can be stored only if the target is not already +present. The implementation should consider the value of +settings_pack::dht_max_dht_items. + + +This function retrieves the sequence number of a mutable item. + +returns true if the item is found and the data is returned +inside the out parameter seq. + +This function retrieves the mutable stored in the DHT. + +For implementers: +The item sequence should be stored in the key item["seq"]. +if force_fill is true or (0 <= seq and seq < item["seq"]) +the following keys should be filled +item["v"] - with the value no encoded. +item["sig"] - with a string representation of the signature. +item["k"] - with a string representation of the public key. + +returns true if the item is found and the data is returned +inside the (entry) out parameter item. + +Store the item's data. This layer is only for storage. +The authentication of the item is performed by the upper layer. + +For implementers: +The sequence number should be checked if the item is already +present. The implementation should consider the value of +settings_pack::dht_max_dht_items. + + +This function retrieves a sample info-hashes + +For implementers: +The info-hashes should be stored in ["samples"] (N x 20 bytes). +the following keys should be filled +item["interval"] - the subset refresh interval in seconds. +item["num"] - number of info-hashes in storage. + +Internally, this function is allowed to lazily evaluate, cache +and modify the actual sample to put in ``item`` + +returns the number of info-hashes in the sample. + +This function is called periodically (non-constant frequency). + +For implementers: +Use this functions for expire peers or items or any other +storage cleanup. + +return stats counters for the store + +The DHT storage interface is a pure virtual class that can +be implemented to customize how the data for the DHT is stored. + +The default storage implementation uses three maps in RAM to save +the peers, mutable and immutable items and it's designed to +provide a fast and fully compliant behavior of the BEPs. + +libtorrent comes with one built-in storage implementation: +``dht_default_storage`` (private non-accessible class). Its +constructor function is called dht_default_storage_constructor(). +You should know that if this storage becomes full of DHT items, +the current implementation could degrade in performance. + +constructor for the default DHT storage. The DHT storage is responsible +for maintaining peers and mutable and immutable items announced and +stored/put to the DHT node. + +announce to DHT as a seed + +announce to DHT with the implied-port flag set. This tells the network to use +your source UDP port as your listen port, rather than the one specified in +the message. This may improve the chances of traversing NATs when using uTP. + +Specify the port number for the SSL listen socket in the DHT announce. + +given a byte range ``v`` and an optional byte range ``salt``, a +sequence number, public key ``pk`` (must be 32 bytes) and a secret key +``sk`` (must be 64 bytes), this function produces a signature which +is written into a 64 byte buffer pointed to by ``sig``. The caller +is responsible for allocating the destination buffer that's passed in +as the ``sig`` argument. Typically it would be allocated on the stack. + + +the bootstrap nodes saved from the buckets node + +the bootstrap nodes saved from the IPv6 buckets node + + +This structure helps to store and load the state +of the ``dht_tracker``. +At this moment the library is only a dual stack +implementation of the DHT. See `BEP 32`_ + + +constructor function for the ut_metadata extension. The ut_metadata +extension allows peers to request the .torrent file (or more +specifically the info-dictionary of the .torrent file) from each +other. This is the main building block in making magnet links work. +This extension is enabled by default unless explicitly disabled in +the session constructor. + +This can either be passed in the add_torrent_params::extensions field, or +via torrent_handle::add_extension(). + +constructor function for the smart ban extension. The extension keeps +track of the data peers have sent us for failing pieces and once the +piece completes and passes the hash check bans the peers that turned +out to have sent corrupt data. +This function can either be passed in the add_torrent_params::extensions +field, or via torrent_handle::add_extension(). + +constructor function for the ut_pex extension. The ut_pex +extension allows peers to gossip about their connections, allowing +the swarm stay well connected and peers aware of more peers in the +swarm. This extension is enabled by default unless explicitly disabled in +the session constructor. + +This can either be passed in the add_torrent_params::extensions field, or +via torrent_handle::add_extension(). + diff --git a/docs/projects.rst b/docs/projects.rst new file mode 100644 index 0000000..5390c67 --- /dev/null +++ b/docs/projects.rst @@ -0,0 +1,211 @@ +projects using libtorrent +========================= + +These are some of the public projects that use libtorrent. If you want your +project listed here, let me_ know. + +.. _me: mailto:arvid@libtorrent.org + + +deluge +------ + +`deluge Torrent`_ is a more full-featured yet still lightweight bittorrent +client. It has the ability to automatically resume partial downloads and +background to the system tray. + +.. _`deluge Torrent`: https://deluge-torrent.org/ + +qBittorrent +----------- + +qBittorrent_ is a free and open-source cross-platform bittorrent client written in Qt, that +is available for Linux, macOS and Windows and is released under GPLv2 license. + +It comes with a powerful and easy-to-use graphical interface, as well as an embedded Web interface. +It has a range of features such as an RSS downloader, scheduling rate limits, torrent queueing, +automatic resuming, background downloading, and system tray icon with a password-protected lock. + +Originally written by Christophe Dumez, currently maintained by sledgehammer999. + +.. _qBittorrent: https://www.qbittorrent.org/ + +DownZemAll +---------- + +`DownZemAll!`_ is a mass download manager for Windows, Mac OS X and Linux. It helps +you to select, organize, prioritize and run your downloads in parallel. Based on +the Qt5 framework, DownZemAll! is written in C/C++. It's a free (as in "free +speech" and also as in "free beer") software. Its use is governed by LGPL +License. + +.. _`DownZemAll!`: https://setvisible.github.io/DownZemAll/ + +Tonidoplug +---------- + +Tonidoplug_ is a tiny, low-power, low-cost home server and +NAS device powered by Tonido software that allows you to access +your applications, files, music and media from anywhere. + +.. _Tonidoplug: http://www.tonidoplug.com/ + +Folx +---- + +Folx_ is a torrent client and download manager for macOS. +The Free version of Folx has all the basic functionality of the torrent +client, which allows users to download and create torrent files. +Folx PRO (available for a small fee) features the possibility to search +for torrent files just from Folx interface. So there is no need to +browse through multiple torrent trackers searching for particular file. + +.. _folx: https://www.mac-downloader.com/ + +Miro +---- + +Miro_ is a free application for channels of internet video (also known as +video podcasts and video RSS). Miro is designed to be easy to use and to give +you an elegant fullscreen viewing experience. + +.. _Miro: https://getmiro.com + +MooPolice +--------- + +MooPolice_ is a windows bittorrent client with a unique look. + +.. _MooPolice: https://www.moopolice.de + + +LeechCraft +---------- + +LeechCraft_ LeechCraft is a free open source cross-platform extensible +software, which primary goal is support of file sharing networks and protocols +like HTTP and FTP. + +.. _LeechCraft: https://leechcraft.org/ + +Free download manager +--------------------- + +FDM_ is a powerful, easy-to-use and absolutely free download accelerator and +manager. Moreover, FDM is 100% safe, open-source software distributed under +GPL License. + +.. _FDM: https://www.freedownloadmanager.org/ + +File Centipede +-------------- + +File centipede is an All-In-One internet file upload/download manager, +BitTorrent Client, WebDAV client, FTP client, and SSH client. + +It is designed to be fast, customizable, and user-friendly. + +.. _`File Centipede`: https://filecxx.com/en_US/index.html + +btg +--- + +btg_ is a Unix bittorrent client which is run as a daemon. It has multiple user +interfaces which connects to the daemon. One GUI (gtkmm), one terminal +interface (ncurses) and one web interface (accessible through a web browser). +Written by Michael Wojciechowski and Johan Strom. + +.. _btg: https://sourceforge.net/projects/btg/ + +electric sheep +-------------- + +`electric sheep`_ is a screensaver which collectively generates animations and +lets the users vote which one to live on. + +.. _`electric sheep`: https://electricsheep.org + +Tvitty +------ + +Tvitty_ is a bittorrent client for Vista Media Center, which allows +searching and downloading of torrents directly on your TV. + +.. _Tvitty: https://tvitty.soft112.com/ + +hrktorrent +---------- + +hrktorrent_ hrktorrent is a light console torrent client written in C++. + +.. _hrktorrent: https://50hz.ws/hrktorrent/ + +Arctic Torrent +-------------- + +`Arctic Torrent`_ is a light-weight +bittorrent client for windows. +Written by Cory Nelson. + +.. _`Arctic Torrent`: https://www.softpedia.com/get/Internet/File-Sharing/Arctic-Torrent.shtml + +Bubba +----- + +Bubba_ is a mini-sized server, designed to fit your home better than +an always running PC. Boasting Torrent downloader, DAAP streaming, +Web, E-mail, printer and FTP server etc. + +.. _Bubba: https://excitostore.com/ + +Flush +----- + +Flush_ is a GTK-based BitTorrent client. + +.. _Flush: https://sourceforge.net/projects/flush/ + +Lince +----- + +Lince_ is a bittorrent client using libtorrent to handle bittorrent protocol +and gtkmm for the interface, it has been designed to be a light and full +featured client. + +.. _Lince: https://lincetorrent.sourceforge.net/ + +BitSlug +------- + +BitSlug_ is a macOS cocoa client. + +.. _BitSlug: https://bitslug.sourceforge.net/ + +DelCo +----- + +DelCo_ is a research project at Tampere university of technology, Finland. + +.. _DelCo: http://delco.cs.tut.fi/ + +Torrent2Exe +----------- + +Torrent2Exe_ is a small BitTorrent client. Its basic idea is to +let users download a custom-built EXE program with the torrent file +integrated into it. + +.. _Torrent2Exe: http://torrent2exe.com + +ZyXEL NSA-220 +------------- + +ZyXEL_ NSA220 makes it easy to store, protect and share files between users +on your home network. The built-in DLNA server works with many set top boxes +to allow you to play back music, watch video files, or view photos on your +home theater system, while the built in download manager can automatically +download video and audio podcasts as well as allow you to download bittorrent +files without needing to leave your computer on. + +.. _ZyXEL: https://www.zyxel.com/uk/en/products_services/nsa_220_plus.shtml + diff --git a/docs/python_binding.rst b/docs/python_binding.rst new file mode 100644 index 0000000..ccb5c15 --- /dev/null +++ b/docs/python_binding.rst @@ -0,0 +1,263 @@ +========================= +libtorrent python binding +========================= + +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +building +======== + +libtorrent can be built as a python module. + +The best way to build the python bindings is using ``setup.py``. This invokes +``b2`` under the hood, so you must have all of libtorrent's build dependencies +installed. + +If you just want to build the shared library python extension without python +packaging semantics, you can also invoke ``b2`` directly. + +prerequisites +============= + +Whether building with ``setup.py`` or directly invoking ``b2``, you must +install the build prerequisites on your system: + +1. All `the build prerequisites for the main libtorrent library`__, including + boost libraries and ``b2``, and your building toolchain (``gcc``, visual + studio, etc). +2. Boost.Python, if not otherwise included in your boost installation +3. Python 3.7+. Older versions may work, but are not tested. + +.. __: building.html + +environment variables +--------------------- + +``b2`` is very sensitive to environment variables. At least the following are +required: + +1. ``BOOST_ROOT`` +2. ``BOOST_BUILD_PATH`` + +``b2`` is also known to reference dozens of other environment variables when +detecting toolsets. Keep this in mind if you are building in an isolation +environment like ``tox``. + +building with setup.py +====================== + +By default, ``setup.py`` will invoke ``b2`` to build libtorrent:: + + python setup.py build + +``setup.py`` is a normal ``distutils``-based setup script. + +To install into your python environment:: + + python setup.py install + +To build a binary wheel package:: + + python -m pip install wheel + python setup.py bdist_wheel + +build for a different python version +------------------------------------ + +``setup.py`` will target the running interpreter. To build for different python +versions, you must change how you invoke ``setup.py``:: + + # build for python3.7 + python3.7 setup.py build + # build for python3.7 + python3.7 setup.py build + + +customizing the build +--------------------- + +You can customize the build by passing options to the ``build_ext`` step of +``setup.py`` by passing arguments directly to ``b2`` via ``--b2-args=``:: + + python setup.py build_ext --b2-args="toolset=msvc-14.2 linkflags=-L../../src/.libs" + +For a full list of ``b2`` build options, see `libtorrent build features`_. + +.. _`libtorrent build features`: building.html#build-features + +Here, it's important to note that ``build_ext`` has no "memory" of the build +config and arguments you passed to it before. This is *different* from the way +``distutils`` normally works. Consider:: + + python setup.py build_ext --b2-args="optimization=space" + # the following will build with DEFAULT optimization + python setup.py install + +In order to customize the build *and* run other steps like installation, you +should run the steps inline with ``build_ext``:: + + python setup.py build_ext --b2-args="optimization=space" install + + +building with b2 +================ + +You will need to update your ``user-config.jam`` so ``b2`` can find your python +installation. + +``b2`` has some auto-detection capabilities. You may be able to do just this:: + + using python : 3.7 ; + +However you may need to specify full paths. On windows, it make look like +this:: + + using python : 3.7 : C:/Users//AppData/Local/Programs/Python/Python36 : C:/Users//AppData/Local/Programs/Python/Python36/include : C:/Users//AppData/Local/Programs/Python/Python36/libs ; + +Or on Linux, like this:: + + using python : 3.7 : /usr/bin/python3.7 : /usr/include/python3.7 : /usr/lib/python3.7 ; + +Note that ``b2``'s python path detection is known to only work for global +python installations. It is known to be broken for virtualenvs or ``pyenv``. If +you are using ``pyenv`` to manage your python versions, you must specify full +include and library paths yourself. + +invoking b2 +----------- + +Build the bindings like so:: + + cd bindings/python + b2 release python=3.7 address-model=64 + +Note that ``address-model`` should match the python installation you are +building for. + +For other build features, see `libtorrent build options`_. + +.. _`libtorrent build options`: building.html#build-features + + +static linking +-------------- + +A python module is a shared library. Specifying ``link=static`` when building +the binding won't work, as it would try to produce a static library. + +Instead, control whether the libtorrent main library or boost is linked +statically with ``libtorrent-link=static`` and ``boost-link=static`` +respectively. + +By default both are built and linked as shared libraries. + +Building and linking boost as static library is only possibly by building it +from source. Specify the ``BOOST_ROOT`` environment variable to point to the +root directory of the boost source distribution. + +For example, to build a self-contained python module:: + + b2 release python=3.7 libtorrent-link=static boost-link=static + +helper targets +-------------- + +There are some targets for placing the build artifact in a helpful location:: + + $ b2 release python=3.7 stage_module stage_dependencies + +This will produce a ``libtorrent`` python module in the current directory (file +name extension depends on operating system). The libraries the python module depends +on will be copied into ``./dependencies``. + +To install the python module, build it with the following command:: + + b2 release python=3.7 install_module + +By default the module will be installed to the python user site. This can be +changed with the ``python-install-scope`` feature. The valid values are ``user`` +(default) and ``system``. e.g.:: + + b2 release python=3.7 install_module python-install-scope=system + +To specify a custom installation path for the python module, specify the desired +path with the ``python-install-path`` feature. e.g.:: + + b2 release python=3.7 install_module python-install-path=/home/foobar/python-site/ + +using libtorrent in python +========================== + +The python interface is nearly identical to the C++ interface. Please refer to +the `library reference`_. The main differences are: + +asio::tcp::endpoint + The endpoint type is represented as a tuple of a string (as the address) and an int for + the port number. E.g. ``("127.0.0.1", 6881)`` represents the localhost port 6881. + +lt::time_duration + The time duration is represented as a number of seconds in a regular integer. + +The following functions takes a reference to a container that is filled with +entries by the function. The python equivalent of these functions instead returns +a list of entries. + +* torrent_handle::get_peer_info +* torrent_handle::file_progress +* torrent_handle::get_download_queue +* torrent_handle::piece_availability + +``create_torrent::add_node()`` takes two arguments, one string and one integer, +instead of a pair. The string is the address and the integer is the port. + +``session::apply_settings()`` accepts a dictionary with keys matching the names +of settings in settings_pack. +When calling ``apply_settings``, the dictionary does not need to have every settings set, +keys that are not present are not updated. + +To get a python dictionary of the settings, call ``session::get_settings``. + +.. _`library reference`: reference.html + +Retrieving session statistics in Python is more convenient than that in C++. The +statistics are stored as an array in ``session_stats_alert``, which will be +posted after calling ``post_session_stats()`` in the ``session`` object. In +order to interpret the statistics array, in C++ it is required to call +``session_stats_metrics()`` to get the indices of these metrics, while in Python +it can be done using ``session_stats_alert.values["NAME_OF_METRIC"]``, where +``NAME_OF_METRIC`` is the name of a metric. + +set_alert_notify +================ + +The ``set_alert_notify()`` function is not compatible with python. Since it +requires locking the GIL from within the libtorrent thread, to call the callback, +it can cause a deadlock with the main thread. + +Instead, use the python-specific ``set_alert_fd()`` which takes a file descriptor +that will have 1 byte written to it to notify the client that there are new +alerts to be popped. + +The file descriptor should be set to non-blocking mode. If writing to the +file/sending to the socket blocks, libtorrent's internal thread will stall. + +This can be used with ``socket.socketpair()``, for example. The file descriptor +is what ``fileno()`` returns on a socket. + +Example +======= + +For an example python program, see ``client.py`` in the ``bindings/python`` +directory. + +A very simple example usage of the module would be something like this: + +.. include:: ../bindings/python/simple_client.py + :code: python + :tab-width: 2 + :start-after: from __future__ import print_function + diff --git a/docs/security-audit.rst b/docs/security-audit.rst new file mode 100644 index 0000000..24768af --- /dev/null +++ b/docs/security-audit.rst @@ -0,0 +1,491 @@ +============================ +Security audit of libtorrent +============================ + +:Author: Arvid Norberg, arvid@libtorrent.org + +In the 4th quarter of 2020 `Mozilla Open Source Support Awards`__ commissioned a +security audit of libtorrent, to be performed by `include security`_. + +__ https://www.mozilla.org/en-US/moss/ +.. _`include security`: https://includesecurity.com/ + +The full report from the audit can be found here_. + +.. _here: 2020\ Q4\ Mozilla\ Libtorrent\ Report\ Public\ Report.pdf + +This document discusses the issues raised by the report as well as describes the +changes made to libtorrent in response to it. These changes were included in +libtorrent version 1.2.12 and version 2.0.2. + +Comments on this document are welcome through any of these means: + +* email them to ``arvid@libtorrent.org`` +* email to libtorrent `mailing list`_ +* an issue on github_. + +.. _`mailing list`: https://sourceforge.net/projects/libtorrent/lists/libtorrent-discuss +.. _github: https://github.com/arvidn/libtorrent/issues + +.. contents:: issues brought up in the report + +F1: Server-Side Request Forgery (SSRF) +====================================== + +For background, see `OWASP definition of SSRF`__. + +__ https://owasp.org/www-community/attacks/Server_Side_Request_Forgery + +Running a tracker on the local network is an established use case for +BitTorrent (here__). Filtering all tracker requests to the local network is not feasible. +Running a tracker on the loopback device would seem to only make sense for +testing. + +__ https://github.com/AVBIT/retracker_local + +The SSRF issue is not limited to tracker URLs, but also applies to web seeds. A +web seed can be embedded in a .torrent file as well as included in a magnet +link. + +The report says: + + If user-controllable URLs must be requested then sanitizing them in a manner + similar to the SafeCurl library is recommended (see the link in the reference + section). + +The `SafeCurl library`__, as I understand is, sanitizes URLs based on include- +and exclude lists of host names, IP addresses, ports, schemes. + +__ https://github.com/wkcaj/safecurl/blob/master/src/fin1te/SafeCurl/Url.php + +tracker and web seed protocols +------------------------------ + +Tracker URLs can be arbitrary URLs that libtorrent appends certain query string +parameters to (like ``&info_hash=`` etc.). The path component of a tracker URL +is typically not relevant, and most trackers follow the convention of using +``/announce``. + +A web seed for a multi-file torrent cannot include any query string arguments +and libtorrent will append the path to the file that's being requested. However, +the response from the web seed can *redirect* to any arbitrary URL, including on +the local network. A web seed for a single-file torrent can be any arbitrary URL. + +Web seed HTTP requests will almost always be a range request (unless the file is +so small to fit in one or a few pieces). + +What heuristics and restrictions could libtorrent implement to mitigate attacks? + +Both trackers and web seeds only use HTTP ``GET`` request, i.e. no ``POST`` for +example. This ought to protect certain APIs that mutate state. + +The examples in the OWASP article are: + +Cloud server meta-data +---------------------- + + Cloud services such as AWS provide a REST interface on + ``http://169.254.169.254/`` where important configuration and sometimes even + authentication keys can be extracted + +The response from a REST API would have to be compatible with the +BitTorrent tracker protocol, which is a bencoded structure with specific keys +being mandatory (the protocol is defined here__, with amendments here__, +here__ and here__). + +__ https://www.bittorrent.org/beps/bep_0003.html#trackers +__ https://www.bittorrent.org/beps/bep_0023.html +__ https://www.bittorrent.org/beps/bep_0007.html +__ https://www.bittorrent.org/beps/bep_0048.html + +A tracker response that doesn't match this protocol will be ignored by libtorrent. +The response will not be published and made available anywhere, including the logs. +Therefore it's not likely there would be a way to *extract* data from a REST API +via a tracker request. + +Database HTTP interfaces +------------------------ + + NoSQL database such as MongoDB provide REST interfaces on HTTP ports. If the + database is expected to only be available to internally, authentication may + be disabled and the attacker can extract data + +Since libtorrent doesn't make the response from a tracker request available to +anybody, especially not if it's not a valid BitTorrent tracker response, it's +not likely data can be extracted via such tracker URL. See previous section for +details. + +Internal REST interfaces +------------------------ + +libtorrent can definitely hit a REST interface and may affect configuration +changes in other software that's installed on the local machine. This is +assuming that the software does not use any authentication other than checking +the source IP being the localhost. + +As mentioned earlier, extracting data from a REST API via a tracker URL is not +likely to be possible. + +It is established practice to include arbitrary URL query parameters in tracker +URLs, and clients amend them with the query parameters required by the tracker +protocol. This makes it difficult to sanitize the query string. + +One way to mitigate hitting REST APIs on local host is to require that tracker +URLs, for local host specifically, use the request path ``/announce``. This is +the convention for bittorrent trackers. + +Web seeds that resolve to a local network address are not allowed to have query +string parameters. + +This SSRF mitigation was implemented for trackers in `#5303`__ and for web seeds in `#5319`__. + +__ https://github.com/arvidn/libtorrent/pull/5303 +__ https://github.com/arvidn/libtorrent/pull/5319 + +Web Seeds that resolve to a *global* address (i.e. not loopback, local network +or multicast address) are not allowed to redirect to a non-global IP. This +mitigation was implemented in `#5846`__, for libtorrent-2.0.3. + +__ https://github.com/arvidn/libtorrent/pull/5846 + +Files +----- + + The attacker may be able to read files using ```` URIs + + +libtorrent only supports ``http``, ``https`` and ``udp`` protocol schemes, and +will reject any other tracker URL. Specifically, libtorrent does not support +the ``file://`` URL scheme. + +Additionally, `#5346`__ implements checks for tracker URLs that include query +string arguments that are supposed to be added by clients. + +__ https://github.com/arvidn/libtorrent/pull/5346 + +F2: Compile Options Can Remove Assert Security Validation +========================================================= + +The comments have been addressed in `#5308`__. The changes include: + +__ https://github.com/arvidn/libtorrent/pull/5308 + +* use ``span`` to simplify updates of pointer + length +* use ``span`` for (immutable) write buffers, to improve const + correctness and avoid a ``const_cast`` +* introduce additional sanity checks that no buffer lengths are < 0 +* introduce additional check to ensure buffer lengths fit in unsigned 16 bit + field (in the case where it's stored in one) +* generally reduce signed <-> unsigned casts + +F3: Confidential and Security Relevant Information Stored in Logs +================================================================= + +The secret keys for protocol encryption are not particularly sensitive, since +it's primarily an obfuscation feature. However, I have never had to use these +keys for debugging, so they don't have much value in the log anyway. + +Addressed in `#5299`__. + +__ https://github.com/arvidn/libtorrent/pull/5299 + +F4: Pseudo Random Number Generator Is Vulnerable to Prediction Attack +===================================================================== + +These are the places ``random_bytes()``, ``random()`` and ``random_shuffle()`` +are used in libtorrent. The "crypto" column indicates whether the random number +is sensitive and must be hard to predict, i.e. have high entropy. + +.. list-table:: + :widths: auto + :header-rows: 1 + + * - crypto + - Use + - Description + * - **Yes** + - PCP nonce + - generating a nonce for PCP (Port Control Protocol). The `PCP RFC section 11.2`__ + references `RFC 4086 Randomness Requirements for Security`__ for + the nonce generation. + + This was fixed. + + __ https://tools.ietf.org/html/rfc6887#section-11.2 + __ https://tools.ietf.org/html/rfc4086 + * - **Yes** + - DHT ed25519 keys + - used for kademlia mutable put feature. These keys are sensitive an + should use an appropriate entropy source. This is not done as part of + normal libtorrent operations, it's a utility function a client using the mutable + PUT-feature can call. This functionality is exposed in the + ``ed25519_create_seed()`` function. + + This was fixed. + * - **Maybe** + - DHT write-token + - The DHT maintains a secret 32 bit number which is updated every 5 + minutes to a new random number. The secret from the last 5 minute period + is also remembered. In responses to ``get`` and ``get_peers`` messages a + *write token* is generated and included. The write token is the first 32 + bits of a SHA-1 of the source IP address, the current secret and the + info_hash. ``put`` and ``announce_peer`` requests are ignored if the + write token is invalid given the current or the last secret. This is + like a SYN-cookie. + + This was changed to use cryptographic random numbers. + * - **Maybe** + - DHT transaction ID + - Each DHT request that is sent to a node includes a 16 bit transaction ID + that must be returned in the response. This is used to map responses to + the correct request (required when making multiple requests to the same + IP), but also to make it harder for a 3rd party to spoof the source IP + and fake a response. Presumably the fact that there are only 65536 + different transaction IDs would be a problem before someone guesses the + random number. Additionally, a request is only valid for a few tens of + seconds, which further mitigates spoofed responses. + + This has been left using pseudo random numbers. + * - **Maybe** + - uTP sequence numbers + - When connecting a uTP socket, the initial sequence number is chosen at + random. + + This has been left using pseudo random numbers. + * - No + - protocol encryption (obfuscation) + - both key generation for DH handshake as well as random + padding ahead of handshake. The protocol encryption feature + is not intended to provide any authentication or confidentiality. + * - No + - i2p session-id + - generation of the session ID, not key generation. All crypto, + including key generation is done by the i2p daemon implementing + the SAM bridge. + * - No + - DHT node-id + - The node ID does not need to be hard to guess, just uniformly + distributed. + * - No + - DHT node-id fingerprint + - Used to identify announces to fake info-hashes. More info here__. + + __ https://blog.libtorrent.org/2014/11/dht-routing-table-maintenance/ + * - No + - DHT peer storage + - When returning peers from peer storage, in response to a DHT + ``get_peers`` request, we pick *n* of *m* random peers. + * - No + - peer-id + - In bittorrent, each peer generates a random peer-id used in interactions + with other peers as well as HTTP(S) trackers. The peer-id is not secret + and does not need to be hard to guess. In fact, for each peer libtorrent + connects to, it generates a different peer-id. Additionally, each torrent + has a unique peer-id that's advertised to trackers. Trackers need a + consistent peer-id for its book keeping. + * - No + - ip_voter + - The ip_voter maintains a list of possible external IP addresses, based + on how many peer interactions we've seen telling us that's our external + IP as observed by them. Knowing our external IP is not critical, it's + primarily used to generate our DHT node ID according to this__. + + __ http://libtorrent.org/dht_sec.html + + The ip_voter uses ``random()`` to probabilistically drop a record of a + possible external IP, if there are too many. + * - No + - local service discovery + - In order to ignore our own service discovery messages sent on a + multi-cast group, we include a "cookie". If we see our own cookie, we + ignore the message. The cookie is generated by ``random()``. + * - No + - piece picker + - The order pieces are picked in is rarest first. Pieces of the same + rarity are picked in random order, using ``random()``. + * - No + - smart-ban + - If a piece fails the hash check, we may not know which peer sent the + corrupt data. The smart ban function will record the hashes of all blocks + of the failed piece. Once the piece passes, it can compare the passing + blocks against the failing one, identifying exactly which peer sent corrupt + data. This is a property of how bittorrent *checks* data at the piece + level, but downloads smaller parts (called "blocks") from potentially + different peers. + + In earlier version of libtorrent, the block hash would use CRC32, and a + secret salt to prevent trivial exploiting by malicious peers. This is no + longer the case, smart-ban uses SHA-1 now, so there is no need for the salt. + + It was removed in `#5295`__. + + __ https://github.com/arvidn/libtorrent/pull/5295 + * - No + - peer-list pruning + - When the peer list has too many peers in it, random low quality peers + are pruned. + * - No + - peer-list duplicate peer + - When receiving a connection from an IP we're already connected to, the + connection to keep and which one to disconnect is based on the local and + remote port numbers. If the ports are the same, one of the two connections + are closed randomly. + * - No + - UPnP external port + - When the external port of a mapping conflicts with an existing map, the + port mapping is re-attempted with a random external port. + * - No + - ut_metadata re-request timeout + - When a peer responds to a metadata request with "don't have", we delay + randomly between 20 - 70 seconds before re-requesting. + * - No + - web seeds + - Web seeds are shuffled, to attempt connecting to them in random order + * - No + - trackers + - Trackers within the same tier are shuffled, to try them in random order + (for load balancing) + * - No + - resume data peers + - When saving resume data and we have more than 100 peers, once "high + quality peers" have been saved, pick low quality peers at random to save. + * - No + - share mode seeds + - In share mode, where libtorrent attempts to maximize its upload to + download ratio, if we're connected to too many seeds, some random seeds + are disconnected. + * - No + - share mode pick + - In share mode, when more than one piece has the lowest availability, one + of them is picked at random + * - No + - http_connection endpoints + - After a successful hostname lookup, the endpoints are randomized to try + them in an arbitrary order, for load balancing. + * - No + - super seeding piece picking + - In Super seeding mode, the rarest piece is selected for upload. If + there's a tie, a piece is chosen at random. + * - No + - UDP listen socket + - When using a proxy, but not connecting peer via the proxy, the local UDP + socket, used for uTP and DHT traffic will bind to the listen socket of + the first configured listen interface. If there is no listen interface + configured, a random port is chosen. + * - No + - bind outgoing uTP socket + - When bind-outgoing-sockets is enabled, uTP sockets are bound to the + listen interface matching the target IP. If there is no match, an + interface is picked at random to bind the outgoing socket to. + * - No + - uTP send ID + - uTP connections are assigned send ID, to allow multiple connections to + the same IP. Similar to port number, but all uTP connections run over a + single UDP socket. + +The following issues were addressed: + +* the existing ``random_bytes()`` function was made to unconditionally produce + pseudo random bytes. +* increase amount of entropy to seed the pseudo random number generator. +* a new function ``crypto_random_bytes()`` was added which unconditionally + use a strong entropy source. +* If no specialized API is available for high-entropy random numbers is + available (like ``libcrypto`` or CryptoAPI on windows) random numbers are + pulled from ``/dev/urandom``. +* The PCP nonce was changed to use ``crypto_random_bytes()`` +* The ed25519 key seed function was changed to use ``crypto_random_bytes()`` + +Addressed in `#5298`__. + +__ https://github.com/arvidn/libtorrent/pull/5298 + +F5: Potential Null Pointer Dereference Issues +============================================= + +This was fundamentally caused by the boost.pool default allocator using ``new +(std::nothrow)``, rather than plain (throwing) ``new``. The code using the pool +added to the confusion by checking for a ``nullptr`` return value, but further +up the call chain that check was not made. The fix was to remove the check for +``nullptr`` and replace the boost.pool allocator to throw ``std::bad_alloc`` on +memory exhaustion. + +Addressed in `#5293`__. + +__ https://github.com/arvidn/libtorrent/pull/5293 + +F6: Integer Overflow +==================== + +This was a bug in the fuzzer itself, not in the production code (as far as I +could find). The parse_int fuzzer used an uninitialized variable. + +Addressed in `#5292`__. + +__ https://github.com/arvidn/libtorrent/pull/5292 + +F7: Magnet URIs Allow IDNA Domain Names +======================================= + +My understanding of this attack is that a tracker hostname could be crafted to +look like a well known host, but in fact be a different host, by using +look-alike unicode characters in the hostname. + +For example, the well-known tracker ``http://bt1.archive.org:6969/announce`` +could be spoofed by using ``bt1.archive.org`` (the ``e`` at the end is really +`U+ff45`__). + +__ https://unicode-table.com/en/FF45/ + +The issue of trusting trackers goes beyond tracker host names in magnet links. +Normal .torrent files also contain tracker URLs, and they could also use +misleading tracker host names. However, this highlights a more fundamental issue +that libtorrent does not provide an API for clients to vet trackers before +announcing to them. libtorrent provides an IP filter that will block announcing +to trackers, but not the URLs or host names directly. + +Having an ability to vet trackers before using them would also mitigate the +`F1: Server-Side Request Forgery (SSRF)`_. + +This issue also goes beyond trackers. Web seeds are also URLs embedded in +.torrent files or magnet links which libtorrent will make requests to. + +These are the changes I'm making to mitigate this issue: + +* enable ``validate_https_trackers`` by default. `#5314`__. The name of this + setting is misleading. It does not only affect trackers, but also web seeds. +* Support loading the system certificate store on windows, to authenticate + trackers with, `#5313`__. +* add an option to allow IDNA domain names, and disable it by default. This + applies to both trackers and web seeds. `#5316`__. + +__ https://github.com/arvidn/libtorrent/pull/5314 +__ https://github.com/arvidn/libtorrent/pull/5313 +__ https://github.com/arvidn/libtorrent/pull/5316 + + +I1: Additional Documentation and Automation +=========================================== + +Addressed in: + +* `#5337`__. + +__ https://github.com/arvidn/libtorrent/pull/5337 + +I2: Automated Fuzzer Generation +=============================== + +No effort has been put into generating fuzzers with FuzzGen_, but it's an +intriguing project I hope to have time to put some effort towards in the future. + +.. _FuzzGen: https://github.com/HexHive/FuzzGen + +I3: Type Confusion and Integer Overflow Improvements +==================================================== + +Addressed in: + +* `#5308`__. + +__ https://github.com/arvidn/libtorrent/pull/5308 diff --git a/docs/settings-ref.rst b/docs/settings-ref.rst new file mode 100644 index 0000000..2154fa3 --- /dev/null +++ b/docs/settings-ref.rst @@ -0,0 +1,3546 @@ +.. _user_agent: + +.. raw:: html + +
    + ++------------+--------+-------------+ +| name | type | default | ++============+========+=============+ +| user_agent | string | libtorrent/ | ++------------+--------+-------------+ + +this is the client identification to the tracker. The recommended +format of this string is: "client-name/client-version +libtorrent/libtorrent-version". This name will not only be used when +making HTTP requests, but also when sending extended headers to +peers that support that extension. It may not contain \r or \n + +.. _announce_ip: + +.. raw:: html + + + ++-------------+--------+---------+ +| name | type | default | ++=============+========+=========+ +| announce_ip | string | nullptr | ++-------------+--------+---------+ + +``announce_ip`` is the ip address passed along to trackers as the +``&ip=`` parameter. If left as the default, that parameter is +omitted. + +.. note:: + This setting is only meant for very special cases where a seed is + running on the same host as the tracker, and the tracker accepts + the IP parameter (which normal trackers don't). Do not set this + option unless you also control the tracker. + +.. _handshake_client_version: + +.. raw:: html + + + ++--------------------------+--------+---------+ +| name | type | default | ++==========================+========+=========+ +| handshake_client_version | string | nullptr | ++--------------------------+--------+---------+ + +this is the client name and version identifier sent to peers in the +handshake message. If this is an empty string, the user_agent is +used instead. This string must be a UTF-8 encoded unicode string. + +.. _outgoing_interfaces: + +.. raw:: html + + + ++---------------------+--------+---------+ +| name | type | default | ++=====================+========+=========+ +| outgoing_interfaces | string | | ++---------------------+--------+---------+ + +This controls which IP address outgoing TCP peer connections are bound +to, in addition to controlling whether such connections are also +bound to a specific network interface/adapter (*bind-to-device*). + +This string is a comma-separated list of IP addresses and +interface names. An empty string will not bind TCP sockets to a +device, and let the network stack assign the local address. + +A list of names will be used to bind outgoing TCP sockets in a +round-robin fashion. An IP address will simply be used to `bind()` +the socket. An interface name will attempt to bind the socket to +that interface. If that fails, or is unsupported, one of the IP +addresses configured for that interface is used to `bind()` the +socket to. If the interface or adapter doesn't exist, the +outgoing peer connection will fail with an error message suggesting +the device cannot be found. Adapter names on Unix systems are of +the form "eth0", "eth1", "tun0", etc. This may be useful for +clients that are multi-homed. Binding an outgoing connection to a +local IP does not necessarily make the connection via the +associated NIC/Adapter. + +When outgoing interfaces are specified, incoming connections or +packets sent to a local interface or IP that's *not* in this list +will be rejected with a `peer_blocked_alert`__ with +``invalid_local_interface`` as the reason. + +Note that these are just interface/adapter names or IP addresses. +There are no ports specified in this list. IPv6 addresses without +port should be specified without enclosing ``[``, ``]``. + +.. _listen_interfaces: + +.. raw:: html + + + ++-------------------+--------+------------------------+ +| name | type | default | ++===================+========+========================+ +| listen_interfaces | string | 0.0.0.0:6881,[::]:6881 | ++-------------------+--------+------------------------+ + +a comma-separated list of (IP or device name, port) pairs. These are +the listen ports that will be opened for accepting incoming uTP and +TCP peer connections. These are also used for *outgoing* uTP and UDP +tracker connections and DHT nodes. + +It is possible to listen on multiple interfaces and +multiple ports. Binding to port 0 will make the operating system +pick the port. + +.. note:: + There are reasons to stick to the same port across sessions, + which would mean only using port 0 on the first start, and + recording the port that was picked for subsequent startups. + Trackers, the DHT and other peers will remember the port they see + you use and hand that port out to other peers trying to connect + to you, as well as trying to connect to you themselves. + +A port that has an "s" suffix will accept SSL peer connections. (note +that SSL sockets are only available in builds with SSL support) + +A port that has an "l" suffix will be considered a local network. +i.e. it's assumed to only be able to reach hosts in the same local +network as the IP address (based on the netmask associated with the +IP, queried from the operating system). + +if binding fails, the `listen_failed_alert`__ is posted. Once a +socket binding succeeds (if it does), the `listen_succeeded_alert`__ +is posted. There may be multiple failures before a success. + +If a device name that does not exist is configured, no listen +socket will be opened for that interface. If this is the only +interface configured, it will be as if no listen ports are +configured. + +If no listen ports are configured (e.g. listen_interfaces is an +empty string), networking will be disabled. No DHT will start, no +outgoing uTP or tracker connections will be made. No incoming TCP +or uTP connections will be accepted. (outgoing TCP connections +will still be possible, depending on +`settings_pack::outgoing_interfaces`__). + +For example: +``[::1]:8888`` - will only accept connections on the IPv6 loopback +address on port 8888. + +``eth0:4444,eth1:4444`` - will accept connections on port 4444 on +any IP address bound to device ``eth0`` or ``eth1``. + +``[::]:0s`` - will accept SSL connections on a port chosen by the +OS. And not accept non-SSL connections at all. + +``0.0.0.0:6881,[::]:6881`` - binds to all interfaces on port 6881. + +``10.0.1.13:6881l`` - binds to the local IP address, port 6881, but +only allow talking to peers on the same local network. The netmask +is queried from the operating system. Interfaces marked ``l`` are +not announced to trackers, unless the tracker is also on the same +local network. + +Windows OS network adapter device name must be specified with GUID. +It can be obtained from "netsh lan show interfaces" command output. +GUID must be uppercased string embraced in curly brackets. +``{E4F0B674-0DFC-48BB-98A5-2AA730BDB6D6}:7777`` - will accept +connections on port 7777 on adapter with this GUID. + +For more information, see the `Multi-homed hosts`_ section. + +.. _`Multi-homed hosts`: manual-ref.html#multi-homed-hosts + +.. _proxy_hostname: + +.. raw:: html + + + ++----------------+--------+---------+ +| name | type | default | ++================+========+=========+ +| proxy_hostname | string | | ++----------------+--------+---------+ + +when using a proxy, this is the hostname where the proxy is running +see proxy_type. Note that when using a proxy, the +`settings_pack::listen_interfaces`__ setting is overridden and only a +single interface is created, just to contact the proxy. This +means a proxy cannot be combined with SSL torrents or multiple +listen interfaces. This proxy listen interface will not accept +incoming TCP connections, will not map ports with any gateway and +will not enable local service discovery. All traffic is supposed +to be channeled through the proxy. + +.. _proxy_username: + +.. _proxy_password: + +.. raw:: html + + + + ++----------------+--------+---------+ +| name | type | default | ++================+========+=========+ +| proxy_username | string | | ++----------------+--------+---------+ +| proxy_password | string | | ++----------------+--------+---------+ + +when using a proxy, these are the credentials (if any) to use when +connecting to it. see proxy_type + +.. _i2p_hostname: + +.. raw:: html + + + ++--------------+--------+---------+ +| name | type | default | ++==============+========+=========+ +| i2p_hostname | string | | ++--------------+--------+---------+ + +sets the i2p_ SAM bridge to connect to. set the port with the +``i2p_port`` setting. Unless this is set, i2p torrents are not +supported. This setting is separate from the other proxy settings +since i2p torrents and their peers are orthogonal. You can have +i2p peers as well as regular peers via a proxy. + +.. _i2p: http://www.i2p2.de + +.. _peer_fingerprint: + +.. raw:: html + + + ++------------------+--------+----------+ +| name | type | default | ++==================+========+==========+ +| peer_fingerprint | string | -LT20B0- | ++------------------+--------+----------+ + +this is the fingerprint for the client. It will be used as the +prefix to the peer_id. If this is 20 bytes (or longer) it will be +truncated to 20 bytes and used as the entire peer-id + +There is a utility function, `generate_fingerprint()`__ that can be used +to generate a standard client peer ID fingerprint prefix. + +.. _dht_bootstrap_nodes: + +.. raw:: html + + + ++---------------------+--------+--------------------------+ +| name | type | default | ++=====================+========+==========================+ +| dht_bootstrap_nodes | string | dht.libtorrent.org:25401 | ++---------------------+--------+--------------------------+ + +This is a comma-separated list of IP port-pairs. They will be added +to the DHT node (if it's enabled) as back-up nodes in case we don't +know of any. + +Changing these after the DHT has been started may not have any +effect until the DHT is restarted. +Here are some other bootstrap nodes that may work: +``router.bittorrent.com:6881``, +``dht.transmissionbt.com:6881`` +``router.bt.ouinet.work:6881``, + +.. _allow_multiple_connections_per_ip: + +.. raw:: html + + + ++-----------------------------------+------+---------+ +| name | type | default | ++===================================+======+=========+ +| allow_multiple_connections_per_ip | bool | false | ++-----------------------------------+------+---------+ + +determines if connections from the same IP address as existing +connections should be rejected or not. Rejecting multiple connections +from the same IP address will prevent abusive +behavior by peers. The logic for determining whether connections are +to the same peer is more complicated with this enabled, and more +likely to fail in some edge cases. It is not recommended to enable +this feature. + +.. _send_redundant_have: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| send_redundant_have | bool | true | ++---------------------+------+---------+ + +``send_redundant_have`` controls if have messages will be sent to +peers that already have the piece. This is typically not necessary, +but it might be necessary for collecting statistics in some cases. + +.. _use_dht_as_fallback: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| use_dht_as_fallback | bool | false | ++---------------------+------+---------+ + +``use_dht_as_fallback`` determines how the DHT is used. If this is +true, the DHT will only be used for torrents where all trackers in +its tracker list has failed. Either by an explicit error message or +a time out. If this is false, the DHT is used regardless of if the +trackers fail or not. + +.. _upnp_ignore_nonrouters: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| upnp_ignore_nonrouters | bool | false | ++------------------------+------+---------+ + +``upnp_ignore_nonrouters`` indicates whether or not the UPnP +implementation should ignore any broadcast response from a device +whose address is not on our subnet. i.e. +it's a way to not talk to other people's routers by mistake. + +.. _use_parole_mode: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| use_parole_mode | bool | true | ++-----------------+------+---------+ + +``use_parole_mode`` specifies if parole mode should be used. Parole +mode means that peers that participate in pieces that fail the hash +check are put in a mode where they are only allowed to download +whole pieces. If the whole piece a peer in parole mode fails the +hash check, it is banned. If a peer participates in a piece that +passes the hash check, it is taken out of parole mode. + +.. _auto_manage_prefer_seeds: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| auto_manage_prefer_seeds | bool | false | ++--------------------------+------+---------+ + +if true, prefer seeding torrents when determining which torrents to give +active slots to. If false, give preference to downloading torrents + +.. _dont_count_slow_torrents: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| dont_count_slow_torrents | bool | true | ++--------------------------+------+---------+ + +if ``dont_count_slow_torrents`` is true, torrents without any +payload transfers are not subject to the ``active_seeds`` and +``active_downloads`` limits. This is intended to make it more +likely to utilize all available bandwidth, and avoid having +torrents that don't transfer anything block the active slots. + +.. _close_redundant_connections: + +.. raw:: html + + + ++-----------------------------+------+---------+ +| name | type | default | ++=============================+======+=========+ +| close_redundant_connections | bool | true | ++-----------------------------+------+---------+ + +``close_redundant_connections`` specifies whether libtorrent should +close connections where both ends have no utility in keeping the +connection open. For instance if both ends have completed their +downloads, there's no point in keeping it open. + +.. _prioritize_partial_pieces: + +.. raw:: html + + + ++---------------------------+------+---------+ +| name | type | default | ++===========================+======+=========+ +| prioritize_partial_pieces | bool | false | ++---------------------------+------+---------+ + +If ``prioritize_partial_pieces`` is true, partial pieces are picked +before pieces that are more rare. If false, rare pieces are always +prioritized, unless the number of partial pieces is growing out of +proportion. + +.. _rate_limit_ip_overhead: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| rate_limit_ip_overhead | bool | true | ++------------------------+------+---------+ + +if set to true, the estimated TCP/IP overhead is drained from the +rate limiters, to avoid exceeding the limits with the total traffic + +.. _announce_to_all_tiers: + +.. _announce_to_all_trackers: + +.. raw:: html + + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| announce_to_all_tiers | bool | false | ++--------------------------+------+---------+ +| announce_to_all_trackers | bool | false | ++--------------------------+------+---------+ + +``announce_to_all_trackers`` controls how multi tracker torrents +are treated. If this is set to true, all trackers in the same tier +are announced to in parallel. If all trackers in tier 0 fails, all +trackers in tier 1 are announced as well. If it's set to false, the +behavior is as defined by the multi tracker specification. + +``announce_to_all_tiers`` also controls how multi tracker torrents +are treated. When this is set to true, one tracker from each tier +is announced to. This is the uTorrent behavior. To be compliant +with the Multi-tracker specification, set it to false. + +.. _prefer_udp_trackers: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| prefer_udp_trackers | bool | true | ++---------------------+------+---------+ + +``prefer_udp_trackers``: true means that trackers +may be rearranged in a way that udp trackers are always tried +before http trackers for the same hostname. Setting this to false +means that the tracker's tier is respected and there's no +preference of one protocol over another. + +.. _disable_hash_checks: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| disable_hash_checks | bool | false | ++---------------------+------+---------+ + +when set to true, all data downloaded from peers will be assumed to +be correct, and not tested to match the hashes in the torrent this +is only useful for simulation and testing purposes (typically +combined with disabled_storage) + +.. _allow_i2p_mixed: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| allow_i2p_mixed | bool | false | ++-----------------+------+---------+ + +if this is true, i2p torrents are allowed to also get peers from +other sources than the tracker, and connect to regular IPs, not +providing any anonymization. This may be useful if the user is not +interested in the anonymization of i2p, but still wants to be able +to connect to i2p peers. + +.. _no_atime_storage: + +.. raw:: html + + + ++------------------+------+---------+ +| name | type | default | ++==================+======+=========+ +| no_atime_storage | bool | true | ++------------------+------+---------+ + +``no_atime_storage`` this is a Linux-only option and passes in the +``O_NOATIME`` to ``open()`` when opening files. This may lead to +some disk performance improvements. + +.. _incoming_starts_queued_torrents: + +.. raw:: html + + + ++---------------------------------+------+---------+ +| name | type | default | ++=================================+======+=========+ +| incoming_starts_queued_torrents | bool | false | ++---------------------------------+------+---------+ + +``incoming_starts_queued_torrents``. If a torrent +has been paused by the auto managed feature in libtorrent, i.e. the +torrent is paused and auto managed, this feature affects whether or +not it is automatically started on an incoming connection. The main +reason to queue torrents, is not to make them unavailable, but to +save on the overhead of announcing to the trackers, the DHT and to +avoid spreading one's unchoke slots too thin. If a peer managed to +find us, even though we're no in the torrent anymore, this setting +can make us start the torrent and serve it. + +.. _report_true_downloaded: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| report_true_downloaded | bool | false | ++------------------------+------+---------+ + +when set to true, the downloaded counter sent to trackers will +include the actual number of payload bytes downloaded including +redundant bytes. If set to false, it will not include any redundancy +bytes + +.. _strict_end_game_mode: + +.. raw:: html + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| strict_end_game_mode | bool | true | ++----------------------+------+---------+ + +``strict_end_game_mode`` controls when a +block may be requested twice. If this is ``true``, a block may only +be requested twice when there's at least one request to every piece +that's left to download in the torrent. This may slow down progress +on some pieces sometimes, but it may also avoid downloading a lot +of redundant bytes. If this is ``false``, libtorrent attempts to +use each peer connection to its max, by always requesting +something, even if it means requesting something that has been +requested from another peer already. + +.. _enable_outgoing_utp: + +.. _enable_incoming_utp: + +.. _enable_outgoing_tcp: + +.. _enable_incoming_tcp: + +.. raw:: html + + + + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| enable_outgoing_utp | bool | true | ++---------------------+------+---------+ +| enable_incoming_utp | bool | true | ++---------------------+------+---------+ +| enable_outgoing_tcp | bool | true | ++---------------------+------+---------+ +| enable_incoming_tcp | bool | true | ++---------------------+------+---------+ + +Enables incoming and outgoing, TCP and uTP peer connections. +``false`` is disabled and ``true`` is enabled. When outgoing +connections are disabled, libtorrent will simply not make +outgoing peer connections with the specific transport protocol. +Disabled incoming peer connections will simply be rejected. +These options only apply to peer connections, not tracker- or any +other kinds of connections. + +.. _no_recheck_incomplete_resume: + +.. raw:: html + + + ++------------------------------+------+---------+ +| name | type | default | ++==============================+======+=========+ +| no_recheck_incomplete_resume | bool | false | ++------------------------------+------+---------+ + +``no_recheck_incomplete_resume`` determines if the storage should +check the whole files when resume data is incomplete or missing or +whether it should simply assume we don't have any of the data. If +false, any existing files will be checked. +By setting this setting to true, the files won't be checked, but +will go straight to download mode. + +.. _anonymous_mode: + +.. raw:: html + + + ++----------------+------+---------+ +| name | type | default | ++================+======+=========+ +| anonymous_mode | bool | false | ++----------------+------+---------+ + +``anonymous_mode``: When set to true, the client tries to hide +its identity to a certain degree. + +* A generic user-agent will be + used for trackers (except for private torrents). +* Your local IPv4 and IPv6 address won't be sent as query string + parameters to private trackers. +* If announce_ip is configured, it will not be sent to trackers +* The client version will not be sent to peers in the extension + handshake. + +.. _report_web_seed_downloads: + +.. raw:: html + + + ++---------------------------+------+---------+ +| name | type | default | ++===========================+======+=========+ +| report_web_seed_downloads | bool | true | ++---------------------------+------+---------+ + +specifies whether downloads from web seeds is reported to the +tracker or not. Turning it off also excludes web +seed traffic from other stats and download rate reporting via the +libtorrent API. + +.. _seeding_outgoing_connections: + +.. raw:: html + + + ++------------------------------+------+---------+ +| name | type | default | ++==============================+======+=========+ +| seeding_outgoing_connections | bool | true | ++------------------------------+------+---------+ + +``seeding_outgoing_connections`` determines if seeding (and +finished) torrents should attempt to make outgoing connections or +not. It may be set to false in very +specific applications where the cost of making outgoing connections +is high, and there are no or small benefits of doing so. For +instance, if no nodes are behind a firewall or a NAT, seeds don't +need to make outgoing connections. + +.. _no_connect_privileged_ports: + +.. raw:: html + + + ++-----------------------------+------+---------+ +| name | type | default | ++=============================+======+=========+ +| no_connect_privileged_ports | bool | false | ++-----------------------------+------+---------+ + +when this is true, libtorrent will not attempt to make outgoing +connections to peers whose port is < 1024. This is a safety +precaution to avoid being part of a DDoS attack + +.. _smooth_connects: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| smooth_connects | bool | true | ++-----------------+------+---------+ + +``smooth_connects`` means the number of +connection attempts per second may be limited to below the +``connection_speed``, in case we're close to bump up against the +limit of number of connections. The intention of this setting is to +more evenly distribute our connection attempts over time, instead +of attempting to connect in batches, and timing them out in +batches. + +.. _always_send_user_agent: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| always_send_user_agent | bool | false | ++------------------------+------+---------+ + +always send user-agent in every web seed request. If false, only +the first request per http connection will include the user agent + +.. _apply_ip_filter_to_trackers: + +.. raw:: html + + + ++-----------------------------+------+---------+ +| name | type | default | ++=============================+======+=========+ +| apply_ip_filter_to_trackers | bool | true | ++-----------------------------+------+---------+ + +``apply_ip_filter_to_trackers`` determines +whether the IP filter applies to trackers as well as peers. If this +is set to false, trackers are exempt from the IP filter (if there +is one). If no IP filter is set, this setting is irrelevant. + +.. _ban_web_seeds: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| ban_web_seeds | bool | true | ++---------------+------+---------+ + +when true, web seeds sending bad data will be banned + +.. _support_share_mode: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| support_share_mode | bool | true | ++--------------------+------+---------+ + +if false, prevents libtorrent to advertise share-mode support + +.. _report_redundant_bytes: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| report_redundant_bytes | bool | true | ++------------------------+------+---------+ + +if this is true, the number of redundant bytes is sent to the +tracker + +.. _listen_system_port_fallback: + +.. raw:: html + + + ++-----------------------------+------+---------+ +| name | type | default | ++=============================+======+=========+ +| listen_system_port_fallback | bool | true | ++-----------------------------+------+---------+ + +if this is true, libtorrent will fall back to listening on a port +chosen by the operating system (i.e. binding to port 0). If a +failure is preferred, set this to false. + +.. _announce_crypto_support: + +.. raw:: html + + + ++-------------------------+------+---------+ +| name | type | default | ++=========================+======+=========+ +| announce_crypto_support | bool | true | ++-------------------------+------+---------+ + +when this is true, and incoming encrypted connections are enabled, +&supportcrypt=1 is included in http tracker announces + +.. _enable_upnp: + +.. raw:: html + + + ++-------------+------+---------+ +| name | type | default | ++=============+======+=========+ +| enable_upnp | bool | true | ++-------------+------+---------+ + +Starts and stops the UPnP service. When started, the listen port +and the DHT port are attempted to be forwarded on local UPnP router +devices. + +The upnp object returned by ``start_upnp()`` can be used to add and +remove arbitrary port mappings. Mapping status is returned through +the `portmap_alert`__ and the `portmap_error_alert`__. The object will be +valid until ``stop_upnp()`` is called. See `upnp and nat pmp`__. + +.. _enable_natpmp: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| enable_natpmp | bool | true | ++---------------+------+---------+ + +Starts and stops the NAT-PMP service. When started, the listen port +and the DHT port are attempted to be forwarded on the router +through NAT-PMP. + +The natpmp object returned by ``start_natpmp()`` can be used to add +and remove arbitrary port mappings. Mapping status is returned +through the `portmap_alert`__ and the `portmap_error_alert`__. The object +will be valid until ``stop_natpmp()`` is called. See +`upnp and nat pmp`__. + +.. _enable_lsd: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| enable_lsd | bool | true | ++------------+------+---------+ + +Starts and stops Local Service Discovery. This service will +broadcast the info-hashes of all the non-private torrents on the +local network to look for peers on the same swarm within multicast +reach. + +.. _enable_dht: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| enable_dht | bool | true | ++------------+------+---------+ + +starts the dht node and makes the trackerless service available to +torrents. + +.. _prefer_rc4: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| prefer_rc4 | bool | false | ++------------+------+---------+ + +if the allowed encryption level is both, setting this to true will +prefer RC4 if both methods are offered, plain text otherwise + +.. _proxy_hostnames: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| proxy_hostnames | bool | true | ++-----------------+------+---------+ + +if true, hostname lookups are done via the configured proxy (if +any). This is only supported by SOCKS5 and HTTP. + +.. _proxy_peer_connections: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| proxy_peer_connections | bool | true | ++------------------------+------+---------+ + +if true, peer connections are made (and accepted) over the +configured proxy, if any. Web seeds as well as regular bittorrent +peer connections are considered "peer connections". Anything +transporting actual torrent payload (trackers and DHT traffic are +not considered peer connections). + +.. _auto_sequential: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| auto_sequential | bool | true | ++-----------------+------+---------+ + +if this setting is true, torrents with a very high availability of +pieces (and seeds) are downloaded sequentially. This is more +efficient for the disk I/O. With many seeds, the download order is +unlikely to matter anyway + +.. _proxy_tracker_connections: + +.. raw:: html + + + ++---------------------------+------+---------+ +| name | type | default | ++===========================+======+=========+ +| proxy_tracker_connections | bool | true | ++---------------------------+------+---------+ + +if true, tracker connections are made over the configured proxy, if +any. + +.. _enable_ip_notifier: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| enable_ip_notifier | bool | true | ++--------------------+------+---------+ + +Starts and stops the internal IP table route changes notifier. + +The current implementation supports multiple platforms, and it is +recommended to have it enable, but you may want to disable it if +it's supported but unreliable, or if you have a better way to +detect the changes. In the later case, you should manually call +``session_handle::reopen_network_sockets`` to ensure network +changes are taken in consideration. + +.. _dht_prefer_verified_node_ids: + +.. raw:: html + + + ++------------------------------+------+---------+ +| name | type | default | ++==============================+======+=========+ +| dht_prefer_verified_node_ids | bool | true | ++------------------------------+------+---------+ + +when this is true, nodes whose IDs are derived from their source +IP according to `BEP 42`_ are preferred in the routing table. + +.. _dht_restrict_routing_ips: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| dht_restrict_routing_ips | bool | true | ++--------------------------+------+---------+ + +determines if the routing table entries should restrict entries to one +per IP. This defaults to true, which helps mitigate some attacks on +the DHT. It prevents adding multiple nodes with IPs with a very close +CIDR distance. + +when set, nodes whose IP address that's in the same /24 (or /64 for +IPv6) range in the same routing table bucket. This is an attempt to +mitigate node ID spoofing attacks also restrict any IP to only have a +single `entry`__ in the whole routing table + +.. _dht_restrict_search_ips: + +.. raw:: html + + + ++-------------------------+------+---------+ +| name | type | default | ++=========================+======+=========+ +| dht_restrict_search_ips | bool | true | ++-------------------------+------+---------+ + +determines if DHT searches should prevent adding nodes with IPs with +very close CIDR distance. This also defaults to true and helps +mitigate certain attacks on the DHT. + +.. _dht_extended_routing_table: + +.. raw:: html + + + ++----------------------------+------+---------+ +| name | type | default | ++============================+======+=========+ +| dht_extended_routing_table | bool | true | ++----------------------------+------+---------+ + +makes the first buckets in the DHT routing table fit 128, 64, 32 and +16 nodes respectively, as opposed to the standard size of 8. All other +buckets have size 8 still. + +.. _dht_aggressive_lookups: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| dht_aggressive_lookups | bool | true | ++------------------------+------+---------+ + +slightly changes the lookup behavior in terms of how many outstanding +requests we keep. Instead of having branch factor be a hard limit, we +always keep *branch factor* outstanding requests to the closest nodes. +i.e. every time we get results back with closer nodes, we query them +right away. It lowers the lookup times at the cost of more outstanding +queries. + +.. _dht_privacy_lookups: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| dht_privacy_lookups | bool | false | ++---------------------+------+---------+ + +when set, perform lookups in a way that is slightly more expensive, +but which minimizes the amount of information leaked about you. + +.. _dht_enforce_node_id: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| dht_enforce_node_id | bool | false | ++---------------------+------+---------+ + +when set, node's whose IDs that are not correctly generated based on +its external IP are ignored. When a query arrives from such node, an +error message is returned with a message saying "invalid node ID". + +.. _dht_ignore_dark_internet: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| dht_ignore_dark_internet | bool | true | ++--------------------------+------+---------+ + +ignore DHT messages from parts of the internet we wouldn't expect to +see any traffic from + +.. _dht_read_only: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| dht_read_only | bool | false | ++---------------+------+---------+ + +when set, the other nodes won't keep this node in their routing +tables, it's meant for low-power and/or ephemeral devices that +cannot support the DHT, it is also useful for mobile devices which +are sensitive to network traffic and battery life. +this node no longer responds to 'query' messages, and will place a +'ro' key (value = 1) in the top-level message dictionary of outgoing +query messages. + +.. _piece_extent_affinity: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| piece_extent_affinity | bool | false | ++-----------------------+------+---------+ + +when this is true, create an affinity for downloading 4 MiB extents +of adjacent pieces. This is an attempt to achieve better disk I/O +throughput by downloading larger extents of bytes, for torrents with +small piece sizes + +.. _validate_https_trackers: + +.. raw:: html + + + ++-------------------------+------+---------+ +| name | type | default | ++=========================+======+=========+ +| validate_https_trackers | bool | true | ++-------------------------+------+---------+ + +when set to true, the certificate of HTTPS trackers and HTTPS web +seeds will be validated against the system's certificate store +(as defined by OpenSSL). If the system does not have a +certificate store, this option may have to be disabled in order +to get trackers and web seeds to work). + +.. _ssrf_mitigation: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| ssrf_mitigation | bool | true | ++-----------------+------+---------+ + +when enabled, tracker and web seed requests are subject to +certain restrictions. + +An HTTP(s) tracker requests to localhost (loopback) +must have the request path start with "/announce". This is the +conventional bittorrent tracker request. Any other HTTP(S) +tracker request to loopback will be rejected. This applies to +trackers that redirect to loopback as well. + +Web seeds that end up on the client's local network (i.e. in a +private IP address range) may not include query string arguments. +This applies to web seeds redirecting to the local network as +well. + +Web seeds on global IPs (i.e. not local network) may not redirect +to a local network address + +.. _allow_idna: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| allow_idna | bool | false | ++------------+------+---------+ + +when disabled, any tracker or web seed with an IDNA hostname +(internationalized domain name) is ignored. This is a security +precaution to avoid various unicode encoding attacks that might +happen at the application level. + +.. _enable_set_file_valid_data: + +.. raw:: html + + + ++----------------------------+------+---------+ +| name | type | default | ++============================+======+=========+ +| enable_set_file_valid_data | bool | false | ++----------------------------+------+---------+ + +when set to true, enables the attempt to use SetFileValidData() +to pre-allocate disk space. This system call will only work when +running with Administrator privileges on Windows, and so this +setting is only relevant in that scenario. Using +SetFileValidData() poses a security risk, as it may reveal +previously deleted information from the disk. + +.. _socks5_udp_send_local_ep: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| socks5_udp_send_local_ep | bool | false | ++--------------------------+------+---------+ + +When using a SOCKS5 proxy, UDP traffic is routed through the +proxy by sending a UDP ASSOCIATE command. If this option is true, +the UDP ASSOCIATE command will include the IP address and +listen port to the local UDP socket. This indicates to the proxy +which source endpoint to expect our packets from. The benefit is +that incoming packets can be forwarded correctly, before any +outgoing packets are sent. The risk is that if there's a NAT +between the client and the proxy, the IP address specified in the +protocol may not be valid from the proxy's point of view. + +.. _tracker_completion_timeout: + +.. raw:: html + + + ++----------------------------+------+---------+ +| name | type | default | ++============================+======+=========+ +| tracker_completion_timeout | int | 30 | ++----------------------------+------+---------+ + +``tracker_completion_timeout`` is the number of seconds the tracker +connection will wait from when it sent the request until it +considers the tracker to have timed-out. + +.. _tracker_receive_timeout: + +.. raw:: html + + + ++-------------------------+------+---------+ +| name | type | default | ++=========================+======+=========+ +| tracker_receive_timeout | int | 10 | ++-------------------------+------+---------+ + +``tracker_receive_timeout`` is the number of seconds to wait to +receive any data from the tracker. If no data is received for this +number of seconds, the tracker will be considered as having timed +out. If a tracker is down, this is the kind of timeout that will +occur. + +.. _stop_tracker_timeout: + +.. raw:: html + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| stop_tracker_timeout | int | 5 | ++----------------------+------+---------+ + +``stop_tracker_timeout`` is the number of seconds to wait when +sending a stopped message before considering a tracker to have +timed out. This is usually shorter, to make the client quit faster. +If the value is set to 0, the connections to trackers with the +stopped event are suppressed. + +.. _tracker_maximum_response_length: + +.. raw:: html + + + ++---------------------------------+------+-----------+ +| name | type | default | ++=================================+======+===========+ +| tracker_maximum_response_length | int | 1024*1024 | ++---------------------------------+------+-----------+ + +this is the maximum number of bytes in a tracker response. If a +response size passes this number of bytes it will be rejected and +the connection will be closed. On gzipped responses this size is +measured on the uncompressed data. So, if you get 20 bytes of gzip +response that'll expand to 2 megabytes, it will be interrupted +before the entire response has been uncompressed (assuming the +limit is lower than 2 MiB). + +.. _piece_timeout: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| piece_timeout | int | 20 | ++---------------+------+---------+ + +the number of seconds from a request is sent until it times out if +no piece response is returned. + +.. _request_timeout: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| request_timeout | int | 60 | ++-----------------+------+---------+ + +the number of seconds one block (16 kiB) is expected to be received +within. If it's not, the block is requested from a different peer + +.. _request_queue_time: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| request_queue_time | int | 3 | ++--------------------+------+---------+ + +the length of the request queue given in the number of seconds it +should take for the other end to send all the pieces. i.e. the +actual number of requests depends on the download rate and this +number. + +.. _max_allowed_in_request_queue: + +.. raw:: html + + + ++------------------------------+------+---------+ +| name | type | default | ++==============================+======+=========+ +| max_allowed_in_request_queue | int | 2000 | ++------------------------------+------+---------+ + +the number of outstanding block requests a peer is allowed to queue +up in the client. If a peer sends more requests than this (before +the first one has been sent) the last request will be dropped. the +higher this is, the faster upload speeds the client can get to a +single peer. + +.. _max_out_request_queue: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| max_out_request_queue | int | 500 | ++-----------------------+------+---------+ + +``max_out_request_queue`` is the maximum number of outstanding +requests to send to a peer. This limit takes precedence over +``request_queue_time``. i.e. no matter the download speed, the +number of outstanding requests will never exceed this limit. + +.. _whole_pieces_threshold: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| whole_pieces_threshold | int | 20 | ++------------------------+------+---------+ + +if a whole piece can be downloaded in this number of seconds, or +less, the peer_connection will prefer to request whole pieces at a +time from this peer. The benefit of this is to better utilize disk +caches by doing localized accesses and also to make it easier to +identify bad peers if a piece fails the hash check. + +.. _peer_timeout: + +.. raw:: html + + + ++--------------+------+---------+ +| name | type | default | ++==============+======+=========+ +| peer_timeout | int | 120 | ++--------------+------+---------+ + +``peer_timeout`` is the number of seconds the peer connection +should wait (for any activity on the peer connection) before +closing it due to time out. 120 seconds is +specified in the protocol specification. After half +the time out, a keep alive message is sent. + +.. _urlseed_timeout: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| urlseed_timeout | int | 20 | ++-----------------+------+---------+ + +same as peer_timeout, but only applies to url-seeds. this is +usually set lower, because web servers are expected to be more +reliable. + +.. _urlseed_pipeline_size: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| urlseed_pipeline_size | int | 5 | ++-----------------------+------+---------+ + +controls the pipelining size of url and http seeds. i.e. the number of HTTP +request to keep outstanding before waiting for the first one to +complete. It's common for web servers to limit this to a relatively +low number, like 5 + +.. _urlseed_wait_retry: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| urlseed_wait_retry | int | 30 | ++--------------------+------+---------+ + +number of seconds until a new retry of a url-seed takes place. +Default retry value for http-seeds that don't provide +a valid ``retry-after`` header. + +.. _file_pool_size: + +.. raw:: html + + + ++----------------+------+---------+ +| name | type | default | ++================+======+=========+ +| file_pool_size | int | 40 | ++----------------+------+---------+ + +sets the upper limit on the total number of files this `session`__ will +keep open. The reason why files are left open at all is that some +anti virus software hooks on every file close, and scans the file +for viruses. deferring the closing of the files will be the +difference between a usable system and a completely hogged down +system. Most operating systems also has a limit on the total number +of file descriptors a process may have open. + +.. _max_failcount: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| max_failcount | int | 3 | ++---------------+------+---------+ + +``max_failcount`` is the maximum times we try to +connect to a peer before stop connecting again. If a +peer succeeds, the failure counter is reset. If a +peer is retrieved from a peer source (other than DHT) +the failcount is decremented by one, allowing another +try. + +.. _min_reconnect_time: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| min_reconnect_time | int | 60 | ++--------------------+------+---------+ + +the number of seconds to wait to reconnect to a peer. this time is +multiplied with the failcount. + +.. _peer_connect_timeout: + +.. raw:: html + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| peer_connect_timeout | int | 15 | ++----------------------+------+---------+ + +``peer_connect_timeout`` the number of seconds to wait after a +connection attempt is initiated to a peer until it is considered as +having timed out. This setting is especially important in case the +number of half-open connections are limited, since stale half-open +connection may delay the connection of other peers considerably. + +.. _connection_speed: + +.. raw:: html + + + ++------------------+------+---------+ +| name | type | default | ++==================+======+=========+ +| connection_speed | int | 30 | ++------------------+------+---------+ + +``connection_speed`` is the number of connection attempts that are +made per second. If a number < 0 is specified, it will default to +200 connections per second. If 0 is specified, it means don't make +outgoing connections at all. + +.. _inactivity_timeout: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| inactivity_timeout | int | 600 | ++--------------------+------+---------+ + +if a peer is uninteresting and uninterested for longer than this +number of seconds, it will be disconnected. + +.. _unchoke_interval: + +.. raw:: html + + + ++------------------+------+---------+ +| name | type | default | ++==================+======+=========+ +| unchoke_interval | int | 15 | ++------------------+------+---------+ + +``unchoke_interval`` is the number of seconds between +chokes/unchokes. On this interval, peers are re-evaluated for being +choked/unchoked. This is defined as 30 seconds in the protocol, and +it should be significantly longer than what it takes for TCP to +ramp up to it's max rate. + +.. _optimistic_unchoke_interval: + +.. raw:: html + + + ++-----------------------------+------+---------+ +| name | type | default | ++=============================+======+=========+ +| optimistic_unchoke_interval | int | 30 | ++-----------------------------+------+---------+ + +``optimistic_unchoke_interval`` is the number of seconds between +each *optimistic* unchoke. On this timer, the currently +optimistically unchoked peer will change. + +.. _num_want: + +.. raw:: html + + + ++----------+------+---------+ +| name | type | default | ++==========+======+=========+ +| num_want | int | 200 | ++----------+------+---------+ + +``num_want`` is the number of peers we want from each tracker +request. It defines what is sent as the ``&num_want=`` parameter to +the tracker. + +.. _initial_picker_threshold: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| initial_picker_threshold | int | 4 | ++--------------------------+------+---------+ + +``initial_picker_threshold`` specifies the number of pieces we need +before we switch to rarest first picking. The first +``initial_picker_threshold`` pieces in any torrent are picked at random +, the following pieces are picked in rarest first order. + +.. _allowed_fast_set_size: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| allowed_fast_set_size | int | 5 | ++-----------------------+------+---------+ + +the number of allowed pieces to send to peers that supports the +fast extensions + +.. _suggest_mode: + +.. raw:: html + + + ++--------------+------+-------------------------------------+ +| name | type | default | ++==============+======+=====================================+ +| suggest_mode | int | settings_pack::no_piece_suggestions | ++--------------+------+-------------------------------------+ + +``suggest_mode`` controls whether or not libtorrent will send out +suggest messages to create a bias of its peers to request certain +pieces. The modes are: + +* ``no_piece_suggestions`` which will not send out suggest messages. +* ``suggest_read_cache`` which will send out suggest messages for + the most recent pieces that are in the read cache. + +.. _max_queued_disk_bytes: + +.. raw:: html + + + ++-----------------------+------+-------------+ +| name | type | default | ++=======================+======+=============+ +| max_queued_disk_bytes | int | 1024 * 1024 | ++-----------------------+------+-------------+ + +``max_queued_disk_bytes`` is the maximum number of bytes, to +be written to disk, that can wait in the disk I/O thread queue. +This queue is only for waiting for the disk I/O thread to receive +the job and either write it to disk or insert it in the write +cache. When this limit is reached, the peer connections will stop +reading data from their sockets, until the disk thread catches up. +Setting this too low will severely limit your download rate. + +.. _handshake_timeout: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| handshake_timeout | int | 10 | ++-------------------+------+---------+ + +the number of seconds to wait for a handshake response from a peer. +If no response is received within this time, the peer is +disconnected. + +.. _send_buffer_low_watermark: + +.. _send_buffer_watermark: + +.. _send_buffer_watermark_factor: + +.. raw:: html + + + + + ++------------------------------+------+------------+ +| name | type | default | ++==============================+======+============+ +| send_buffer_low_watermark | int | 10 * 1024 | ++------------------------------+------+------------+ +| send_buffer_watermark | int | 500 * 1024 | ++------------------------------+------+------------+ +| send_buffer_watermark_factor | int | 50 | ++------------------------------+------+------------+ + +``send_buffer_low_watermark`` the minimum send buffer target size +(send buffer includes bytes pending being read from disk). For good +and snappy seeding performance, set this fairly high, to at least +fit a few blocks. This is essentially the initial window size which +will determine how fast we can ramp up the send rate + +if the send buffer has fewer bytes than ``send_buffer_watermark``, +we'll read another 16 kiB block onto it. If set too small, upload +rate capacity will suffer. If set too high, memory will be wasted. +The actual watermark may be lower than this in case the upload rate +is low, this is the upper limit. + +the current upload rate to a peer is multiplied by this factor to +get the send buffer watermark. The factor is specified as a +percentage. i.e. 50 -> 0.5 This product is clamped to the +``send_buffer_watermark`` setting to not exceed the max. For high +speed upload, this should be set to a greater value than 100. For +high capacity connections, setting this higher can improve upload +performance and disk throughput. Setting it too high may waste RAM +and create a bias towards read jobs over write jobs. + +.. _choking_algorithm: + +.. _seed_choking_algorithm: + +.. raw:: html + + + + ++------------------------+------+-----------------------------------+ +| name | type | default | ++========================+======+===================================+ +| choking_algorithm | int | settings_pack::fixed_slots_choker | ++------------------------+------+-----------------------------------+ +| seed_choking_algorithm | int | settings_pack::round_robin | ++------------------------+------+-----------------------------------+ + +``choking_algorithm`` specifies which algorithm to use to determine +how many peers to unchoke. The unchoking algorithm for +downloading torrents is always "tit-for-tat", i.e. the peers we +download the fastest from are unchoked. + +The options for choking algorithms are defined in the +`choking_algorithm_t`__ enum. + +``seed_choking_algorithm`` controls the seeding unchoke behavior. +i.e. How we select which peers to unchoke for seeding torrents. +Since a seeding torrent isn't downloading anything, the +tit-for-tat mechanism cannot be used. The available options are +defined in the `seed_choking_algorithm_t`__ enum. + +.. _disk_io_write_mode: + +.. _disk_io_read_mode: + +.. raw:: html + + + + ++--------------------+------+--------------------------------+ +| name | type | default | ++====================+======+================================+ +| disk_io_write_mode | int | DISK_WRITE_MODE | ++--------------------+------+--------------------------------+ +| disk_io_read_mode | int | settings_pack::enable_os_cache | ++--------------------+------+--------------------------------+ + +determines how files are opened when they're in read only mode +versus read and write mode. The options are: + +enable_os_cache + Files are opened normally, with the OS caching reads and writes. +disable_os_cache + This opens all files in no-cache mode. This corresponds to the + OS not letting blocks for the files linger in the cache. This + makes sense in order to avoid the bittorrent client to + potentially evict all other processes' cache by simply handling + high throughput and large files. If libtorrent's read cache is + disabled, enabling this may reduce performance. +write_through + flush pieces to disk as they complete validation. + +One reason to disable caching is that it may help the operating +system from growing its file cache indefinitely. + +.. _outgoing_port: + +.. _num_outgoing_ports: + +.. raw:: html + + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| outgoing_port | int | 0 | ++--------------------+------+---------+ +| num_outgoing_ports | int | 0 | ++--------------------+------+---------+ + +this is the first port to use for binding outgoing connections to. +This is useful for users that have routers that allow QoS settings +based on local port. when binding outgoing connections to specific +ports, ``num_outgoing_ports`` is the size of the range. It should +be more than a few + +.. warning:: setting outgoing ports will limit the ability to keep + multiple connections to the same client, even for different + torrents. It is not recommended to change this setting. Its main + purpose is to use as an escape hatch for cheap routers with QoS + capability but can only classify flows based on port numbers. + +It is a range instead of a single port because of the problems with +failing to reconnect to peers if a previous socket to that peer and +port is in ``TIME_WAIT`` state. + +.. _peer_dscp: + +.. raw:: html + + + ++-----------+------+---------+ +| name | type | default | ++===========+======+=========+ +| peer_dscp | int | 0x04 | ++-----------+------+---------+ + +``peer_dscp`` determines the DSCP field in the IP header of every +packet sent to peers (including web seeds). ``0x0`` means no marking, +``0x04`` represents Lower Effort. For more details see `RFC 8622`_. + +.. _`RFC 8622`: http://www.faqs.org/rfcs/rfc8622.html + +``peer_tos`` is the backwards compatible name for this setting. + +.. _active_downloads: + +.. _active_seeds: + +.. _active_checking: + +.. _active_dht_limit: + +.. _active_tracker_limit: + +.. _active_lsd_limit: + +.. _active_limit: + +.. raw:: html + + + + + + + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| active_downloads | int | 3 | ++----------------------+------+---------+ +| active_seeds | int | 5 | ++----------------------+------+---------+ +| active_checking | int | 1 | ++----------------------+------+---------+ +| active_dht_limit | int | 88 | ++----------------------+------+---------+ +| active_tracker_limit | int | 1600 | ++----------------------+------+---------+ +| active_lsd_limit | int | 60 | ++----------------------+------+---------+ +| active_limit | int | 500 | ++----------------------+------+---------+ + +for auto managed torrents, these are the limits they are subject +to. If there are too many torrents some of the auto managed ones +will be paused until some slots free up. ``active_downloads`` and +``active_seeds`` controls how many active seeding and downloading +torrents the queuing mechanism allows. The target number of active +torrents is ``min(active_downloads + active_seeds, active_limit)``. +``active_downloads`` and ``active_seeds`` are upper limits on the +number of downloading torrents and seeding torrents respectively. +Setting the value to -1 means unlimited. + +For example if there are 10 seeding torrents and 10 downloading +torrents, and ``active_downloads`` is 4 and ``active_seeds`` is 4, +there will be 4 seeds active and 4 downloading torrents. If the +settings are ``active_downloads`` = 2 and ``active_seeds`` = 4, +then there will be 2 downloading torrents and 4 seeding torrents +active. Torrents that are not auto managed are not counted against +these limits. + +``active_checking`` is the limit of number of simultaneous checking +torrents. + +``active_limit`` is a hard limit on the number of active (auto +managed) torrents. This limit also applies to slow torrents. + +``active_dht_limit`` is the max number of torrents to announce to +the DHT. + +``active_tracker_limit`` is the max number of torrents to announce +to their trackers. + +``active_lsd_limit`` is the max number of torrents to announce to +the local network over the local service discovery protocol. + +You can have more torrents *active*, even though they are not +announced to the DHT, lsd or their tracker. If some peer knows +about you for any reason and tries to connect, it will still be +accepted, unless the torrent is paused, which means it won't accept +any connections. + +.. _auto_manage_interval: + +.. raw:: html + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| auto_manage_interval | int | 30 | ++----------------------+------+---------+ + +``auto_manage_interval`` is the number of seconds between the +torrent queue is updated, and rotated. + +.. _seed_time_limit: + +.. raw:: html + + + ++-----------------+------+--------------+ +| name | type | default | ++=================+======+==============+ +| seed_time_limit | int | 24 * 60 * 60 | ++-----------------+------+--------------+ + +this is the limit on the time a torrent has been an active seed +(specified in seconds) before it is considered having met the seed +limit criteria. See `queuing`__. + +.. _auto_scrape_interval: + +.. _auto_scrape_min_interval: + +.. raw:: html + + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| auto_scrape_interval | int | 1800 | ++--------------------------+------+---------+ +| auto_scrape_min_interval | int | 300 | ++--------------------------+------+---------+ + +``auto_scrape_interval`` is the number of seconds between scrapes +of queued torrents (auto managed and paused torrents). Auto managed +torrents that are paused, are scraped regularly in order to keep +track of their downloader/seed ratio. This ratio is used to +determine which torrents to seed and which to pause. + +``auto_scrape_min_interval`` is the minimum number of seconds +between any automatic scrape (regardless of torrent). In case there +are a large number of paused auto managed torrents, this puts a +limit on how often a scrape request is sent. + +.. _max_peerlist_size: + +.. _max_paused_peerlist_size: + +.. raw:: html + + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| max_peerlist_size | int | 3000 | ++--------------------------+------+---------+ +| max_paused_peerlist_size | int | 1000 | ++--------------------------+------+---------+ + +``max_peerlist_size`` is the maximum number of peers in the list of +known peers. These peers are not necessarily connected, so this +number should be much greater than the maximum number of connected +peers. Peers are evicted from the cache when the list grows passed +90% of this limit, and once the size hits the limit, peers are no +longer added to the list. If this limit is set to 0, there is no +limit on how many peers we'll keep in the peer list. + +``max_paused_peerlist_size`` is the max peer list size used for +torrents that are paused. This can be used to save memory for paused +torrents, since it's not as important for them to keep a large peer +list. + +.. _min_announce_interval: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| min_announce_interval | int | 5 * 60 | ++-----------------------+------+---------+ + +this is the minimum allowed announce interval for a tracker. This +is specified in seconds and is used as a sanity check on what is +returned from a tracker. It mitigates hammering mis-configured +trackers. + +.. _auto_manage_startup: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| auto_manage_startup | int | 60 | ++---------------------+------+---------+ + +this is the number of seconds a torrent is considered active after +it was started, regardless of upload and download speed. This is so +that newly started torrents are not considered inactive until they +have a fair chance to start downloading. + +.. _seeding_piece_quota: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| seeding_piece_quota | int | 20 | ++---------------------+------+---------+ + +``seeding_piece_quota`` is the number of pieces to send to a peer, +when seeding, before rotating in another peer to the unchoke set. + +.. _max_rejects: + +.. raw:: html + + + ++-------------+------+---------+ +| name | type | default | ++=============+======+=========+ +| max_rejects | int | 50 | ++-------------+------+---------+ + +``max_rejects`` is the number of piece requests we will reject in a +row while a peer is choked before the peer is considered abusive +and is disconnected. + +.. _recv_socket_buffer_size: + +.. _send_socket_buffer_size: + +.. raw:: html + + + + ++-------------------------+------+---------+ +| name | type | default | ++=========================+======+=========+ +| recv_socket_buffer_size | int | 0 | ++-------------------------+------+---------+ +| send_socket_buffer_size | int | 0 | ++-------------------------+------+---------+ + +specifies the buffer sizes set on peer sockets. 0 means the OS +default (i.e. don't change the buffer sizes). +The socket buffer sizes are changed using setsockopt() with +SOL_SOCKET/SO_RCVBUF and SO_SNDBUFFER. + +Note that uTP peers share a single UDP socket buffer for each of the +``listen_interfaces``, along with DHT and UDP tracker traffic. +If the buffer size is too small for the combined traffic through the +socket, packets may be dropped. + +.. _max_peer_recv_buffer_size: + +.. raw:: html + + + ++---------------------------+------+-----------------+ +| name | type | default | ++===========================+======+=================+ +| max_peer_recv_buffer_size | int | 2 * 1024 * 1024 | ++---------------------------+------+-----------------+ + +the max number of bytes a single peer connection's receive buffer is +allowed to grow to. + +.. _optimistic_disk_retry: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| optimistic_disk_retry | int | 10 * 60 | ++-----------------------+------+---------+ + +``optimistic_disk_retry`` is the number of seconds from a disk +write errors occur on a torrent until libtorrent will take it out +of the upload mode, to test if the error condition has been fixed. + +libtorrent will only do this automatically for auto managed +torrents. + +You can explicitly take a torrent out of upload only mode using +set_upload_mode(). + +.. _max_suggest_pieces: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| max_suggest_pieces | int | 16 | ++--------------------+------+---------+ + +``max_suggest_pieces`` is the max number of suggested piece indices +received from a peer that's remembered. If a peer floods suggest +messages, this limit prevents libtorrent from using too much RAM. + +.. _local_service_announce_interval: + +.. raw:: html + + + ++---------------------------------+------+---------+ +| name | type | default | ++=================================+======+=========+ +| local_service_announce_interval | int | 5 * 60 | ++---------------------------------+------+---------+ + +``local_service_announce_interval`` is the time between local +network announces for a torrent. +This interval is specified in seconds. + +.. _dht_announce_interval: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| dht_announce_interval | int | 15 * 60 | ++-----------------------+------+---------+ + +``dht_announce_interval`` is the number of seconds between +announcing torrents to the distributed hash table (DHT). + +.. _udp_tracker_token_expiry: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| udp_tracker_token_expiry | int | 60 | ++--------------------------+------+---------+ + +``udp_tracker_token_expiry`` is the number of seconds libtorrent +will keep UDP tracker connection tokens around for. This is +specified to be 60 seconds. The higher this +value is, the fewer packets have to be sent to the UDP tracker. In +order for higher values to work, the tracker needs to be configured +to match the expiration time for tokens. + +.. _num_optimistic_unchoke_slots: + +.. raw:: html + + + ++------------------------------+------+---------+ +| name | type | default | ++==============================+======+=========+ +| num_optimistic_unchoke_slots | int | 0 | ++------------------------------+------+---------+ + +``num_optimistic_unchoke_slots`` is the number of optimistic +unchoke slots to use. +Having a higher number of optimistic unchoke slots mean you will +find the good peers faster but with the trade-off to use up more +bandwidth. 0 means automatic, where libtorrent opens up 20% of your +allowed upload slots as optimistic unchoke slots. + +.. _max_pex_peers: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| max_pex_peers | int | 50 | ++---------------+------+---------+ + +the max number of peers we accept from pex messages from a single +peer. this limits the number of concurrent peers any of our peers +claims to be connected to. If they claim to be connected to more +than this, we'll ignore any peer that exceeds this limit + +.. _tick_interval: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| tick_interval | int | 500 | ++---------------+------+---------+ + +``tick_interval`` specifies the number of milliseconds between +internal ticks. This is the frequency with which bandwidth quota is +distributed to peers. It should not be more than one second (i.e. +1000 ms). Setting this to a low value (around 100) means higher +resolution bandwidth quota distribution, setting it to a higher +value saves CPU cycles. + +.. _share_mode_target: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| share_mode_target | int | 3 | ++-------------------+------+---------+ + +``share_mode_target`` specifies the target share ratio for share +mode torrents. If set to 3, we'll try to upload 3 +times as much as we download. Setting this very high, will make it +very conservative and you might end up not downloading anything +ever (and not affecting your share ratio). It does not make any +sense to set this any lower than 2. For instance, if only 3 peers +need to download the rarest piece, it's impossible to download a +single piece and upload it more than 3 times. If the +share_mode_target is set to more than 3, nothing is downloaded. + +.. _upload_rate_limit: + +.. _download_rate_limit: + +.. raw:: html + + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| upload_rate_limit | int | 0 | ++---------------------+------+---------+ +| download_rate_limit | int | 0 | ++---------------------+------+---------+ + +``upload_rate_limit`` and ``download_rate_limit`` sets +the session-global limits of upload and download rate limits, in +bytes per second. By default peers on the local network are not rate +limited. + +A value of 0 means unlimited. + +For fine grained control over rate limits, including making them apply +to local peers, see `peer classes`__. + +.. _dht_upload_rate_limit: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| dht_upload_rate_limit | int | 8000 | ++-----------------------+------+---------+ + +the number of bytes per second (on average) the DHT is allowed to send. +If the incoming requests causes to many bytes to be sent in responses, +incoming requests will be dropped until the quota has been replenished. + +.. _unchoke_slots_limit: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| unchoke_slots_limit | int | 8 | ++---------------------+------+---------+ + +``unchoke_slots_limit`` is the max number of unchoked peers in the +`session`__. The number of unchoke slots may be ignored depending on +what ``choking_algorithm`` is set to. Setting this limit to -1 +means unlimited, i.e. all peers will always be unchoked. + +.. _connections_limit: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| connections_limit | int | 200 | ++-------------------+------+---------+ + +``connections_limit`` sets a global limit on the number of +connections opened. The number of connections is set to a hard +minimum of at least two per torrent, so if you set a too low +connections limit, and open too many torrents, the limit will not +be met. + +.. _connections_slack: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| connections_slack | int | 10 | ++-------------------+------+---------+ + +``connections_slack`` is the number of incoming connections +exceeding the connection limit to accept in order to potentially +replace existing ones. + +.. _utp_target_delay: + +.. _utp_gain_factor: + +.. _utp_min_timeout: + +.. _utp_syn_resends: + +.. _utp_fin_resends: + +.. _utp_num_resends: + +.. _utp_connect_timeout: + +.. _utp_loss_multiplier: + +.. raw:: html + + + + + + + + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| utp_target_delay | int | 100 | ++---------------------+------+---------+ +| utp_gain_factor | int | 3000 | ++---------------------+------+---------+ +| utp_min_timeout | int | 500 | ++---------------------+------+---------+ +| utp_syn_resends | int | 2 | ++---------------------+------+---------+ +| utp_fin_resends | int | 2 | ++---------------------+------+---------+ +| utp_num_resends | int | 3 | ++---------------------+------+---------+ +| utp_connect_timeout | int | 3000 | ++---------------------+------+---------+ +| utp_loss_multiplier | int | 50 | ++---------------------+------+---------+ + +``utp_target_delay`` is the target delay for uTP sockets in +milliseconds. A high value will make uTP connections more +aggressive and cause longer queues in the upload bottleneck. It +cannot be too low, since the noise in the measurements would cause +it to send too slow. +``utp_gain_factor`` is the number of bytes the uTP congestion +window can increase at the most in one RTT. +If this is set too high, the congestion controller reacts +too hard to noise and will not be stable, if it's set too low, it +will react slow to congestion and not back off as fast. + +``utp_min_timeout`` is the shortest allowed uTP socket timeout, +specified in milliseconds. The +timeout depends on the RTT of the connection, but is never smaller +than this value. A connection times out when every packet in a +window is lost, or when a packet is lost twice in a row (i.e. the +resent packet is lost as well). + +The shorter the timeout is, the faster the connection will recover +from this situation, assuming the RTT is low enough. +``utp_syn_resends`` is the number of SYN packets that are sent (and +timed out) before giving up and closing the socket. +``utp_num_resends`` is the number of times a packet is sent (and +lost or timed out) before giving up and closing the connection. +``utp_connect_timeout`` is the number of milliseconds of timeout +for the initial SYN packet for uTP connections. For each timed out +packet (in a row), the timeout is doubled. ``utp_loss_multiplier`` +controls how the congestion window is changed when a packet loss is +experienced. It's specified as a percentage multiplier for +``cwnd``. Do not change this value unless you know what you're doing. +Never set it higher than 100. + +.. _mixed_mode_algorithm: + +.. raw:: html + + + ++----------------------+------+----------------------------------+ +| name | type | default | ++======================+======+==================================+ +| mixed_mode_algorithm | int | settings_pack::peer_proportional | ++----------------------+------+----------------------------------+ + +The ``mixed_mode_algorithm`` determines how to treat TCP +connections when there are uTP connections. Since uTP is designed +to yield to TCP, there's an inherent problem when using swarms that +have both TCP and uTP connections. If nothing is done, uTP +connections would often be starved out for bandwidth by the TCP +connections. This mode is ``prefer_tcp``. The ``peer_proportional`` +mode simply looks at the current throughput and rate limits all TCP +connections to their proportional share based on how many of the +connections are TCP. This works best if uTP connections are not +rate limited by the global rate limiter (which they aren't by +default). + +.. _listen_queue_size: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| listen_queue_size | int | 5 | ++-------------------+------+---------+ + +``listen_queue_size`` is the value passed in to listen() for the +listen socket. It is the number of outstanding incoming connections +to queue up while we're not actively waiting for a connection to be +accepted. 5 should be sufficient for any +normal client. If this is a high performance server which expects +to receive a lot of connections, or used in a simulator or test, it +might make sense to raise this number. It will not take affect +until the ``listen_interfaces`` settings is updated. + +.. _torrent_connect_boost: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| torrent_connect_boost | int | 30 | ++-----------------------+------+---------+ + +``torrent_connect_boost`` is the number of peers to try to connect +to immediately when the first tracker response is received for a +torrent. This is a boost to given to new torrents to accelerate +them starting up. The normal connect scheduler is run once every +second, this allows peers to be connected immediately instead of +waiting for the `session`__ tick to trigger connections. +This may not be set higher than 255. + +.. _alert_queue_size: + +.. raw:: html + + + ++------------------+------+---------+ +| name | type | default | ++==================+======+=========+ +| alert_queue_size | int | 2000 | ++------------------+------+---------+ + +``alert_queue_size`` is the maximum number of alerts queued up +internally. If alerts are not popped, the queue will eventually +fill up to this level. Once the `alert`__ queue is full, additional +alerts will be dropped, and not delivered to the client. Once the +client drains the queue, new alerts may be delivered again. In order +to know that alerts have been dropped, see +session_handle::dropped_alerts(). + +.. _max_metadata_size: + +.. raw:: html + + + ++-------------------+------+------------------+ +| name | type | default | ++===================+======+==================+ +| max_metadata_size | int | 3 * 1024 * 10240 | ++-------------------+------+------------------+ + +``max_metadata_size`` is the maximum allowed size (in bytes) to be +received by the metadata extension, i.e. magnet links. + +.. _hashing_threads: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| hashing_threads | int | 1 | ++-----------------+------+---------+ + +``hashing_threads`` is the number of disk I/O threads to use for +piece hash verification. These threads are *in addition* to the +regular disk I/O threads specified by `settings_pack::aio_threads`__. +These threads are only used for full checking of torrents. The +hash checking done while downloading are done by the regular disk +I/O threads. +The `hasher`__ threads do not only compute hashes, but also perform +the read from disk. On storage optimal for sequential access, +such as hard drives, this setting should be set to 1, which is +also the default. + +.. _checking_mem_usage: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| checking_mem_usage | int | 256 | ++--------------------+------+---------+ + +the number of blocks to keep outstanding at any given time when +checking torrents. Higher numbers give faster re-checks but uses +more memory. Specified in number of 16 kiB blocks + +.. _predictive_piece_announce: + +.. raw:: html + + + ++---------------------------+------+---------+ +| name | type | default | ++===========================+======+=========+ +| predictive_piece_announce | int | 0 | ++---------------------------+------+---------+ + +if set to > 0, pieces will be announced to other peers before they +are fully downloaded (and before they are hash checked). The +intention is to gain 1.5 potential round trip times per downloaded +piece. When non-zero, this indicates how many milliseconds in +advance pieces should be announced, before they are expected to be +completed. + +.. _aio_threads: + +.. raw:: html + + + ++-------------+------+---------+ +| name | type | default | ++=============+======+=========+ +| aio_threads | int | 10 | ++-------------+------+---------+ + +for some aio back-ends, ``aio_threads`` specifies the number of +io-threads to use. + +.. _tracker_backoff: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| tracker_backoff | int | 250 | ++-----------------+------+---------+ + +``tracker_backoff`` determines how aggressively to back off from +retrying failing trackers. This value determines *x* in the +following formula, determining the number of seconds to wait until +the next retry: + + delay = 5 + 5 * x / 100 * fails^2 + +This setting may be useful to make libtorrent more or less +aggressive in hitting trackers. + +.. _share_ratio_limit: + +.. _seed_time_ratio_limit: + +.. raw:: html + + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| share_ratio_limit | int | 200 | ++-----------------------+------+---------+ +| seed_time_ratio_limit | int | 700 | ++-----------------------+------+---------+ + +when a seeding torrent reaches either the share ratio (bytes up / +bytes down) or the seed time ratio (seconds as seed / seconds as +downloader) or the seed time limit (seconds as seed) it is +considered done, and it will leave room for other torrents. These +are specified as percentages. Torrents that are considered done will +still be allowed to be seeded, they just won't have priority anymore. +For more, see `queuing`__. + +.. _peer_turnover: + +.. _peer_turnover_cutoff: + +.. _peer_turnover_interval: + +.. raw:: html + + + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| peer_turnover | int | 4 | ++------------------------+------+---------+ +| peer_turnover_cutoff | int | 90 | ++------------------------+------+---------+ +| peer_turnover_interval | int | 300 | ++------------------------+------+---------+ + +peer_turnover is the percentage of peers to disconnect every +turnover peer_turnover_interval (if we're at the peer limit), this +is specified in percent when we are connected to more than limit * +peer_turnover_cutoff peers disconnect peer_turnover fraction of the +peers. It is specified in percent peer_turnover_interval is the +interval (in seconds) between optimistic disconnects if the +disconnects happen and how many peers are disconnected is +controlled by peer_turnover and peer_turnover_cutoff + +.. _connect_seed_every_n_download: + +.. raw:: html + + + ++-------------------------------+------+---------+ +| name | type | default | ++===============================+======+=========+ +| connect_seed_every_n_download | int | 10 | ++-------------------------------+------+---------+ + +this setting controls the priority of downloading torrents over +seeding or finished torrents when it comes to making peer +connections. Peer connections are throttled by the connection_speed +and the half-open connection limit. This makes peer connections a +limited resource. Torrents that still have pieces to download are +prioritized by default, to avoid having many seeding torrents use +most of the connection attempts and only give one peer every now +and then to the downloading torrent. libtorrent will loop over the +downloading torrents to connect a peer each, and every n:th +connection attempt, a finished torrent is picked to be allowed to +connect to a peer. This setting controls n. + +.. _max_http_recv_buffer_size: + +.. raw:: html + + + ++---------------------------+------+------------+ +| name | type | default | ++===========================+======+============+ +| max_http_recv_buffer_size | int | 4*1024*204 | ++---------------------------+------+------------+ + +the max number of bytes to allow an HTTP response to be when +announcing to trackers or downloading .torrent files via the +``url`` provided in ``add_torrent_params``. + +.. _max_retry_port_bind: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| max_retry_port_bind | int | 10 | ++---------------------+------+---------+ + +if binding to a specific port fails, should the port be incremented +by one and tried again? This setting specifies how many times to +retry a failed port bind + +.. _alert_mask: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| alert_mask | int | int | ++------------+------+---------+ + +a bitmask combining flags from `alert_category_t`__ defining which +kinds of alerts to receive + +.. _out_enc_policy: + +.. _in_enc_policy: + +.. raw:: html + + + + ++----------------+------+---------------------------+ +| name | type | default | ++================+======+===========================+ +| out_enc_policy | int | settings_pack::pe_enabled | ++----------------+------+---------------------------+ +| in_enc_policy | int | settings_pack::pe_enabled | ++----------------+------+---------------------------+ + +control the settings for incoming and outgoing connections +respectively. see `enc_policy`__ enum for the available options. +Keep in mind that protocol encryption degrades performance in +several respects: + +1. It prevents "zero copy" disk buffers being sent to peers, since + each peer needs to mutate the data (i.e. encrypt it) the data + must be copied per peer connection rather than sending the same + buffer to multiple peers. +2. The encryption itself requires more CPU than plain bittorrent + protocol. The highest cost is the Diffie Hellman exchange on + connection setup. +3. The encryption handshake adds several round-trips to the + connection setup, and delays transferring data. + +.. _allowed_enc_level: + +.. raw:: html + + + ++-------------------+------+------------------------+ +| name | type | default | ++===================+======+========================+ +| allowed_enc_level | int | settings_pack::pe_both | ++-------------------+------+------------------------+ + +determines the encryption level of the connections. This setting +will adjust which encryption scheme is offered to the other peer, +as well as which encryption scheme is selected by the client. See +`enc_level`__ enum for options. + +.. _inactive_down_rate: + +.. _inactive_up_rate: + +.. raw:: html + + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| inactive_down_rate | int | 2048 | ++--------------------+------+---------+ +| inactive_up_rate | int | 2048 | ++--------------------+------+---------+ + +the download and upload rate limits for a torrent to be considered +active by the queuing mechanism. A torrent whose download rate is +less than ``inactive_down_rate`` and whose upload rate is less than +``inactive_up_rate`` for ``auto_manage_startup`` seconds, is +considered inactive, and another queued torrent may be started. +This logic is disabled if ``dont_count_slow_torrents`` is false. + +.. _proxy_type: + +.. raw:: html + + + ++------------+------+---------------------+ +| name | type | default | ++============+======+=====================+ +| proxy_type | int | settings_pack::none | ++------------+------+---------------------+ + +proxy to use. see `proxy_type_t`__. + +.. _proxy_port: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| proxy_port | int | 0 | ++------------+------+---------+ + +the port of the proxy server + +.. _i2p_port: + +.. raw:: html + + + ++----------+------+---------+ +| name | type | default | ++==========+======+=========+ +| i2p_port | int | 0 | ++----------+------+---------+ + +sets the i2p_ SAM bridge port to connect to. set the hostname with +the ``i2p_hostname`` setting. + +.. _i2p: http://www.i2p2.de + +.. _urlseed_max_request_bytes: + +.. raw:: html + + + ++---------------------------+------+------------------+ +| name | type | default | ++===========================+======+==================+ +| urlseed_max_request_bytes | int | 16 * 1024 * 1024 | ++---------------------------+------+------------------+ + +The maximum request range of an url seed in bytes. This value +defines the largest possible sequential web seed request. Lower values +are possible but will be ignored if they are lower then piece size. +This value should be related to your download speed to prevent +libtorrent from creating too many expensive http requests per +second. You can select a value as high as you want but keep in mind +that libtorrent can't create parallel requests if the first request +did already select the whole file. +If you combine bittorrent seeds with web seeds and pick strategies +like rarest first you may find your web seed requests split into +smaller parts because we don't download already picked pieces +twice. + +.. _web_seed_name_lookup_retry: + +.. raw:: html + + + ++----------------------------+------+---------+ +| name | type | default | ++============================+======+=========+ +| web_seed_name_lookup_retry | int | 1800 | ++----------------------------+------+---------+ + +time to wait until a new retry of a web seed name lookup + +.. _close_file_interval: + +.. raw:: html + + + ++---------------------+------+---------------------+ +| name | type | default | ++=====================+======+=====================+ +| close_file_interval | int | CLOSE_FILE_INTERVAL | ++---------------------+------+---------------------+ + +the number of seconds between closing the file opened the longest +ago. 0 means to disable the feature. The purpose of this is to +periodically close files to trigger the operating system flushing +disk cache. Specifically it has been observed to be required on +windows to not have the disk cache grow indefinitely. +This defaults to 240 seconds on windows, and disabled on other +systems. + +.. _utp_cwnd_reduce_timer: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| utp_cwnd_reduce_timer | int | 100 | ++-----------------------+------+---------+ + +When uTP experiences packet loss, it will reduce the congestion +window, and not reduce it again for this many milliseconds, even if +experiencing another lost packet. + +.. _max_web_seed_connections: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| max_web_seed_connections | int | 3 | ++--------------------------+------+---------+ + +the max number of web seeds to have connected per torrent at any +given time. + +.. _resolver_cache_timeout: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| resolver_cache_timeout | int | 1200 | ++------------------------+------+---------+ + +the number of seconds before the internal host name resolver +considers a cache value timed out, negative values are interpreted +as zero. + +.. _send_not_sent_low_watermark: + +.. raw:: html + + + ++-----------------------------+------+---------+ +| name | type | default | ++=============================+======+=========+ +| send_not_sent_low_watermark | int | 16384 | ++-----------------------------+------+---------+ + +specify the not-sent low watermark for socket send buffers. This +corresponds to the, Linux-specific, ``TCP_NOTSENT_LOWAT`` TCP socket +option. + +.. _rate_choker_initial_threshold: + +.. raw:: html + + + ++-------------------------------+------+---------+ +| name | type | default | ++===============================+======+=========+ +| rate_choker_initial_threshold | int | 1024 | ++-------------------------------+------+---------+ + +the rate based choker compares the upload rate to peers against a +threshold that increases proportionally by its size for every +peer it visits, visiting peers in decreasing upload rate. The +number of upload slots is determined by the number of peers whose +upload rate exceeds the threshold. This option sets the start +value for this threshold. A higher value leads to fewer unchoke +slots, a lower value leads to more. + +.. _upnp_lease_duration: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| upnp_lease_duration | int | 3600 | ++---------------------+------+---------+ + +The expiration time of UPnP port-mappings, specified in seconds. 0 +means permanent lease. Some routers do not support expiration times +on port-maps (nor correctly returning an error indicating lack of +support). In those cases, set this to 0. Otherwise, don't set it any +lower than 5 minutes. + +.. _max_concurrent_http_announces: + +.. raw:: html + + + ++-------------------------------+------+---------+ +| name | type | default | ++===============================+======+=========+ +| max_concurrent_http_announces | int | 50 | ++-------------------------------+------+---------+ + +limits the number of concurrent HTTP tracker announces. Once the +limit is hit, tracker requests are queued and issued when an +outstanding announce completes. + +.. _dht_max_peers_reply: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| dht_max_peers_reply | int | 100 | ++---------------------+------+---------+ + +the maximum number of peers to send in a reply to ``get_peers`` + +.. _dht_search_branching: + +.. raw:: html + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| dht_search_branching | int | 5 | ++----------------------+------+---------+ + +the number of concurrent search request the node will send when +announcing and refreshing the routing table. This parameter is called +alpha in the kademlia paper + +.. _dht_max_fail_count: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| dht_max_fail_count | int | 20 | ++--------------------+------+---------+ + +the maximum number of failed tries to contact a node before it is +removed from the routing table. If there are known working nodes that +are ready to replace a failing node, it will be replaced immediately, +this limit is only used to clear out nodes that don't have any node +that can replace them. + +.. _dht_max_torrents: + +.. raw:: html + + + ++------------------+------+---------+ +| name | type | default | ++==================+======+=========+ +| dht_max_torrents | int | 2000 | ++------------------+------+---------+ + +the total number of torrents to track from the DHT. This is simply an +upper limit to make sure malicious DHT nodes cannot make us allocate +an unbounded amount of memory. + +.. _dht_max_dht_items: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| dht_max_dht_items | int | 700 | ++-------------------+------+---------+ + +max number of items the DHT will store + +.. _dht_max_peers: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| dht_max_peers | int | 500 | ++---------------+------+---------+ + +the max number of peers to store per torrent (for the DHT) + +.. _dht_max_torrent_search_reply: + +.. raw:: html + + + ++------------------------------+------+---------+ +| name | type | default | ++==============================+======+=========+ +| dht_max_torrent_search_reply | int | 20 | ++------------------------------+------+---------+ + +the max number of torrents to return in a torrent search query to the +DHT + +.. _dht_block_timeout: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| dht_block_timeout | int | 5 * 60 | ++-------------------+------+---------+ + +the number of seconds a DHT node is banned if it exceeds the rate +limit. The rate limit is averaged over 10 seconds to allow for bursts +above the limit. + +.. _dht_block_ratelimit: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| dht_block_ratelimit | int | 5 | ++---------------------+------+---------+ + +the max number of packets per second a DHT node is allowed to send +without getting banned. + +.. _dht_item_lifetime: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| dht_item_lifetime | int | 0 | ++-------------------+------+---------+ + +the number of seconds a immutable/mutable item will be expired. +default is 0, means never expires. + +.. _dht_sample_infohashes_interval: + +.. raw:: html + + + ++--------------------------------+------+---------+ +| name | type | default | ++================================+======+=========+ +| dht_sample_infohashes_interval | int | 21600 | ++--------------------------------+------+---------+ + +the info-hashes sample recomputation interval (in seconds). +The node will precompute a subset of the tracked info-hashes and return +that instead of calculating it upon each request. The permissible range +is between 0 and 21600 seconds (inclusive). + +.. _dht_max_infohashes_sample_count: + +.. raw:: html + + + ++---------------------------------+------+---------+ +| name | type | default | ++=================================+======+=========+ +| dht_max_infohashes_sample_count | int | 20 | ++---------------------------------+------+---------+ + +the maximum number of elements in the sampled subset of info-hashes. +If this number is too big, expect the DHT storage implementations +to clamp it in order to allow UDP packets go through + +.. _max_piece_count: + +.. raw:: html + + + ++-----------------+------+----------+ +| name | type | default | ++=================+======+==========+ +| max_piece_count | int | 0x200000 | ++-----------------+------+----------+ + +``max_piece_count`` is the maximum allowed number of pieces in +metadata received via magnet links. Loading large torrents (with +more pieces than the default limit) may also require passing in +a higher limit to `read_resume_data()`__ and +`torrent_info::parse_info_section()`__, if those are used. + +.. _metadata_token_limit: + +.. raw:: html + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| metadata_token_limit | int | 2500000 | ++----------------------+------+---------+ + +when receiving metadata (torrent file) from peers, this is the +max number of bencoded tokens we're willing to parse. This limit +is meant to prevent DoS attacks on peers. For very large +torrents, this limit may have to be raised. + +.. _disk_write_mode: + +.. raw:: html + + + ++-----------------+------+---------------------------------------------------+ +| name | type | default | ++=================+======+===================================================+ +| disk_write_mode | int | settings_pack::mmap_write_mode_t::auto_mmap_write | ++-----------------+------+---------------------------------------------------+ + +controls whether disk writes will be made through a memory mapped +file or via normal write calls. This only affects the +mmap_disk_io. When saving to a non-local drive (network share, +NFS or NAS) using memory mapped files is most likely inferior. +When writing to a local SSD (especially in DAX mode) using memory +mapped files likely gives the best performance. +The values for this setting are specified as `mmap_write_mode_t`__. + +.. _mmap_file_size_cutoff: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| mmap_file_size_cutoff | int | 40 | ++-----------------------+------+---------+ + +when using mmap_disk_io, files smaller than this number of blocks +will not be memory mapped, but will use normal pread/pwrite +operations. This file size limit is specified in 16 kiB blocks. + +.. _i2p_inbound_quantity: + +.. _i2p_outbound_quantity: + +.. _i2p_inbound_length: + +.. _i2p_outbound_length: + +.. raw:: html + + + + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| i2p_inbound_quantity | int | 3 | ++-----------------------+------+---------+ +| i2p_outbound_quantity | int | 3 | ++-----------------------+------+---------+ +| i2p_inbound_length | int | 3 | ++-----------------------+------+---------+ +| i2p_outbound_length | int | 3 | ++-----------------------+------+---------+ + +Configures the SAM `session`__ +quantity of I2P inbound and outbound tunnels [1..16]. +number of hops for I2P inbound and outbound tunnels [0..7] +Changing these will not trigger a reconnect to the SAM bridge, +they will take effect the next time the SAM connection is +re-established (by restarting or changing i2p_hostname or +i2p_port). + +.. _announce_port: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| announce_port | int | 0 | ++---------------+------+---------+ + +``announce_port`` is the port passed along as the ``port`` parameter +to remote trackers such as HTTP or DHT. This setting does not affect +the effective listening port nor local service discovery announcements. +If left as zero (default), the listening port value is used. + +.. note:: + This setting is only meant for very special cases where a + seed's listening port differs from the external port. As an + example, if a local proxy is used and that the proxy supports + reverse tunnels through NAT-PMP, the tracker must connect to + the external NAT-PMP port (configured using ``announce_port``) + instead of the actual local listening port. + + +__ reference-Alerts.html#peer_blocked_alert +__ reference-Alerts.html#listen_failed_alert +__ reference-Alerts.html#listen_succeeded_alert +__ reference-Settings.html#outgoing_interfaces +__ reference-Settings.html#listen_interfaces +__ reference-Settings.html#generate_fingerprint() +__ reference-Alerts.html#portmap_alert +__ reference-Alerts.html#portmap_error_alert +__ manual-ref.html#upnp-and-nat-pmp +__ reference-Alerts.html#portmap_alert +__ reference-Alerts.html#portmap_error_alert +__ manual-ref.html#upnp-and-nat-pmp +__ reference-Bencoding.html#entry +__ reference-Session.html#session +__ reference-Settings.html#choking_algorithm_t +__ reference-Settings.html#seed_choking_algorithm_t +__ manual-ref.html#queuing +__ manual-ref.html#peer-classes +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Alerts.html#alert +__ reference-Settings.html#aio_threads +__ reference-Utility.html#hasher +__ manual-ref.html#queuing +__ reference-Alerts.html#alert_category_t +__ reference-Settings.html#enc_policy +__ reference-Settings.html#enc_level +__ reference-Settings.html#proxy_type_t +__ reference-Resume_Data.html#read_resume_data() +__ reference-Torrent_Info.html#parse_info_section() +__ reference-Settings.html#mmap_write_mode_t +__ reference-Session.html#session + diff --git a/docs/streaming.rst b/docs/streaming.rst new file mode 100644 index 0000000..254f766 --- /dev/null +++ b/docs/streaming.rst @@ -0,0 +1,147 @@ +Streaming implementation +======================== + +.. include:: header.rst + +This documents describes the algorithm libtorrent uses to satisfy time critical +piece requests, i.e. streaming. + +streaming vs sequential_download +-------------------------------- + +Libtorrent's ``sequential_download`` mode and the time-critical logic can be +understood as two different ways of managing *peer request queues*. + +``sequential_download`` will simply wait until a queue slot opens up, and +request the next piece in the sequence. This mechanism is even simpler than the +classic "rarest-first" algorithm; it does a good job of keeping request queues +full, thus saturating available download bandwidth; and pieces do arrive +*roughly* in-order. However, it's sub-optimal for streaming: piece 0 may be +requested from a slow peer, and fast peers will get requests for later-index +pieces instead of retrying more-critical ones. + +The time-critical logic does more *active management* of peer request queues, +such that the most time-critical pieces occupy the "best" queue slots, across +all peers. It can be considered an advanced version of ``sequential_download``. +The main trade-off is that it is more complex to implement and utilize. + +piece picking +------------- + +The standard bittorrent piece picker is peer-centric. A peer unchokes us or we +complete a block from a peer and we want to make another request to that peer. +The piece picker answers the question: which block should we request from this +peer. + +When streaming, we have a number of *time critical* pieces, the ones the video +or audio player will need next to keep up with the stream. To keep the deadlines +of these pieces, we need a mechanism to answer the question: I want to request +blocks from this piece, which peer is the most likely to be able to deliver it +to me the soonest. + +This question is answered by ``torrent::request_time_critical_pieces()`` in +libtorrent. + +At a high level, this algorithm keeps a list of peers, sorted by the estimated +download queue time. That is, the estimated time for a new request to this +peer to be received. The bottom 10th percentile of the peers (the 10% slowest +peers) are ignored and not included in the peer list. Peers that have choked +us, are not interesting, is on parole, disconnecting, have too many outstanding +block requests or is snubbed are also excluded from the peer list. + +The time critical pieces are also kept sorted by their deadline. Pieces with +an earlier deadline first. This list of pieces is iterated, starting at the +top, and blocks are requested from a piece until we cannot make any more +requests from it. We then move on to the next piece and request blocks from it +until we cannot make any more. The peer each request is sent to is the one +with the lowest `download queue time`_. Each time a request is made, this +estimate is updated and the peer is resorted in this list. + +Any peer that doesn't have the piece is ignored until we move on to the next +piece. + +If the top peer's download queue time is more than 2 seconds, the loop is +terminated. This is to not over-request. ``request_time_critical_pieces()`` +is called once per second, so this will keep the queue full with margin. + +download queue time +------------------- + +Each peer maintains the number of bytes that have been requested from it but +not yet been received. This is referred to as ``outstanding_bytes``. This number +is incremented by the size of each outgoing request and decremented for each +*payload* byte received. + +This counter is divided by an estimated download rate from the peer to form +the estimated *download queue time*. That is, the estimated time it will take +any new request to this peer to begin being received. + +The estimated download rate of a peer is not trivial. There may not be any +outstanding requests to the peer, in which case the payload download rate +will be zero. That would not be a reasonable estimate of the rate we would see +once we make a request. + +If we have not received any payload from a peer in the last 30 seconds, we +must use an alternative estimate of the download rate. If we have received +payload from this peer previously, we can use the peak download rate. + +If we have received less than 2 blocks (32 kiB) and we have been unchoked for +less than 5 seconds ago, use the average download rate of all peers (that have +outstanding requests). + +timeouts +-------- + +An observation that is useful to keep in mind when streaming is that your +download capacity is likely to be saturated by your peers. In this case, if the +swarm is well seeded, most peers will send data to you at close to the same +rate. This makes it important to support streaming from many slow peers. For +instance, this means you can't make assumptions about the download time of a +block being less than some absolute time. You may be downloading at well above +the bit rate of the video, but each individual peer only transfers at 5 kiB/s. + +In this state, your download rate is a zero-sum-game. Any block you request +that is not urgent, will take away from the bandwidth you get for peers that +are urgent. Make sure to limit requests to useful blocks only. + +Some requests will stall. It appears to be very hard to have enough accuracy in +the prediction of download queue time such that all requests come back within a +reasonable amount of time. + +To support adaptive timeouts, each torrent maintains a running average of how +long it takes to complete a piece. There is also a running average of the +deviation from the mean download time. + +This download time is used as the benchmark to determine when blocks have +timed out, and should be re-requested from another peer. + +If any time-critical piece has taken more than the average piece download +time + a half average deviation form that, the piece is considered to have +timed out. This means we are allowed to double-request blocks. Subsequent +passes over this piece will make sure that any blocks we don't already have +are requested one more time. + +In fact, this scales to multiple time-outs. The time since a download was +started is divided by average download time + average deviation time / 2. +The resulting integer is the number if *times* the piece has timed out. + +Each time a piece times out, another *busy request* is allowed to try to make +it complete sooner. A busy request is where a block is requested from a peer +even though it has already been requested from another peer. + +This has the effect of getting more and more aggressive in requesting blocks +the longer it takes to complete the piece. If this mechanism is too aggressive, +a significant amount of bandwidth may be lost in redundant download (keep in +mind the zero-sum game). + +It never makes sense to request a block twice from the same peer. There is logic +in place to prevent this. + +optimizations +------------- + +One optimization is to buffer all piece requests while looping over the time- +critical pieces and not send them until one round is complete. This increases +the chances that the request messages are coalesced into the same packet. +This in turn lowers the number of system calls and network overhead. + diff --git a/docs/style.css b/docs/style.css new file mode 100644 index 0000000..c0b3912 --- /dev/null +++ b/docs/style.css @@ -0,0 +1,582 @@ +:root { + /* Nord Color Theme: https://www.nordtheme.com/ + Details about the palette and use cause of each color: https://www.nordtheme.com/docs/colors-and-palettes */ + + /* Polar Night */ + --nord0: #2e3440; + --nord1: #3b4252; + --nord2: #434c5e; + --nord3: #4c566a; + /* Snow Storm */ + --nord4: #d8dee9; + --nord5: #e5e9f0; + --nord6: #eceff4; + /* Frost */ + --nord7: #8fbcbb; + --nord8: #88c0d0; + --nord9: #81a1c1; + --nord10: #5e81ac; + /* Aurora */ + --nord11: #bf616a; + --nord12: #d08770; + --nord13: #ebcb8b; + --nord14: #a3be8c; + --nord15: #b48ead; +} + +@media (prefers-color-scheme: light) { + body { + background-color: white; + color: black; + } +} + +@media (prefers-color-scheme: dark) { + body { + background-color: var(--nord0); + color: var(--nord6); + } +} + +/* STYLE */ + +body { + margin: 0; + font-size: 12pt; +} + +dt { + margin-bottom: 0.3em; + font-style: italic; + font-weight: 600; +} + +dd { + margin-left: 2em; + margin-bottom: 1em; +} + +tt { + font-family: monospace; +} + +h1 { + font-size: 1.7em; +} + +h2 { + font-size: 1.5em; +} + +@media (prefers-color-scheme: light) { + tt { + background-color: rgb(0 0 0 / 5%); + } + + table { + border-color: #ccc; + } + + th, + td { + border-color: #ddd; + } + + th { + border-bottom-color: black; + } + + a.reference, + a { + color: #000070; + } + + hr { + border-color: #eee; + } +} + +@media (prefers-color-scheme: dark) { + tt { + background-color: var(--nord3); + } + + table { + border-color: var(--nord7); + } + + th, + td { + border-color: var(--nord2); + } + + th { + border-bottom-color: var(--nord7); + } + + a.reference, + a { + color: var(--nord8); + } + + hr { + border-color: var(--nord7); + } +} + +hr { + border-bottom-width: 1px; + border-style: solid; +} + +table { + margin-bottom: 1em; + border-collapse: collapse; + border-style: solid; +} + +th, +td { + padding: 0.2em; +} + +th { + border-bottom-style: solid; + border-bottom-width: 1px; +} + +a { + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +p.last { + margin-bottom: 0.3em; +} + +p.first { + margin-top: 0.3em; +} + +.align-right { + float: right; +} + +@media (prefers-color-scheme: dark) { + .bw { + filter: invert(1); + } +} + +/* TEMPLATE */ + +@media (prefers-color-scheme: light) { + #footer { + color: #777; + } + + #footer a { + color: #555; + } + + #footer a:hover { + color: #000; + } + + #gradient { + background: linear-gradient(#aaa, #ddd); + } + + #filler { + background: linear-gradient(#ddd, #fff); + } +} + +@media (prefers-color-scheme: dark) { + #footer { + color: var(--nord5); + } + + #footer a { + color: var(--nord9); + } + + #footer a:hover { + color: var(--nord8); + } + + #gradient { + background: var(--nord1); + } + + #filler { + background: linear-gradient(var(--nord1), var(--nord0)); + } +} + +#container { + text-align: left; + max-width: 60em; + margin: 5px auto; + position: relative; + padding: 3px; +} + +#gradient { + height: 40px; +} + +#filler { + min-height: 400px; + height: 100%; +} + +#footer { + margin-bottom: 0; + margin-left: auto; + margin-right: auto; + columns: 7em 3; + max-width: 40em; +} + +#footer a { + text-decoration: none; +} + +#footer a:hover { + text-decoration: underline; +} + +table.docinfo { + float: right; + width: 200px; + margin-right: 0; + margin-left: 20px; + margin-bottom: 20px; + border: none; +} + +@media screen and (width <= 499px) { + table.docinfo { + display: none; + } +} + +table.docinfo th { + text-align: right; + background-color: transparent; + border: none; +} + +table.docinfo td { + padding-left: 10px; +} + +/* FRONT PAGE */ + +@media screen and (width >= 500px) { + #librarySidebar { + float: left; + width: 13em; + } + + #libraryBody { + margin-left: 13em; + } +} + +@media screen and (width <= 240px) { + #librarySidebar ul { + list-style-type: none; + padding-inline-start: 0; + } +} + +#librarySidebar li { + padding-bottom: 0.35em; +} + +@media (prefers-color-scheme: light) { + #libraryBody { + border-color: #eee; + } +} + +@media (prefers-color-scheme: dark) { + #libraryBody { + border-color: var(--nord7); + } +} + +#libraryBody { + border-left-style: solid; + border-left-width: 1px; + padding-left: 10px; + margin-right: 10px; +} + +.screenshot { + width: 100%; +} + +.front-page-screenshot { + float: right; +} + +@media screen and (width <= 890px) { + .front-page-screenshot { + display: none; + } +} + +.front-page-qr { + float: right; + clear: right; +} + +@media screen and (width <= 600px) { + .front-page-qr { + display: none; + } +} + +.report-issue { + float: right; + font-size: 90%; +} + +/* REFERENCE MAIN TABLE OF CONTENT */ + +@media (prefers-color-scheme: light) { + div.main-toc { + border-color: #999; + } +} + +@media (prefers-color-scheme: dark) { + div.main-toc { + border-color: var(--nord3); + } +} + +div.main-toc { + columns: 13em 4; + border-style: solid; + border-width: 1px; + padding: 5px; + margin-bottom: 10px; +} + +.rubric { + margin-top: 5px; + margin-bottom: 5px; + font-size: 120%; + font-weight: bold; +} + +/* TABLE OF CONTENT */ + +@media (prefers-color-scheme: light) { + #table-of-contents { + background-color: white; + border-color: #a1c5d6; + } +} + +@media (prefers-color-scheme: dark) { + #table-of-contents { + background-color: var(--nord1); + border-color: var(--nord10); + } +} + +#table-of-contents { + margin-left: 20px; + padding: 0.8em; + border-style: solid; + border-width: 1px; + position: relative; + z-index: 1; +} + +@media screen and (width >= 500px) { + #table-of-contents { + width: 15em; + float: right; + clear: right; + } +} + +#table-of-contents p { + font-size: 140%; + font-weight: bold; + padding-bottom: 0.5em; + margin: 0; +} + +#table-of-contents ul { + margin: 0; + padding: 0 0 0 0.8em; + list-style: square; + text-align: left; + line-height: 1.5em; +} + +@media screen and (width <= 319px) { + #table-of-contents ul { + list-style-type: none; + padding-inline-start: 0; + } +} + +#table-of-contents a.reference { + border: none; + font-weight: bold; +} + +#table-of-contents li li a.reference { + font-weight: normal; + padding: 0; +} + +/* CODE BLOCKS */ + +@media (prefers-color-scheme: light) { + pre { + background: #f6f6f6; + border-color: #bbb; + } +} + +@media (prefers-color-scheme: dark) { + pre { + background: var(--nord2); + border-color: var(--nord1); + } +} + +pre { + font-family: monospace; + padding: 5px 10px; + border-style: solid; + border-width: 1px; + margin: 1em 0; +} + +/* SYNTAX HIGHLIGHTING */ + +.keyword { + font-weight: bold; +} + +@media (prefers-color-scheme: light) { + .string { + color: #771; + } + + .comment { + font-style: italic; + color: #559; + } + + .preproc { + font-style: italic; + color: #959; + } + + .number { + color: #595; + } +} + +@media (prefers-color-scheme: dark) { + .string { + color: var(--nord14); + } + + .comment { + font-style: italic; + color: var(--nord10); + } + + .preproc { + font-style: italic; + color: var(--nord10); + } + + .number { + color: var(--nord15); + } +} + +/* ALERT BOXES */ + +@media (prefers-color-scheme: light) { + div.warning, + div.note, + div.important { + background: #f1fff5; + border-color: #d1dfd5; + } + + div.warning { + background: #fffdca; + border-color: #dddd80; + } + + div.note .admonition-title { + border-bottom-color: #d1dfd5; + } + + div.warning .admonition-title { + border-bottom-color: #dddd80; + } +} + +@media (prefers-color-scheme: dark) { + div.note, + div.important { + background: #0f3a0f; + border-color: #5d9e5d; + } + + div.warning { + background: #666507; + border-color: #dbd818; + } + + div.warning .admonition-title { + border-bottom-color: #dbd818; + } + + div.note .admonition-title { + border-bottom-color: #5d9e5d; + } +} + +div.warning, +div.note, +div.important { + width: 80%; + margin: 1.5em auto; + border-style: solid; + border-width: 1px; + padding: 5px 10px; +} + +div.warning { + border-style: solid; + border-width: 1px; +} + +p.admonition-title { + font-size: 128%; + letter-spacing: 2px; + text-transform: uppercase; + margin: 0 0 0.5em; + border-bottom-style: solid; + border-bottom-width: 1px; +} diff --git a/docs/stylesheet b/docs/stylesheet new file mode 100644 index 0000000..3beb380 --- /dev/null +++ b/docs/stylesheet @@ -0,0 +1,287 @@ +{ + "embeddedFonts" : [ ], + "pageSetup" : { + "size": "A4", + "width": null, + "height": null, + "margin-top": "2cm", + "margin-bottom": "2cm", + "margin-left": "2cm", + "margin-right": "2cm", + "margin-gutter": "0cm", + "firstTemplate": "oneColumn" + }, + "pageTemplates" : { + "coverPage": { + "frames": [ + ["0cm", "0cm", "100%", "100%"] + ], + "showHeader" : false, + "showFooter" : false + }, + "oneColumn": { + "frames": [ + ["0cm", "0cm", "100%", "100%"] + ] + }, + "twoColumn": { + "frames": [ + ["0cm", "0cm", "49%", "100%"], + ["51%", "0cm", "49%", "100%"] + ] + } + }, + "fontsAlias" : { + "stdFont": "Times-Roman", + "stdBold": "Times-Bold", + "stdItalic": "Times-Italic", + "stdBoldItalic": "Times-BoldItalic", + "stdMono": "Courier", + "stdMonoItalic": "Courier-Oblique", + "stdMonoBold": "Courier-Bold", + "stdMonoBoldItalic": "Courier-BoldOblique", + "stdSerif": "Times-Roman" + }, + "linkColor" : "black", + "styles" : [ + [ "base" , { + "parent": null, + "fontName": "stdFont", + "fontSize":10, + "leading":12, + "leftIndent":0, + "rightIndent":0, + "firstLineIndent":0, + "alignment":"TA_LEFT", + "spaceBefore":0, + "spaceAfter":0, + "bulletFontName":"stdFont", + "bulletFontSize":10, + "bulletIndent":0, + "textColor": "black", + "backColor": null, + "wordWrap": null, + "borderWidth": 0, + "borderPadding": 0, + "borderColor": null, + "borderRadius": null, + "allowWidows": false, + "allowOrphans": false, + "hyphenation": false + }] , + ["normal" , { + "parent": "base" + }], + ["title-reference" , { + "parent": "normal", + "fontName": "stdItalic" + }], + ["bodytext" , { + "parent": "normal", + "spaceBefore":6, + "alignment": "TA_JUSTIFY", + "hyphenation": true + }], + ["footer" , { + "parent": "normal", + "alignment": "TA_CENTER" + }], + ["header" , { + "parent": "normal", + "alignment": "TA_CENTER" + }], + ["attribution" , { + "parent": "bodytext", + "alignment": "TA_RIGHT" + }], + ["figure" , { + "parent": "bodytext", + "alignment": "TA_CENTER" + }], + ["definition-list-term" , { + "parent": "normal", + "fontName": "stdBold", + "spaceBefore": 4, + "spaceAfter": 0, + "keepWithNext": true + }], + ["definition-list-classifier" , { + "parent": "normal", + "fontName": "stdItalic" + }], + ["definition" , { + "parent": "bodytext", + "firstLineIndent": 0, + "bulletIndent": 0, + "spaceBefore": 0 + }], + ["fieldname" , { + "parent": "bodytext", + "alignment": "TA_RIGHT", + "fontName": "stdBold" + }], + ["rubric" , { + "parent": "bodytext", + "textColor": "darkred", + "alignment": "TA_CENTER" + }], + ["italic" , { + "parent": "bodytext", + "fontName": "stdItalic" + }], + ["title" , { + "parent": "normal", + "fontName": "stdBold", + "fontSize": "200%", + "alignment": "TA_CENTER", + "spaceBefore": 12, + "spaceAfter": 10 + }], + ["subtitle" , { + "parent": "title", + "spaceBefore": 9, + "spaceAfter": 6, + "fontSize": "75%" + }], + ["heading1" , { + "parent": "normal", + "fontName": "stdBold", + "fontSize": "150%", + "keepWithNext": true, + "spaceBefore": 9, + "spaceAfter": 3 + }], + ["heading2" , { + "parent": "normal", + "fontName": "stdBold", + "fontSize": "125%", + "keepWithNext": true, + "spaceBefore": 9, + "spaceAfter": 3 + }], + ["heading3" , { + "parent": "normal", + "fontName": "stdBold", + "keepWithNext": true, + "spaceBefore": 4, + "spaceAfter": 2 + }], + ["heading4" , { + "parent": "normal", + "fontName": "stdBold", + "keepWithNext": true + }], + ["sidebar-title" , { + "parent": "heading3" + }], + ["sidebar-subtitle" , { + "parent": "heading4" + }], + ["sidebar" , { + "float": "left", + "width": "30%", + "parent": "normal", + "backColor": "beige", + "borderColor": "darkgray", + "borderPadding": 8, + "borderWidth": 0.5 + }], + ["literal" , { + "parent": "normal", + "fontName": "stdMono", + "firstLineIndent": 0 + }], + ["table" , { + "rowBackgrounds" : ["f0f0d8","#ffffe8"], + "borderColor": "white" + }], + ["table-title" , { + "parent" : "heading4", + "backColor" : "#e0e0c8", + "alignment" : "TA_CENTER" + }], + ["table-heading" , { + "parent" : "heading4", + "backColor" : "#e0e0c0", + "alignment" : "TA_CENTER", + "valign" : "BOTTOM" + }], + ["code" , { + "parent": "literal", + "fontSize": "75%", + "leftIndent": 0, + "spaceBefore": 5, + "spaceAfter": 5, + "backColor": "#e7e7e7", + "borderColor": "#808080", + "borderRadius": 0, + "borderWidth": 0.5, + "borderPadding": 4 + }], + ["pygments-n" , {"parent": "code"}], + ["pygments-nx" , {"parent": "code"}], + ["pygments-p" , {"parent": "code"}], + ["pygments-hll", {"parent": "code", "backColor": "#ffffcc"}], + ["pygments-c", {"textColor": "#008800", "parent": "code"}], + ["pygments-err", {"parent": "code"}], + ["pygments-k", {"textColor": "#AA22FF", "parent": "code"}], + ["pygments-o", {"textColor": "#666666", "parent": "code"}], + ["pygments-cm", {"textColor": "#008800", "parent": "code"}], + ["pygments-cp", {"textColor": "#008800", "parent": "code"}], + ["pygments-c1", {"textColor": "#008800", "parent": "code"}], + ["pygments-cs", {"textColor": "#008800", "parent": "code"}], + ["pygments-gd", {"textColor": "#A00000", "parent": "code"}], + ["pygments-ge", {"parent": "code"}], + ["pygments-gr", {"textColor": "#FF0000", "parent": "code"}], + ["pygments-gh", {"textColor": "#000080", "parent": "code"}], + ["pygments-gi", {"textColor": "#00A000", "parent": "code"}], + ["pygments-go", {"textColor": "#808080", "parent": "code"}], + ["pygments-gp", {"textColor": "#000080", "parent": "code"}], + ["pygments-gs", {"parent": "code"}], + ["pygments-gu", {"textColor": "#800080", "parent": "code"}], + ["pygments-gt", {"textColor": "#0040D0", "parent": "code"}], + ["pygments-kc", {"textColor": "#AA22FF", "parent": "code"}], + ["pygments-kd", {"textColor": "#AA22FF", "parent": "code"}], + ["pygments-kn", {"textColor": "#AA22FF", "parent": "code"}], + ["pygments-kp", {"textColor": "#AA22FF", "parent": "code"}], + ["pygments-kr", {"textColor": "#AA22FF", "parent": "code"}], + ["pygments-kt", {"textColor": "#00BB00", "parent": "code"}], + ["pygments-m", {"textColor": "#666666", "parent": "code"}], + ["pygments-s", {"textColor": "#BB4444", "parent": "code"}], + ["pygments-na", {"textColor": "#BB4444", "parent": "code"}], + ["pygments-nb", {"textColor": "#AA22FF", "parent": "code"}], + ["pygments-nc", {"textColor": "#0000FF", "parent": "code"}], + ["pygments-no", {"textColor": "#880000", "parent": "code"}], + ["pygments-nd", {"textColor": "#AA22FF", "parent": "code"}], + ["pygments-ni", {"textColor": "#999999", "parent": "code"}], + ["pygments-ne", {"textColor": "#D2413A", "parent": "code"}], + ["pygments-nf", {"textColor": "#00A000", "parent": "code"}], + ["pygments-nl", {"textColor": "#A0A000", "parent": "code"}], + ["pygments-nn", {"textColor": "#0000FF", "parent": "code"}], + ["pygments-nt", {"textColor": "#008000", "parent": "code"}], + ["pygments-nv", {"textColor": "#B8860B", "parent": "code"}], + ["pygments-ow", {"textColor": "#AA22FF", "parent": "code"}], + ["pygments-w", {"textColor": "#bbbbbb", "parent": "code"}], + ["pygments-mf", {"textColor": "#666666", "parent": "code"}], + ["pygments-mh", {"textColor": "#666666", "parent": "code"}], + ["pygments-mi", {"textColor": "#666666", "parent": "code"}], + ["pygments-mo", {"textColor": "#666666", "parent": "code"}], + ["pygments-sb", {"textColor": "#BB4444", "parent": "code"}], + ["pygments-sc", {"textColor": "#BB4444", "parent": "code"}], + ["pygments-sd", {"textColor": "#BB4444", "parent": "code"}], + ["pygments-s2", {"textColor": "#BB4444", "parent": "code"}], + ["pygments-se", {"textColor": "#BB6622", "parent": "code"}], + ["pygments-sh", {"textColor": "#BB4444", "parent": "code"}], + ["pygments-si", {"textColor": "#BB6688", "parent": "code"}], + ["pygments-sx", {"textColor": "#008000", "parent": "code"}], + ["pygments-sr", {"textColor": "#BB6688", "parent": "code"}], + ["pygments-s1", {"textColor": "#BB4444", "parent": "code"}], + ["pygments-ss", {"textColor": "#B8860B", "parent": "code"}], + ["pygments-bp", {"textColor": "#AA22FF", "parent": "code"}], + ["pygments-vc", {"textColor": "#B8860B", "parent": "code"}], + ["pygments-vg", {"textColor": "#B8860B", "parent": "code"}], + ["pygments-vi", {"textColor": "#B8860B", "parent": "code"}], + ["pygments-il", {"textColor": "#666666", "parent": "code"}] + ] +} + diff --git a/docs/template.txt b/docs/template.txt new file mode 100644 index 0000000..6d56ba4 --- /dev/null +++ b/docs/template.txt @@ -0,0 +1,42 @@ + +%(head_prefix)s +%(head)s + + + + +%(stylesheet)s +%(body_prefix)s +
    + + libtorrent logo + +
    +%(body_pre_docinfo)s +%(docinfo)s +%(body)s + +
    +
    +
    + + +%(body_suffix)s diff --git a/docs/template2.txt b/docs/template2.txt new file mode 100644 index 0000000..6fa8146 --- /dev/null +++ b/docs/template2.txt @@ -0,0 +1,45 @@ + +%(head_prefix)s +%(head)s + + + + +%(stylesheet)s +%(body_prefix)s +
    + +
    +%(body_pre_docinfo)s +%(docinfo)s +
    + +
    +%(body)s + +
    +
    +
    + + +%(body_suffix)s diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst new file mode 100644 index 0000000..a15fe3c --- /dev/null +++ b/docs/troubleshooting.rst @@ -0,0 +1,20 @@ +================= +libtorrent manual +================= + +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +The following troubleshooting chart may help in finding out why torrents fail +to download. It is not complete, please submit suggestions via pull requests at +https://github.com/arvidn/libtorrent or to the `mailing list`_. Ideally in the +form of patches against ``docs/troubleshooting.dot``. + +.. _`mailing list`: https://lists.sourceforge.net/lists/listinfo/libtorrent-discuss + +.. image:: img/troubleshooting_thumb.png + :target: img/troubleshooting.png + diff --git a/docs/troubleshooting_thumb.png b/docs/troubleshooting_thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..9858aa6cf88f693e583d6b349228679cdbf9555f GIT binary patch literal 45948 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 VU_JGWg}C5 alerts; + ses.pop_alerts(&alerts); + + for (auto* a : alerts) { + if (lt::alert_cast(a) + || lt::alert_cast(a)) + { + std::cout << a->message() << "\n"; + } + + // ... + } + +The alerts with data will have the type session_stats_alert and there is one +session_stats_header_alert that will be posted on startup containing the column names +for all metrics. Logging this line will greatly simplify interpreting the output, +and is required for the script to work out-of-the-box. + +The python script in ``tools/parse_session_stats.py`` can parse the resulting +file and produce graphs of relevant stats. It requires gnuplot_. + +.. _gnuplot: http://www.gnuplot.info + +reducing memory footprint +========================= + +These are things you can do to reduce the memory footprint of libtorrent. You get +some of this by basing your default settings_pack on the min_memory_usage() +setting preset function. + +Keep in mind that lowering memory usage will affect performance, always profile +and benchmark your settings to determine if it's worth the trade-off. + +The bytes of receive buffers is proportional to the number of connections we +make, and is limited by the total number of connections in the session (default +is 200). + +The bytes of send buffers is proportional to the number of upload slots that are +allowed in the session. The default is auto configured based on the observed +upload rate. + +remove torrents +--------------- + +Torrents that have been added to libtorrent will inevitably use up memory, even +when it's paused. A paused torrent will not use any peer connection objects or +any send or receive buffers though. Any added torrent holds the entire .torrent +file in memory, it also remembers the entire list of peers that it's heard about +(which can be fairly long unless it's capped). It also retains information about +which blocks and pieces we have on disk, which can be significant for torrents +with many pieces. + +If you need to minimize the memory footprint, consider removing torrents from +the session rather than pausing them. This will likely only make a difference +when you have a very large number of torrents in a session. + +The downside of removing them is that they will no longer be auto-managed. Paused +auto managed torrents are scraped periodically, to determine which torrents are +in the greatest need of seeding, and libtorrent will prioritize to seed those. + +socket buffer sizes +------------------- + +You can make libtorrent explicitly set the kernel buffer sizes of all its peer +sockets. If you set this to a low number, you may see reduced throughput, especially +for high latency connections. It is however an opportunity to save memory per +connection, and might be worth considering if you have a very large number of +peer connections. This memory will not be visible in your process, this sets +the amount of kernel memory is used for your sockets. + +Change this by setting settings_pack::recv_socket_buffer_size and +settings_pack::send_socket_buffer_size. + +peer list size +-------------- + +The default maximum for the peer list is 4000 peers. For IPv4 peers, each peer +entry uses 32 bytes, which ends up using 128 kB per torrent. If seeding 4 popular +torrents, the peer lists alone uses about half a megabyte. + +The default limit is the same for paused torrents as well, so if you have a +large number of paused torrents (that are popular) it will be even more +significant. + +If you're short of memory, you should consider lowering the limit. 500 is probably +enough. You can do this by setting settings_pack::max_peerlist_size to +the max number of peers you want in a torrent's peer list. This limit applies per +torrent. For 5 torrents, the total number of peers in peer lists will be 5 times +the setting. + +You should also lower the same limit but for paused torrents. It might even make sense +to set that even lower, since you only need a few peers to start up while waiting +for the tracker and DHT to give you fresh ones. The max peer list size for paused +torrents is set by settings_pack::max_paused_peerlist_size. + +The drawback of lowering this number is that if you end up in a position where +the tracker is down for an extended period of time, your only hope of finding live +peers is to go through your list of all peers you've ever seen. Having a large +peer list will also help increase performance when starting up, since the torrent +can start connecting to peers in parallel with connecting to the tracker. + +send buffer watermark +--------------------- + +The send buffer watermark controls when libtorrent will ask the disk I/O thread +to read blocks from disk, and append it to a peer's send buffer. + +When the send buffer has fewer than or equal number of bytes as +settings_pack::send_buffer_watermark, the peer will ask the disk I/O thread +for more data to send. The trade-off here is between wasting memory by having too +much data in the send buffer, and hurting send rate by starving out the socket, +waiting for the disk read operation to complete. + +If your main objective is memory usage and you're not concerned about being able +to achieve high send rates, you can set the watermark to 9 bytes. This will guarantee +that no more than a single (16 kiB) block will be on the send buffer at a time, for +all peers. This is the least amount of memory possible for the send buffer. + +You should benchmark your max send rate when adjusting this setting. If you have +a very fast disk, you are less likely see a performance hit. + +reduce executable size +---------------------- + +Compilers generally add a significant number of bytes to executables that make use +of C++ exceptions. By disabling exceptions (-fno-exceptions on GCC), you can +reduce the executable size with up to 45%. In order to build without exception +support, you need to patch parts of boost. + +Also make sure to optimize for size when compiling. + +Another way of reducing the executable size is to disable code that isn't used. +There are a number of ``TORRENT_*`` macros that control which features are included +in libtorrent. If these macros are used to strip down libtorrent, make sure the same +macros are defined when building libtorrent as when linking against it. Some of +these macros may affect ABI (although they are not intended to). + +One, probably, safe macro to define is ``TORRENT_NO_DEPRECATE`` which removes all +deprecated functions and struct members. As long as no deprecated functions are +relied upon, this should be a simple way to shave off some size from the executable. + +For all available options, see the `building libtorrent`_ section. Look +specifically for the ``TORRENT_DISABLE_*`` macros. + +.. _`building libtorrent`: building.html + +high performance seeding +======================== + +In the case of a high volume seed, there are two main concerns. Performance and scalability. +This translates into high send rates, and low memory and CPU usage per peer connection. + +file pool +--------- + +libtorrent keeps an LRU cache for open file handles. Each file that is opened, +is stuck in the cache. The main purpose of this is because of anti-virus +software that hooks on file-open and file close to scan the file. Anti-virus +software that does that will significantly increase the cost of opening and +closing files. However, for a high performance seed, the file open/close might +be so frequent that it becomes a significant cost. It might therefore be a good +idea to allow a large file descriptor cache. Adjust this though +settings_pack::file_pool_size. + +Don't forget to set a high rlimit for file descriptors in your process as well. This limit +must be high enough to keep all connections and files open. + +uTP-TCP mixed mode +------------------ + +libtorrent supports uTP_, which has a delay based congestion controller. In order to +avoid having a single TCP bittorrent connection completely starve out any uTP connection, +there is a mixed mode algorithm. This attempts to detect congestion on the uTP peers and +throttle TCP to avoid it taking over all bandwidth. This balances the bandwidth resources +between the two protocols. When running on a network where the bandwidth is in such an +abundance that it's virtually infinite, this algorithm is no longer necessary, and might +even be harmful to throughput. It is advised to experiment with the +settings_pack::mixed_mode_algorithm, setting it to settings_pack::prefer_tcp. +This setting entirely disables the balancing and un-throttles all connections. On a typical +home connection, this would mean that none of the benefits of uTP would be preserved +(the modem's send buffer would be full at all times) and uTP connections would for the most +part be squashed by the TCP traffic. + +.. _`uTP`: utp.html + +send buffer low watermark +------------------------- + +libtorrent uses a low watermark for send buffers to determine when a new piece should +be requested from the disk I/O subsystem, to be appended to the send buffer. The low +watermark is determined based on the send rate of the socket. It needs to be large +enough to not draining the socket's send buffer before the disk operation completes. + +The watermark is bound to a max value, to avoid buffer sizes growing out of control. +The default max send buffer size might not be enough to sustain very high upload rates, +and you might have to increase it. It's specified in bytes in +settings_pack::send_buffer_watermark. + +peers +----- + +First of all, in order to allow many connections, set the global connection limit +high, settings_pack::connections_limit. Also set the upload rate limit to +infinite, settings_pack::upload_rate_limit, 0 means infinite. + +When dealing with a large number of peers, it might be a good idea to have slightly +stricter timeouts, to get rid of lingering connections as soon as possible. + +There are a couple of relevant settings: settings_pack::request_timeout, +settings_pack::peer_timeout and settings_pack::inactivity_timeout. + +For seeds that are critical for a delivery system, you most likely want to allow +multiple connections from the same IP. That way two people from behind the same NAT +can use the service simultaneously. This is controlled by +settings_pack::allow_multiple_connections_per_ip. + +In order to always unchoke peers, turn off automatic unchoke by setting +settings_pack::choking_algorithm to settings_pack::fixed_slots_choker and set the number +of upload slots to a large number via settings_pack::unchoke_slots_limit, +or use -1 (which means infinite). + +torrent limits +-------------- + +To seed thousands of torrents, you need to increase the settings_pack::active_limit +and settings_pack::active_seeds. + +hashing +------- + +When downloading at very high rates, it is possible to have the CPU be the +bottleneck for passing every downloaded byte through SHA-1 and/or SHA-256. In +order to enable computing hashes in parallel, on multi-core systems, set +settings_pack::aio_threads to the number of threads libtorrent should perform +I/O and settings_pack::hashing_threads to the number of threads to compute piece +hashes in. + +scalability +=========== + +In order to make more efficient use of the libtorrent interface when running a large +number of torrents simultaneously, one can use the ``session::get_torrent_status()`` call +together with ``session::post_torrent_updates()``. Keep in mind that every call into +libtorrent that return some value have to block your thread while posting a message to +the main network thread and then wait for a response. Calls that don't return any data +will simply post the message and then immediately return, performing the work +asynchronously. The time this takes might become significant once you reach a +few hundred torrents, depending on how many calls you make to each torrent and how often. +``session::get_torrent_status()`` lets you query the status of all torrents in a single call. +This will actually loop through all torrents and run a provided predicate function to +determine whether or not to include it in the returned vector. + +To use ``session::post_torrent_updates()`` torrents need to have the ``flag_update_subscribe`` +flag set. When post_torrent_updates() is called, a state_update_alert alert +is posted, with all the torrents that have updated since the last time this +function was called. The client have to keep its own state of all torrents, and +update it based on this alert. + +understanding the disk threads +============================== + +*This section is somewhat outdated, there are potentially more than one disk +thread* + +All disk operations are funneled through a separate thread, referred to as the +disk thread. The main interface to the disk thread is a queue where disk jobs +are posted, and the results of these jobs are then posted back on the main +thread's io_service. + +A disk job is essentially one of: + +1. write this block to disk, i.e. a write job. For the most part this is just a + matter of sticking the block in the disk cache, but if we've run out of + cache space or completed a whole piece, we'll also flush blocks to disk. + This is typically very fast, since the OS just sticks these buffers in its + write cache which will be flushed at a later time, presumably when the drive + head will pass the place on the platter where the blocks go. + +2. read this block from disk. The first thing that happens is we look in the + cache to see if the block is already in RAM. If it is, we'll return + immediately with this block. If it's a cache miss, we'll have to hit the + disk. Here we decide to defer this job. We find the physical offset on the + drive for this block and insert the job in an ordered queue, sorted by the + physical location. At a later time, once we don't have any more non-read + jobs left in the queue, we pick one read job out of the ordered queue and + service it. The order we pick jobs out of the queue is according to an + elevator cursor moving up and down along the ordered queue of read jobs. If + we have enough space in the cache we'll read read_cache_line_size number of + blocks and stick those in the cache. This defaults to 32 blocks. If the + system supports asynchronous I/O (Windows, Linux, Mac OS X, BSD, Solars for + instance), jobs will be issued immediately to the OS. This especially + increases read throughput, since the OS has a much greater flexibility to + reorder the read jobs. + +Other disk job consist of operations that needs to be synchronized with the +disk I/O, like renaming files, closing files, flushing the cache, updating the +settings etc. These are relatively rare though. + +contributions +============= + +If you have added instrumentation for some part of libtorrent that is not +covered here, or if you have improved any of the parser scripts, please consider +contributing it back to the project. + +If you have run tests and found that some algorithm or default value in +libtorrent are suboptimal, please contribute that knowledge back as well, to +allow us to improve the library. + +If you have additional suggestions on how to tune libtorrent for any specific +use case, please let us know and we'll update this document. + diff --git a/docs/tutorial.rst b/docs/tutorial.rst new file mode 100644 index 0000000..9633d4a --- /dev/null +++ b/docs/tutorial.rst @@ -0,0 +1,305 @@ +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +tutorial +======== + +The fundamental feature of starting and downloading torrents in libtorrent is +achieved by creating a *session*, which provides the context and a container for +torrents. This is done with via the session class, most of its interface is +documented under session_handle though. + +To add a torrent to the session, you fill in an add_torrent_params object and +pass it either to add_torrent() or async_add_torrent(). + +``add_torrent()`` is a blocking call which returns a torrent_handle. + +For example: + +.. code:: c++ + + #include + #include + #include + #include + + int main(int argc, char const* argv[]) + { + if (argc != 2) { + fprintf(stderr, "usage: %s \n"); + return 1; + } + lt::session ses; + + lt::add_torrent_params atp = lt::parse_magnet_uri(argv[1]); + atp.save_path = "."; // save in current dir + lt::torrent_handle h = ses.add_torrent(atp); + + // ... + } + +Once you have a torrent_handle, you can affect it as well as querying status. +First, let's extend the example to print out messages from the bittorrent engine +about progress and events happening under the hood. libtorrent has a mechanism +referred to as *alerts* to communicate back information to the client application. + +Clients can poll a session for new alerts via the pop_alerts() call. This +function fills in a vector of alert pointers with all new alerts since the last +call to this function. The pointers are owned by the session object at will +become invalidated by the next call to pop_alerts(). + +The alerts form a class hierarchy with alert as the root class. Each specific +kind of alert may include additional state, specific to the kind of message. All +alerts implement a message() function that prints out pertinent information +of the alert message. This can be convenient for simply logging events. + +For programmatically react to certain events, use alert_cast to attempt +a down cast of an alert object to a more specific type. + +In order to print out events from libtorrent as well as exiting when the torrent +completes downloading, we can poll the session for alerts periodically and print +them out, as well as listening for the torrent_finished_alert, which is posted +when a torrent completes. + +.. include:: ../examples/bt-get.cpp + :code: c++ + :tab-width: 2 + :start-after: */ + +alert masks +----------- + +The output from this program will be quite verbose, which is probably a good +starting point to get some understanding of what's going on. Alerts are +categorized into alert categories. Each category can be enabled and disabled +independently via the *alert mask*. + +The alert mask is a configuration option offered by libtorrent. There are many +configuration options, see settings_pack. The alert_mask setting is an integer +of the category flags ORed together. + +For instance, to only see the most pertinent alerts, the session can be +constructed like this: + +.. code:: c++ + + lt::settings_pack pack; + pack.set_int(lt::settings_pack::alert_mask + , lt::alert_category::error + | lt::alert_category::storage + | lt::alert_category::status); + + lt::session ses(pack); + +Configuration options can be updated after the session is started by calling +apply_settings(). Some settings are best set before starting the session +though, like listen_interfaces, to avoid race conditions. If you start the +session with the default settings and then immediately change them, there will +still be a window where the default settings apply. + +Changing the settings may trigger listen sockets to close and re-open and +NAT-PMP, UPnP updates to be sent. For this reason, it's typically a good idea +to batch settings updates into a single call. + +session destruction +------------------- + +The session destructor is blocking by default. When shutting down, trackers +will need to be contacted to stop torrents and other outstanding operations +need to be cancelled. Shutting down can sometimes take several seconds, +primarily because of trackers that are unresponsive (and time out) and also +DNS servers that are unresponsive. DNS lookups are especially difficult to +abort when stalled. + +In order to be able to start destruction asynchronously, one can call +session::abort(). + +This call returns a session_proxy object, which is a handle keeping the session +state alive while shutting it down. It deliberately does not provide any of the +session operations, since it's shutting down. + +After having a session_proxy object, the session destructor does not block. +However, the session_proxy destructor *will*. + +This can be used to shut down multiple sessions or other parts of the +application in parallel. + +asynchronous operations +----------------------- + +Essentially any call to a member function of session or torrent_handle that +returns a value is a blocking synchronous call. Meaning it will post a message +to the main libtorrent thread and wait for a response. Such calls may be +expensive, and in applications where stalls should be avoided (such as user +interface threads), blocking calls should be avoided. + +In the example above, session::add_torrent() returns a torrent_handle and is +thus blocking. For higher efficiency, async_add_torrent() will post a message +to the main thread to add a torrent, and post the resulting torrent_handle back +in an alert (add_torrent_alert). This is especially useful when adding a lot +of torrents in quick succession, as there's no stall in between calls. + +In the example above, we don't actually use the torrent_handle for anything, so +converting it to use async_add_torrent() is just a matter of replacing the +add_torrent() call with async_add_torrent(). + +torrent_status_updates +---------------------- + +To get updates to the status of torrents, call post_torrent_updates() on the +session object. This will cause libtorrent to post a state_update_alert +containing torrent_status objects for all torrents whose status has *changed* +since the last call to post_torrent_updates(). + +The state_update_alert looks something like this: + +.. code:: c++ + + struct state_update_alert : alert + { + virtual std::string message() const; + std::vector status; + }; + +The ``status`` field only contains the torrent_status for torrents with +updates since the last call. It may be empty if no torrent has updated its +state. This feature is critical for scalability. + +See the torrent_status object for more information on what is in there. +Perhaps the most interesting fields are ``total_payload_download``, +``total_payload_upload``, ``num_peers`` and ``state``. + +resuming torrents +----------------- + +Since bittorrent downloads pieces of files in random order, it's not trivial to +resume a partial download. When resuming a download, the bittorrent engine must +restore the state of the downloading torrent, specifically which parts of the +file(s) are downloaded. There are two approaches to doing this: + +1. read every piece of the downloaded files from disk and compare it against its + expected hash. +2. save, to disk, the state of which pieces (and partial pieces) are downloaded, + and load it back in again when resuming. + +If no resume data is provided with a torrent that's added, libtorrent will +employ (1) by default. + +To save resume data, call save_resume_data() on the torrent_handle object. +This will ask libtorrent to generate the resume data and post it back in +a save_resume_data_alert. If generating the resume data fails for any reason, +a save_resume_data_failed_alert is posted instead. + +Exactly one of those alerts will be posted for every call to +save_resume_data(). This is an important property when shutting down a +session with multiple torrents, every resume alert must be handled before +resuming with shut down. Any torrent may fail to save resume data, so the client +would need to keep a count of the outstanding resume files, decremented on +either save_resume_data_alert or save_resume_data_failed_alert. + +The save_resume_data_alert looks something like this: + +.. code:: c++ + + struct save_resume_data_alert : torrent_alert + { + virtual std::string message() const; + + // the resume data + add_torrent_params params; + }; + +The ``params`` field is an add_torrent_params object containing all the state +to add the torrent back to the session again. This object can be serialized +using write_resume_data() or write_resume_data_buf(), and de-serialized +with read_resume_data(). + +example +------- + +Here's an updated version of the above example with the following updates: + +1. not using blocking calls +2. printing torrent status updates rather than the raw log +3. saving and loading resume files + +.. include:: ../examples/bt-get2.cpp + :code: c++ + :tab-width: 2 + :start-after: */ + +session state +------------- + +On construction, a session object is configured by a session_params object. The +session_params object notably contain session_settings, the state of the DHT +node (e.g. routing table), the session's IP filter as well as the disk I/O +back-end and dht storage to use. + +There are functions to serialize and de-serialize the session_params object to +help in restoring session state from last run. Doing so is especially helpful +for bootstrapping the DHT, using nodes from last run. + +Before destructing the session object, call ``session::session_state()`` to get +the current state as a session_params object. + +Call write_session_params() or write_session_params_buf() to serialize the state +into a bencoded entry or to a flat buffer (``std::vector``) respectively. + +On startup, before constructing the session object, load the buffer back from +disk and call read_session_params() to de-serialize it back into a session_params +object. Before passing it into the session constructor is your chance to set +update the settings_pack (``params``) member of settings_params, or configuring +the disk_io_constructor. + +example +------- + +Another updated version of the above example with the following updates: + +1. load and save session_params to file ".session" +2. allow shutting down on ``SIGINT`` + +.. include:: ../examples/bt-get3.cpp + :code: c++ + :tab-width: 2 + :start-after: */ + +torrent files +------------- + +To add torrent files to a session (as opposed to a magnet link), it must first +be loaded into a torrent_info object. + +The torrent_info object can be created either by filename a buffer or a +bencoded structure. When adding by filename, there's a sanity check limit on the +size of the file, for adding arbitrarily large torrents, load the file outside +of the constructor. + +The torrent_info object provides an opportunity to query information about the +.torrent file as well as mutating it before adding it to the session. + +bencoding +--------- + +bencoded structures is the default data storage format used by bittorrent, such +as .torrent files, tracker announce and scrape responses and some wire protocol +extensions. libtorrent provides an efficient framework for decoding bencoded +data through bdecode() function. + +There are two separate mechanisms for *encoding* and *decoding*. When decoding, +use the bdecode() function that returns a bdecode_node. When encoding, use +bencode() taking an entry object. + +The key property of bdecode() is that it does not copy any data out of the +buffer that was parsed. It builds the tree structures of references pointing +into the buffer. The buffer must stay alive and valid for as long as the +bdecode_node is in use. + +For performance details on bdecode(), see the `blog post`_ about it. + +.. _`blog post`: https://blog.libtorrent.org/2015/03/bdecode-parsers/ + diff --git a/docs/udp_tracker_protocol.rst b/docs/udp_tracker_protocol.rst new file mode 100644 index 0000000..5f78559 --- /dev/null +++ b/docs/udp_tracker_protocol.rst @@ -0,0 +1,320 @@ +Bittorrent UDP-tracker protocol extension +========================================= + +:Author: Arvid Norberg, arvid@libtorrent.org + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + + +introduction +------------ + +A tracker with the protocol "udp://" in its URI +is supposed to be contacted using this protocol. + +This protocol is supported by +xbt-tracker_. + + +.. _xbt-tracker: http://xbtt.sourceforge.net + +For additional information and descriptions of +the terminology used in this document, see +the `protocol specification`__ + +__ http://wiki.theory.org/index.php/BitTorrentSpecification + +All values are sent in network byte order (big-endian). The sizes +are specified with ANSI-C standard types. + +If no response to a request is received within 15 seconds, resend +the request. If no reply has been received after 60 seconds, stop +retrying. + + +connecting +---------- + +Client sends packet: + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int64_t | connection_id | Must be initialized to 0x41727101980 | +| | | in network byte order. This will | +| | | identify the protocol. | ++-------------+---------------------+----------------------------------------+ +| int32_t | action | 0 for a connection request | ++-------------+---------------------+----------------------------------------+ +| int32_t | transaction_id | Randomized by client. | ++-------------+---------------------+----------------------------------------+ + +Server replies with packet: + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int32_t | action | Describes the type of packet, in this | +| | | case it should be 0, for connect. | +| | | If 3 (for error) see errors_. | ++-------------+---------------------+----------------------------------------+ +| int32_t | transaction_id | Must match the transaction_id sent | +| | | from the client. | ++-------------+---------------------+----------------------------------------+ +| int64_t | connection_id | A connection id, this is used when | +| | | further information is exchanged with | +| | | the tracker, to identify you. | +| | | This connection id can be reused for | +| | | multiple requests, but if it's cached | +| | | for too long, it will not be valid | +| | | anymore. | ++-------------+---------------------+----------------------------------------+ + + +announcing +---------- + +Client sends packet: + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int64_t | connection_id | The connection id acquired from | +| | | establishing the connection. | ++-------------+---------------------+----------------------------------------+ +| int32_t | action | Action. in this case, 1 for announce. | +| | | See actions_. | ++-------------+---------------------+----------------------------------------+ +| int32_t | transaction_id | Randomized by client. | ++-------------+---------------------+----------------------------------------+ +| int8_t[20] | info_hash | The info-hash of the torrent you want | +| | | announce yourself in. | ++-------------+---------------------+----------------------------------------+ +| int8_t[20] | peer_id | Your peer id. | ++-------------+---------------------+----------------------------------------+ +| int64_t | downloaded | The number of byte you've downloaded | +| | | in this session. | ++-------------+---------------------+----------------------------------------+ +| int64_t | left | The number of bytes you have left to | +| | | download until you're finished. | ++-------------+---------------------+----------------------------------------+ +| int64_t | uploaded | The number of bytes you have uploaded | +| | | in this session. | ++-------------+---------------------+----------------------------------------+ +| int32_t | event | The event, one of | +| | | | +| | | * none = 0 | +| | | * completed = 1 | +| | | * started = 2 | +| | | * stopped = 3 | ++-------------+---------------------+----------------------------------------+ +| uint32_t | ip | Your ip address. Set to 0 if you want | +| | | the tracker to use the ``sender`` of | +| | | this UDP packet. | ++-------------+---------------------+----------------------------------------+ +| uint32_t | key | A unique key that is randomized by the | +| | | client. | ++-------------+---------------------+----------------------------------------+ +| int32_t | num_want | The maximum number of peers you want | +| | | in the reply. Use -1 for default. | ++-------------+---------------------+----------------------------------------+ +| uint16_t | port | The port you're listening on. | ++-------------+---------------------+----------------------------------------+ +| uint16_t | extensions | See extensions_ | ++-------------+---------------------+----------------------------------------+ + + +Server replies with packet: + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int32_t | action | The action this is a reply to. Should | +| | | in this case be 1 for announce. | +| | | If 3 (for error) see errors_. | +| | | See actions_. | ++-------------+---------------------+----------------------------------------+ +| int32_t | transaction_id | Must match the transaction_id sent | +| | | in the announce request. | ++-------------+---------------------+----------------------------------------+ +| int32_t | interval | the number of seconds you should wait | +| | | until re-announcing yourself. | ++-------------+---------------------+----------------------------------------+ +| int32_t | leechers | The number of peers in the swarm that | +| | | has not finished downloading. | ++-------------+---------------------+----------------------------------------+ +| int32_t | seeders | The number of peers in the swarm that | +| | | has finished downloading and are | +| | | seeding. | ++-------------+---------------------+----------------------------------------+ + +The rest of the server reply is a variable number of the following structure: + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int32_t | ip | The ip of a peer in the swarm. | ++-------------+---------------------+----------------------------------------+ +| uint16_t | port | The peer's listen port. | ++-------------+---------------------+----------------------------------------+ + + +scraping +-------- + +Client sends packet: + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int64_t | connection_id | The connection id retrieved from the | +| | | establishing of the connection. | ++-------------+---------------------+----------------------------------------+ +| int32_t | action | The action, in this case, 2 for | +| | | scrape. See actions_. | ++-------------+---------------------+----------------------------------------+ +| int32_t | transaction_id | Randomized by client. | ++-------------+---------------------+----------------------------------------+ + +The following structure is repeated for each info-hash to scrape, but limited by +the MTU. + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int8_t[20] | info_hash | The info hash that is to be scraped. | ++-------------+---------------------+----------------------------------------+ + + +Server replies with packet: + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int32_t | action | The action, should in this case be | +| | | 2 for scrape. | +| | | If 3 (for error) see errors_. | ++-------------+---------------------+----------------------------------------+ +| int32_t | transaction_id | Must match the sent transaction id. | ++-------------+---------------------+----------------------------------------+ + +The rest of the packet contains the following structures once for each info-hash +you asked in the scrape request. + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int32_t | complete | The current number of connected seeds. | ++-------------+---------------------+----------------------------------------+ +| int32_t | downloaded | The number of times this torrent has | +| | | been downloaded. | ++-------------+---------------------+----------------------------------------+ +| int32_t | incomplete | The current number of connected | +| | | leechers. | ++-------------+---------------------+----------------------------------------+ + + +errors +------ + +In case of a tracker error, + +server replies packet: + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int32_t | action | The action, in this case 3, for error. | +| | | See actions_. | ++-------------+---------------------+----------------------------------------+ +| int32_t | transaction_id | Must match the transaction_id sent | +| | | from the client. | ++-------------+---------------------+----------------------------------------+ +| int8_t[] | error_string | The rest of the packet is a string | +| | | describing the error. | ++-------------+---------------------+----------------------------------------+ + + +actions +------- + +The action fields has the following encoding: + + * connect = 0 + * announce = 1 + * scrape = 2 + * error = 3 (only in server replies) + + +extensions +---------- + +The extensions field is a bitmask. The following +bits are assigned: + + * 1 = authentication_. + * 2 = `request string`_. + +If multiple bits are present in the extension field, the extension +bodies are appended to the packet in the order of least significant +bit first. For instance, if both bit 1 and 2 are set, the extension +represented by bit 1 comes first, followed by the extension represented +by bit 2. + +authentication +~~~~~~~~~~~~~~ + +The packet will have an authentication part +appended to it. It has the following format: + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int8_t | username_length | The number of characters in the | +| | | username. | ++-------------+---------------------+----------------------------------------+ +| int8_t[] | username | The username, the number of characters | +| | | as specified in the previous field. | ++-------------+---------------------+----------------------------------------+ +| uint8_t[8] | passwd_hash | sha1(packet + sha1(password)) | +| | | The packet in this case means the | +| | | entire packet except these 8 bytes | +| | | that are the password hash. These are | +| | | the 8 first bytes (most significant) | +| | | from the 20 bytes hash calculated. | ++-------------+---------------------+----------------------------------------+ + +request string +-------------- + +The request string extension is meant to allow torrent creators pass along +cookies back to the tracker. This can be useful for authenticating that a +torrent is allowed to be tracked by a tracker for instance. It could also +be used to authenticate users by generating torrents with unique tokens +in the tracker URL for each user. The extension body has the following format: + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int8_t | request length | The number of bytes in the request | +| | | string. | ++-------------+---------------------+----------------------------------------+ +| int8_t[] | request string | The string that comes after the host- | +| | | name and port in the UDP tracker URL. | +| | | Typically this starts with "/announce" | +| | | The bittorrent client is not expected | +| | | to append query string arguments for | +| | | stats reporting, like "uploaded" and | +| | | "downloaded" since this is already | +| | | reported in the UDP tracker protocol. | +| | | However, the client is free to add | +| | | arguments as extensions. | ++-------------+---------------------+----------------------------------------+ + +credits +------- + +Protocol designed by Olaf van der Spek and extended by Arvid Norberg + diff --git a/docs/upgrade_to_1.2.rst b/docs/upgrade_to_1.2.rst new file mode 100644 index 0000000..c36a118 --- /dev/null +++ b/docs/upgrade_to_1.2.rst @@ -0,0 +1,164 @@ +=========================== +Upgrading to libtorrent 1.2 +=========================== + +:Author: Arvid Norberg, arvid@libtorrent.org + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +libtorrent version 1.2 comes with some significant updates in the API. +This document summarizes the changes affecting library users. + +C++98 no longer supported +========================= + +With libtorrent 1.2, C++98 is no longer supported, you need a compiler capable +of at least C++11 to build libtorrent. + +This also means libtorrent types now support move. + +listen interfaces +================= + +There's a subtle change in how the ``listen_interfaces`` setting is interpreted +in 1.2 compared to 1.1. + +In libtorrent-1.1, if you listen to ``0.0.0.0:6881`` (which was the default), +not only would an IPv4 listen socket be opened (bound to INADDR_ANY) but also an +IPv6 socket would be opened. + +In libtorrent-1.2, if you listen to ``0.0.0.0:6881`` only the IPv4 INADDR_ANY is +opened as a listen socket. If you want to listen to both IPv4 and IPv6, you need +to listen to ``0.0.0.0:6881,[::]:6881``. + +forward declaring libtorrent types deprecated +============================================= + +Clients are discouraged from forward declaring types from libtorrent. +Instead, include the header. + +A future release will introduce ABI versioning using an inline namespace, which will break any forward declarations by clients. + +There is a new namespace alias, ``lt`` which is shorthand for ``libtorrent``. +In the future, ``libtorrent`` will be the alias and ``lt`` the namespace name. +With no forward declarations inside libtorrent's namespace though, there should not be any reason for clients to re-open the namespace. + +resume data handling +==================== + +To significantly simplify handling of resume data, the previous way of handling it is deprecated. +resume data is no longer passed in as a flat buffer in the add_torrent_params. +The add_torrent_params structure itself *is* the resume data now. + +In order to parse the bencoded fast resume file (which is still the same format, and backwards compatible) use the read_resume_data() function. + +Similarly, when saving resume data, the save_resume_data_alert now has a ``params`` field of type add_torrent_params which contains the resume data. +This object can be serialized into the bencoded form using write_resume_data(). + +This give the client full control over which properties should be loaded from the resume data and which should be controlled by the client directly. +The flags ``flag_override_resume_data``, ``flag_merge_resume_trackers``, ``flag_use_resume_save_path`` and ``flag_merge_resume_http_seeds`` have all been deprecated, since they are no longer needed. + +The old API is still supported as long as libtorrent is built with deprecated functions enabled (which is the default). +It will be performing slightly better without deprecated functions present. + +rate_limit_utp changed defaults +=============================== + +The setting ``rate_limit_utp`` was deprecated in libtorrent 1.1. +When building without deprecated features (``deprecated-functions=off``) the default behavior also changed to have rate limits apply to utp sockets too. +In order to be more consistent between the two build configurations, the default value has changed to true. +The new mechanism provided to apply special rate limiting rules is *peer classes*. +In order to implement the old behavior of not rate limiting uTP peers, one can set up a peer class for all uTP peers, to make the normal peer classes not apply to them (which is where the rate limits are set). + +announce entry multi-home support +================================= + +The announce_entry type now captures status on individual endpoints, as opposed to treating every tracker behind the same name as a single tracker. +This means some properties has moved into the ``announce_endpoint`` structure, and an announce entry has 0 or more endpoints. + +alerts no longer cloneable +========================== + +As part of the transition to a more efficient handling of alerts, 1.1 allocated them in a contiguous, heterogeneous, vector. +This means they are no longer heap allocated nor held by a smart pointer. +The ``clone()`` member on alerts was deprecated in 1.1 and removed in 1.2. +To pass alerts across threads, instead pull out the relevant information from the alerts and pass that across. + +progress alert category +======================= + +The ``alert::progress_notification`` category has been deprecated. +Alerts posted in this category are now also posted in one of these new categories: + +* ``alert::block_progress_notification`` +* ``alert::piece_progress_notification`` +* ``alert::file_progress_notification`` +* ``alert::upload_notification`` + +boost replaced by std +===================== + +``boost::shared_ptr`` has been replaced by ``std::shared_ptr`` in the libtorrent API. +The same goes for ```` types, instead of ``boost::int64_t``, libtorrent now uses ``std::int64_t``. +Instead of ``boost::array``, ``std::array`` is used, and ``boost::function`` has been replaced by ``std::function``. + +strong typedefs +=============== + +In order to strengthen type-safety, libtorrent now uses special types to represent certain indexes and ID types. +Any integer referring to a piece index, now has the type ``piece_index_t``, and indices to files in a torrent, use ``file_index_t``. +Similarly, time points and duration now use ``time_point`` and ``duration`` from the ```` standard library. + +The specific types have typedefs at ``lt::time_point`` and ``lt::duration``, and the clock used by libtorrent is ``lt::clock_type``.` + +strongly typed flags +==================== + +Enum flags have been replaced by strongly typed flags. +This means their implicit conversion to and from ``int`` is deprecated. +For example, the following expressions are deprecated:: + + if ((atp.flags & add_torrent_params::flag_paused) == 0) + + atp.flags = 0; + +Instead say:: + + if (!(atp.flags & torrent_flags::paused)) + + atp.flags = {}; + +(Also note that in this specific example, the flags moved out of the ``add_torrent_params`` structure, but this is unrelated to them also having stronger types). + +span<> and string_view +====================== + +The interface has adopted ``string_view`` (from boost for now) and ``span<>`` (custom implementation for now). +This means some function calls that previously took ``char const*`` or ``std::string`` may now take an ``lt::string_view``. +Similarly, functions that previously would take a pointer and length pair will now take a ``span<>``. + +periphery utility functions no longer exported +============================================== + +Historically, libtorrent has exported functions not essential to its core bittorrent functionality. +Such as filesystem functions like ``directory``, ``file`` classes and ``remove``, ``create_directory`` functions. +Path manipulation functions like ``combine_path``, ``extension``, ``split_path`` etc. +String manipulation functions like ``from_hex`` and ``to_hex``. +Time functions like ``time_now``. These functions are no longer available to clients, and some have been removed from the library. +Instead, it is recommended to use boost.filesystem or the experimental filesystem TS. + +plugins +======= + +libtorrent session plugins no longer have all callbacks called unconditionally. +The plugin has to register which callbacks it's interested in receiving by returning a bitmask from ``feature_flags_t implemented_features()``. +The return value is documented in the plugin class. + +RSS functions removed +===================== + +The deprecated RSS functions have been removed from the library interface. + + diff --git a/docs/upgrade_to_2.0.rst b/docs/upgrade_to_2.0.rst new file mode 100644 index 0000000..e1554a5 --- /dev/null +++ b/docs/upgrade_to_2.0.rst @@ -0,0 +1,336 @@ +=========================== +Upgrading to libtorrent 2.0 +=========================== + +:Author: Arvid Norberg, arvid@libtorrent.org + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +In libtorrent 2.0, some parts of the API has changed and some deprecated parts +have been removed. +This document summarizes the changes affecting library clients. + +C++11 no longer supported +========================= + +libtorrent 2.0 requires at least C++-14. To build with boost build, specify the +C++ version using the ``cxxstd=14`` build feature (14 is the default). + +boost version +============= + +The oldest boost version supported is 1.67 + +BitTorrent v2 support +===================== + +Supporting bittorrent v2 come with some changes to the API. Specifically to +support *hybrid* torrents. i.e. torrents that are compatible with v1-only +bittorrent clients as well as supporting v2 features among the peers that +support them. + +Example torrents +---------------- + +* `bittorrent-v2-hybrid-test.torrent`_ +* `bittorrent-v2-test.torrent`_ + +.. _`bittorrent-v2-hybrid-test.torrent`: https://libtorrent.org/bittorrent-v2-hybrid-test.torrent +.. _`bittorrent-v2-test.torrent`: https://libtorrent.org/bittorrent-v2-test.torrent + +info-hashes +----------- + +With bittorrent v2 support, each torrent may now have two separate info hashes, +one SHA-1 hash and one SHA-256 hash. These are bundled in a new type called +info_hash_t. Many places that previously took an info-hash as sha1_hash now +takes an info_hash_t. For backwards compatibility, info_hash_t is implicitly +convertible to and from sha1_hash and is interpreted as the v1 info-hash. +The implicit conversion is deprecated though. + +Perhaps most noteworthy is that ``add_torrent_params::info_hash`` is now +deprecated in favor of ``add_torrent_params::info_hashes`` which is an +info_hash_t. + +The alerts torrent_removed_alert, torrent_deleted_alert, +torrent_delete_failed_alert all have ``info_hash`` members. Those members are +now deprecated in favor of an ``info_hashes`` member, which is of type +info_hash_t. + +An info_hash_t object for a hybrid torrent will have both the v1 and v2 hashes +set, it will compare false to a sha1_hash of *just* the v1 hash. + +Calls to torrent_handle::info_hash() may need to be replaced by +torrent_handle::info_hashes(), in order to get both v1 and v2 hashes. + +announce_entry/tracker changes +------------------------------ + +On major change in the API is reporting of trackers. Since hybrid torrents +announce once per info-hash (once for v1 and once for v2), the tracker results +are also reported per *bittorrent version*. + +Each tracker (announce_entry) has a list of ``endpoints``. Each corresponding to +a local listen socket. Each listen socket is announced independently. The +announce_endpoint in turn has an array ``info_hashes``, containing objects of +type announce_infohash, for each bittorrent version. The array is indexed by +the enum protocol_version. There are two members, ``V1`` and ``V2``. + +Example: + +.. code:: c++ + + std::vector tr = h.trackers(); + for (lt::announce_entry const& ae : h.trackers()) { + for (lt::announce_endpoint const& aep : ae.endpoints) { + int version = 1; + for (lt::announce_infohash const& ai : aep.info_hashes) { + std::cout << "[V" << version << "] " << ae.tier << " " << ae.url + << " " << (ih.updating ? "updating" : "") + << " " << (ih.start_sent ? "start-sent" : "") + << " fails: " << ih.fails + << " msg: " << ih.message + << "\n"; + ++version; + } + } + } + +Merkle tree support removed +--------------------------- + +The old merkle tree torrent support has been removed, as BitTorrent v2 has +better support for merkle trees, where each file has its own merkle tree. + +This means add_torrent_params no longer has the ``merkle_tree`` member. Instead +it has the new ``verified_leaf_hashes`` and ``merkle_trees`` members. + +It also means the ``merkle`` flag for create_torrent has been removed. +torrent_info no longer has ``set_merkle_tree()`` and ``merkle_tree()`` member +functions. + +create_torrent changes +---------------------- + +The create_torrent class creates *hybrid* torrents by default. i.e. torrents +compatible with both v1 and v2 bittorrent clients. + +To create v1-only torrents use the ``v1_only`` flag. To create v2-only torrents, +use the ``v2_only`` flag. + +Perhaps the most important addition for v2 torrents is the new member function +set_hash2(), which is similar to set_hash(), but for the v2-part of a torrent. +One important difference is that v2 hashes are SHA-256 hashes, and they are set +*per file*. In v2 torrents, each file forms a merkle tree and each v2 piece hash +is the SHA-256 merkle root hash of the 16 kiB blocks in that piece. + +All v2 torrents have pieces aligned to files, so the ``optimize_alignment`` flag +is no longer relevant (as it's effectively always on). Similarly, the +``mutable_torrent_support`` flag is also always on. + +``pad_file_limit`` and ``alignment`` parameters to the create_torrent constructor +have also been removed. The rules for padding and alignment is well defined for +v2 torrents. + +set_file_hash() and file_hash() functions are obsolete, as v2 torrents have +a file_root() for each file. + + +on_unknown_torrent() plugin API +------------------------------- + +Since hybrid torrents have two info-hashes, the on_unknown_torrent() function +on the plugin class now takes an info_hash_t instead of a sha1_hash. + +socket_type_t +------------- + +There is a new ``enum class`` called ``socket_type_t`` used to identify different +kinds of sockets. In previous versions of libtorrent this was exposed as plain +``int`` with subtly different sets of meanings. + +Previously there was an enum value ``udp``, which has been deprecated in favor of ``utp``. + +The socket type is exposed in the following alerts, which now use the ``socket_type_t`` +enum instead of ``int``: + +* ``peer_connect_alert`` +* ``peer_disconnected_alert`` +* ``incoming_connection_alert`` +* ``listen_failed_alert`` +* ``listen_succeeded_alert`` + + +DHT settings +============ + +DHT configuration options have previously been set separately from the main client settings. +In libtorrent 2.0 they have been unified into the main settings_pack. + +Hence, `lt::dht::dht_settings` is now deprecated, in favor of the new `dht_*` +settings in settings_pack. + +Deprecating `dht_settings` also causes an API change to the dht custom storage +constructor (see session_params). Instead of taking a `dht_settings` object, it +is now passed the full `settings_pack`. This is considered a niche interface, +so there is no backward compatibility option provided. + +stats_alert +=========== + +The stats_alert is deprecated. Instead, call session::post_torrent_updates(). +This will post a state_update_alert containing torrent_status of all torrents +that have any updates since last time this function was called. + +The new mechanism scales a lot better. + +saving and restoring session state +================================== + +The functions ``save_state()`` and ``load_state()`` on the session object have +been deprecated in favor loading the session state up-front using +read_session_params() and construct the session from it. + +The session state can be acquired, in the form of a session_params object, by +calling session::session_state(). + +The session_params object is passed to the session constructor, and will restore +the state from a previous session. + +Use read_session_params() and write_session_params() to serialize and de-serialize +the session_params object. + +As a result of this, plugins that wish to save and restore state or settings +must now use the new overload of load_state(), that takes a +``std::map``. Similarly, for saving state, it now has +to be saved to a ``std::map`` via the new overload of +save_state(). + +A lot of session constructors have been deprecated in favor of the ones that take +a session_params object. The session_params object can be implicitly constructed +from a settings_pack, to cover one of the now-deprecated constructors. However, +to access this conversion `libtorrent/session_params.hpp` must be included. + +userdata is no longer a void\* +============================== + +The ``userdata`` field in add_torrent_params is no longer a raw void pointer. +Instead it is a type-safe client_data_t object. client_data_t is similar to +``std::any``, it can hold a pointer of any type by assignment and can be cast +back to that pointer via ``static_cast`` (explicit conversion). However, if the +pointer type it is cast to is not identical to what was assigned, a ``nullptr`` +is returned. Note that the type has to be identical in CV-qualifiers as well. + +This userdata field affects the plugin APIs that has this field passed into it. + +Additionally, there's now a way to ask a torrent_handle for the userdata, so it is +associated with the torrent itself. + +Adding torrents by URL no longer supported +========================================== + +The URL covers 3 separate features, all deprecated in the previous version and +removed in 2.0. + +downloading over HTTP +--------------------- + +One used to be able to add a torrent by specifying an HTTP URL in the +``add_torrent_params::url`` member. Libtorrent would download the file and attempt +to load the file as a .torrent file. The torrent_handle in this mode would +not represent a torrent, but a *potential* torrent. Its info-hash was the hash of +the URL until the torrent file could be loaded, at which point the info hash *changed*. +The corresponding ``torrent_update_alert`` has also been removed. In libtorrent 2.0 +info-hashes cannot change. (Although they can be amended with bittorrent v1 or v2 +info-hashes). + +Instead of using this feature, clients should download the .torrent files +themselves, possibly spawn their own threads, before adding them to the session. + +magnet links +------------ + +The ``add_torrent_params::url`` could also be used to add torrents by magnet link. +This was also deprecated in the previous version and has been removed in +libtorrent 2.0. Instead, use parse_magnet_uri() to construct an add_torrent_params +object to add to the session. This also allows the client to alter settings, +such as ``save_path``, before adding the magnet link. + +async loading of .torrent files +------------------------------- + +The ``add_torrent_params::url`` field also supported ``file://`` URLs. This would +use a libtorrent thread to load the file from disk, asynchronously (in the case +of async_add_torrent()). This feature has been removed. Clients should instead +load their torrents from disk themselves, before adding them to the session. +Possibly spawning their own threads. + +Disk I/O overhaul +================= + +In libtorrent 2.0, the disk I/O subsystem underwent a significant update. In +previous versions of libtorrent, each torrent has had its own, isolated, +disk storage object. This was a customization point. In order to share things +like a pool of open file handles across torrents (to have a global limit on +open file descriptors) all storage objects would share a file_pool object +passed in to them. + +In libtorrent 2.0, the default disk I/O uses memory mapped files, which means +a lot more of what used to belong in the disk caching subsystem is now handled +by the kernel. This greatly simplifies the disk code and also has the potential +of making a lot more efficient use of modern disks as well as physical memory. + +In this new system, the customization point is the whole disk I/O subsystem. +Instead of configuring a custom storage (implementing ``storage_interface``) when +adding a torrent, you can now configure a disk subsystem (implementing +disk_interface) when creating a session. + +Systems that don't support memory mapped files can still be used with a simple +``fopen()``/``fclose()`` family of functions. This disk subsystem is also not threaded +and generally more primitive than the memory mapped file one. + +Clients that need to customize storage should implement the disk_interface and +configure it at session creation time instead of ``storage_interface`` configured +in add_torrent_params. add_torrent_params no longer has a storage_constructor +member. + +As a consequence of this, ``get_storage_impl()`` has been removed from torrent_handle. + +``aio_threads`` and ``hashing_threads`` +--------------------------------------- + +In previous versions of libtorrent, the number of disk threads to use were +configured by settings_pack::aio_threads. Every fourth thread was dedicated to +run hash jobs, i.e. computing SHA-1 piece hashes to compare them against the +expected hash. + +This setting has now been split up to allow controlling the number of dedicated +hash threads independently from the number of generic disk I/O threads. +settings_pack::hashing_threads is now used to control the number of threads +dedicated to computing hashes. + +cache_size +---------- + +The ``cache_size`` setting is no longer used. The caching of disk I/O is handled +by the operating system. + +get_cache_info() get_cache_status() +----------------------------------- + +Since libtorrent no longer manages the disk cache (except for a store-buffer), +``get_cache_info()`` and ``get_cache_status()`` on the session object has also +been removed. They cannot return anything useful. + +last remnants of RSS support removed +==================================== + +The ``rss_notification`` alert category flag has been removed, which has been unused +and deprecated since libtorrent 1.2. + +The ``uuid`` member of add_torrent_params has been removed. Torrents can no longer +be added under a specific UUID. This feature was specifically meant for RSS feeds, +which was removed in the previous version of libtorrent. + diff --git a/docs/utp.rst b/docs/utp.rst new file mode 100644 index 0000000..07d555c --- /dev/null +++ b/docs/utp.rst @@ -0,0 +1,345 @@ +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +uTP +=== + +uTP (uTorrent transport protocol) is a transport protocol which uses one-way +delay measurements for its congestion controller. This article is about uTP +in general and specifically about libtorrent's implementation of it. + +rationale +--------- + +One of the most common problems users are experiencing using bittorrent is +that their internet "stops working". This can be caused by a number of things, +for example: + +1. a home router that crashes or slows down when its NAT pin-hole + table overflows, triggered by DHT or simply many TCP connections. + +2. a home router that crashes or slows down by UDP traffic (caused by + the DHT) + +3. a home DSL or cable modem having its send buffer filled up by outgoing + data, and the buffer fits seconds worth of bytes. This adds seconds + of delay on interactive traffic. For a web site that needs 10 round + trips to load this may mean 10s of seconds of delay to load compared + to without bittorrent. Skype or other delay sensitive applications + would be affected even more. + +This document will cover (3). + +Typically this is solved by asking the user to enter a number of bytes +that the client is allowed to send per second (i.e. setting an upload +rate limit). The common recommendation is to set this limit to 80% of the +uplink's capacity. This is to leave some headroom for things like TCP +ACKs as well as the user's interactive use of the connection such as +browsing the web or checking email. + +There are two major drawbacks with this technique: + +1. The user needs to actively make this setting (very few protocols + require the user to provide this sort of information). This also + means the user needs to figure out what its up-link capacity is. + This is unfortunately a number that many ISPs are not advertising + (because it's often much lower than the download capacity) which + might make it hard to find. + +2. The 20% headroom is wasted most of the time. Whenever the user + is not using the internet connection for anything, those extra 20% + could have been used by bittorrent to upload, but they're already + allocated for interactive traffic. On top of that, 20% of the up-link + is often not enough to give a good and responsive browsing experience. + +The ideal bandwidth allocation would be to use 100% for bittorrent when +there is no interactive cross traffic, and 100% for interactive traffic +whenever there is any. This would not waste any bandwidth while the user +is idling, and it would make for a much better experience when the user +is using the internet connection for other things. + +This is what uTP does. + +TCP +--- + +The reason TCP will fill the send buffer, and cause the delay on all traffic, +is because its congestion control is *only* based on packet loss (and timeout). + +Since the modem is buffering, packets won't get dropped until the entire queue +is full, and no more packets will fit. The packets will be dropped, TCP will +detect this within an RTT or so. When TCP notices a packet loss, it will slow +down its send rate and the queue will start to drain again. However, TCP will +immediately start to ramp up its send rate again until the buffer is full and +it detects packet loss again. + +TCP is designed to fully utilize the link capacity, without causing congestion. +Whenever it sense congestion (through packet loss) it backs off. TCP is not +designed to keep delays low. When you get the first packet loss (assuming the +kind of queue described above, tail-queue) it is already too late. Your queue +is full and you have the maximum amount of delay your modem can provide. + +TCP controls its send rate by limiting the number of bytes in-flight at any +given time. This limit is called congestion window (*cwnd* for short). During +steady state, the congestion window is constantly increasing linearly. Each +packet that is successfully transferred will increase cwnd. + +:: + + cwnd + send_rate = ---- + RTT + + +Send rate is proportional to cwnd divided by RTT. A smaller cwnd will cause +the send rate to be lower and a larger cwnd will cause the send rate to be +higher. + +Using a congestion window instead of controlling the rate directly is simple +because it also introduces an upper bound for memory usage for packets that +haven't been ACKed yet and needs to be kept around. + +The behavior of TCP, where it bumps up against the ceiling, backs off and then +starts increasing again until it hits the ceiling again, forms a saw tooth shape. +If the modem wouldn't have any send buffer at all, a single TCP stream would +not be able to fully utilize the link because of this behavior, since it would +only fully utilize the link right before the packet loss and the back-off. + +LEDBAT congestion controller +---------------------------- + +The congestion controller in uTP is called LEDBAT_, which also is an IETF working +group attempting to standardize it. The congestion controller, on top of reacting +to packet loss the same way TCP does, also reacts to changes in delays. + +For any uTP (or LEDBAT_) implementation, there is a target delay. This is the +amount of delay that is acceptable, and is in fact targeted for the connection. +The target delay is defined to 25 ms in LEDBAT_, uTorrent uses 100 ms and +libtorrent uses 75 ms. Whenever a delay measurement is lower than the target, +cwnd is increased proportional to (target_delay - delay). Whenever the measurement +is higher than the target, cwnd is decreased proportional to (delay - target_delay). + +It can simply be expressed as:: + + cwnd += gain * (target_delay - delay) + +.. image:: img/cwnd_thumb.png + :target: cwnd.png + :class: bw + :align: right + +Similarly to TCP, this is scaled so that the increase is evened out over one RTT. + +The linear controller will adjust the cwnd more for delays that are far off the +target, and less for delays that are close to the target. This makes it converge +at the target delay. Although, due to noise there is almost always some amount of +oscillation. This oscillation is typically smaller than the saw tooth TCP forms. + +The figure to the right shows how (TCP) cross traffic causes uTP to essentially +entirely stop sending anything. Its delay measurements are mostly well above the target +during this time. The cross traffic is only a single TCP stream in this test. + +As soon as the cross traffic ceases, uTP will pick up its original send rate within +a second. + +Since uTP constantly measures the delay, with every single packet, the reaction time +to cross traffic causing delays is a single RTT (typically a fraction of a second). + +one way delays +-------------- + +uTP measures the delay imposed on packets being sent to the other end +of the connection. This measurement only includes buffering delay along +the link, not propagation delay (the speed of light times distance) nor +the routing delay (the time routers spend figuring out where to forward +the packet). It does this by always comparing all measurements to a +baseline measurement, to cancel out any fixed delay. By focusing on the +variable delay along a link, it will specifically detect points where +there might be congestion, since those points will have buffers. + +.. image:: img/delays_thumb.png + :target: delays.png + :class: bw + :align: right + +Delay on the return link is explicitly not included in the delay measurement. +This is because in a peer-to-peer application, the other end is likely to also +be connected via a modem, with the same send buffer restrictions as we assume +for the sending side. The other end having its send queue full is not an indication +of congestion on the path going the other way. + +In order to measure one way delays for packets, we cannot rely on clocks being +synchronized, especially not at the microsecond level. Instead, the actual time +it takes for a packet to arrive at the destination is not measured, only the changes +in the transit time is measured. + +Each packet that is sent includes a time stamp of the current time, in microseconds, +of the sending machine. The receiving machine calculates the difference between its +own timestamp and the one in the packet and sends this back in the ACK. This difference, +since it is in microseconds, will essentially be a random 32 bit number. However, +the difference will stay somewhat similar over time. Any changes in this difference +indicates that packets are either going through faster or slower. + +In order to measure the one-way buffering delay, a base delay is established. The +base delay is the lowest ever seen value of the time stamp difference. Each delay +sample we receive back, is compared against the base delay and the delay is the +difference. + +This is the delay that's fed into the congestion controller. + +A histogram of typical delay measurements is shown to the right. This is from +a transfer between a cable modem connection and a DSL connection. + +The details of the delay measurements are slightly more complicated since the +values needs to be able to wrap (cross the 2^32 boundary and start over at 0). + +Path MTU discovery +------------------ + +MTU is short for *Maximum Transfer Unit* and describes the largest packet size that +can be sent over a link. Any datagrams which size exceeds this limit will either +be *fragmented* or dropped. A fragmented datagram means that the payload is split up +in multiple packets, each with its own individual packet header. + +There are several reasons to avoid sending datagrams that get fragmented: + +1. A fragmented datagram is more likely to be lost. If any fragment is lost, + the whole datagram is dropped. + +2. Bandwidth is likely to be wasted. If the datagram size is not divisible + by the MTU the last packet will not contain as much payload as it could, and the + payload over protocol header ratio decreases. + +3. It's expensive to fragment datagrams. Few routers are optimized to handle large + numbers of fragmented packets. Datagrams that have to fragment are likely to + be delayed significantly, and contribute to more CPU being used on routers. + Typically fragmentation (and other advanced IP features) are implemented in + software (slow) and not hardware (fast). + +The path MTU is the lowest MTU of any link along a path from two endpoints on the +internet. The MTU bottleneck isn't necessarily at one of the endpoints, but can +be anywhere in between. + +The most common MTU is 1500 bytes, which is the largest packet size for ethernet +networks. Many home DSL connections, however, tunnel IP through PPPoE (Point to +Point Protocol over Ethernet. Yes, that is the old dial-up modem protocol). This +protocol uses up 8 bytes per packet for its own header. + +If the user happens to be on an internet connection over a VPN, it will add another +layer, with its own packet headers. + +In short; if you would pick the largest possible packet size on an ethernet network, +1472, and stick with it, you would be quite likely to generate fragments for a lot +of connections. The fragments that will be created will be very small and especially +inflate the overhead waste. + +The other approach of picking a very conservative packet size, that would be very +unlikely to get fragmented has the following drawbacks: + +1. People on good, normal, networks will be penalized with a small packet size. + Both in terms of router load but also bandwidth waste. + +2. Software routers are typically not limited by the number of bytes they can route, + but the number of packets. Small packets means more of them, and more load on + software routers. + +The solution to the problem of finding the optimal packet size, is to dynamically +adjust the packet size and search for the largest size that can make it through +without being fragmented along the path. + +To help do this, you can set the DF bit (Don't Fragment) in your Datagrams. This +asks routers that otherwise would fragment packets to instead drop them, and send +back an ICMP message reporting the MTU of the link the packet couldn't fit. With +this message, it's very simple to discover the path MTU. You simply mark your packets +not to be fragmented, and change your packet size whenever you receive the ICMP +packet-too-big message. + +Unfortunately it's not quite that simple. There are a significant number of firewalls +in the wild blocking all ICMP messages. This means we can't rely on them, we also have +to guess that a packet was dropped because of its size. This is done by only marking +certain packets with DF, and if all other packets go through, except for the MTU probes, +we know that we need to lower our packet sizes. + +If we set up bounds for the path MTU (say the minimum internet MTU, 576 and ethernet's 1500), +we can do a binary search for the MTU. This would let us find it in just a few round-trips. + +On top of this, libtorrent has an optimization where it figures out which interface a +uTP connection will be sent over, and initialize the MTU ceiling to that interface's MTU. +This means that a VPN tunnel would advertise its MTU as lower, and the uTP connection would +immediately know to send smaller packets, no search required. It also has the side-effect +of being able to use much larger packet sizes for non-ethernet interfaces or ethernet links +with jumbo frames. + +clock drift +----------- + +.. image:: img/our_delay_base_thumb.png + :target: our_delay_base.png + :class: bw + :align: right + +Clock drift is clocks progressing at different rates. It's different from clock +skew which means clocks set to different values (but which may progress at the same +rate). + +Any clock drift between the two machines involved in a uTP transfer will result +in systematically inflated or deflated delay measurements. + +This can be solved by letting the base delay be the lowest seen sample in the last +*n* minutes. This is a trade-off between seeing a single packet go straight through +the queue, with no delay, and the amount of clock drift one can assume on normal computers. + +It turns out that it's fairly safe to assume that one of your packets will in fact go +straight through without any significant delay, once every 20 minutes or so. However, +the clock drift between normal computers can be as much as 17 ms in 10 minutes. 17 ms +is quite significant, especially if your target delay is 25 ms (as in the LEDBAT_ spec). + +Clocks progresses at different rates depending on temperature. This means computers +running hot are likely to have a clock drift compared to computers running cool. + +So, by updating the delay base periodically based on the lowest seen sample, you'll either +end up changing it upwards (artificially making the delay samples appear small) without +the congestion or delay actually having changed, or you'll end up with a significant clock +drift and have artificially low samples because of that. + +The solution to this problem is based on the fact that the clock drift is only a problem +for one of the sides of the connection. Only when your delay measurements keep increasing +is it a problem. If your delay measurements keep decreasing, the samples will simply push +down the delay base along with it. With this in mind, we can simply keep track of the +other end's delay measurements as well, applying the same logic to it. Whenever the +other end's base delay is adjusted downwards, we adjust our base delay upwards by the same +amount. + +This will accurately keep the base delay updated with the clock drift and improve +the delay measurements. The figure on the right shows the absolute timestamp differences +along with the base delay. The slope of the measurements is caused by clock drift. + +For more information on the clock drift compensation, see the slides from BitTorrent's +presentation at IPTPS10_. + +.. _IPTPS10: http://www.usenix.org/event/iptps10/tech/slides/cohen.pdf +.. _LEDBAT: https://datatracker.ietf.org/doc/draft-ietf-ledbat-congestion/ + +features +-------- + +libtorrent's uTP implementation includes the following features: + +* Path MTU discovery, including jumbo frames and detecting restricted + MTU tunnels. Binary search packet sizes to find the largest non-fragmented. +* Selective ACK. The ability to acknowledge individual packets in the + event of packet loss +* Fast resend. The first time a packet is lost, it's resent immediately. + Triggered by duplicate ACKs. +* Nagle's algorithm. Minimize protocol overhead by attempting to lump + full packets of payload together before sending a packet. +* Delayed ACKs to minimize protocol overhead. +* Microsecond resolution timestamps. +* Advertised receive window, to support download rate limiting. +* Correct handling of wrapping sequence numbers. +* Easy configuration of target-delay, gain-factor, timeouts, delayed-ACK + and socket buffers. + diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..387cc6f --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,27 @@ +project(libtorrent-examples) + +set(single_file_examples + simple_client + custom_storage + stats_counters + dump_torrent + dump_bdecode + make_torrent + connection_tester + upnp_test) + +if(CMAKE_CXX_COMPILER_ID MATCHES Clang) + add_compile_options(-Wno-implicit-int-float-conversion) +endif() + +foreach(example ${single_file_examples}) + add_executable(${example} "${example}.cpp") + target_link_libraries(${example} PRIVATE torrent-rasterbar) +endforeach(example) + +add_executable(client_test + client_test.cpp + print.cpp + torrent_view.cpp + session_view.cpp) +target_link_libraries(client_test PRIVATE torrent-rasterbar) diff --git a/examples/Jamfile b/examples/Jamfile new file mode 100644 index 0000000..ab08ba2 --- /dev/null +++ b/examples/Jamfile @@ -0,0 +1,62 @@ +import modules ; + +BOOST_ROOT = [ modules.peek : BOOST_ROOT ] ; + +use-project /torrent : .. ; + +if $(BOOST_ROOT) +{ + use-project /boost : $(BOOST_ROOT) ; +} + +project client_test + : requirements + multi /torrent//torrent + darwin:-Wno-unused-command-line-argument +# disable warning C4275: non DLL-interface classkey 'identifier' used as base for DLL-interface classkey 'identifier' + msvc:/wd4275 + # C4268: 'identifier' : 'const' static/global data initialized + # with compiler generated default constructor fills the object with zeros + msvc:/wd4268 + msvc:/wd4373 + clang:-Wno-implicit-int-float-conversion + @warnings + : default-build + static + 14 + 64 + ; + +exe client_test : client_test.cpp print.cpp torrent_view.cpp session_view.cpp ; + +exe simple_client : simple_client.cpp ; +exe custom_storage : custom_storage.cpp ; +exe bt-get : bt-get.cpp ; +exe bt-get2 : bt-get2.cpp ; +exe bt-get3 : bt-get3.cpp ; +exe stats_counters : stats_counters.cpp ; +exe dump_torrent : dump_torrent.cpp ; +exe torrent2magnet : torrent2magnet.cpp ; +exe magnet2torrent : magnet2torrent.cpp ; +exe dump_bdecode : dump_bdecode.cpp ; +exe make_torrent : make_torrent.cpp ; +exe connection_tester : connection_tester.cpp ; +exe upnp_test : upnp_test.cpp ; +exe check_files : check_files.cpp ; + +explicit stage_client_test ; +explicit stage_connection_tester ; +explicit stage ; +explicit stage_dependencies ; + +install stage : client_test connection_tester torrent2magnet magnet2torrent make_torrent dump_torrent check_files upnp_test stats_counters bt-get bt-get2 simple_client dump_bdecode custom_storage : . ; +install stage_client_test : client_test : . ; +install stage_connection_tester : connection_tester : . ; + +install stage_dependencies + : /torrent//torrent + : dependencies + on + SHARED_LIB + ; + diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100644 index 0000000..e7bbf41 --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,35 @@ +example_programs = \ + client_test \ + stats_counters \ + dump_torrent \ + make_torrent \ + simple_client \ + custom_storage \ + upnp_test \ + bt_get \ + bt_get2 \ + connection_tester + +if ENABLE_EXAMPLES +bin_PROGRAMS = $(example_programs) +endif + +EXTRA_PROGRAMS = $(example_programs) +EXTRA_DIST = Jamfile CMakeLists.txt session_view.hpp torrent_view.hpp print.hpp cmake/FindLibtorrentRasterbar.cmake + +client_test_SOURCES = client_test.cpp print.cpp session_view.cpp torrent_view.cpp +stats_counters_SOURCES = stats_counters.cpp +bt_get_SOURCES = bt-get.cpp +bt_get2_SOURCES = bt-get2.cpp +dump_torrent_SOURCES = dump_torrent.cpp +make_torrent_SOURCES = make_torrent.cpp +simple_client_SOURCES = simple_client.cpp +custom_storage_SOURCES = custom_storage.cpp +connection_tester_SOURCES = connection_tester.cpp +upnp_test_SOURCES = upnp_test.cpp + +LDADD = $(top_builddir)/src/libtorrent-rasterbar.la + +AM_CPPFLAGS = -ftemplate-depth-50 @DEBUGFLAGS@ +AM_LDFLAGS = @BOOST_SYSTEM_LIB@ @OPENSSL_LDFLAGS@ @OPENSSL_LIBS@ +DEFAULT_INCLUDES = -I$(top_srcdir)/include @OPENSSL_INCLUDES@ diff --git a/examples/bt-get.cpp b/examples/bt-get.cpp new file mode 100644 index 0000000..a61d788 --- /dev/null +++ b/examples/bt-get.cpp @@ -0,0 +1,82 @@ +/* + +Copyright (c) 2016-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +int main(int argc, char const* argv[]) try +{ + if (argc != 2) { + std::cerr << "usage: " << argv[0] << " " << std::endl; + return 1; + } + lt::settings_pack p; + p.set_int(lt::settings_pack::alert_mask, lt::alert_category::status + | lt::alert_category::error); + lt::session ses(p); + + lt::add_torrent_params atp = lt::parse_magnet_uri(argv[1]); + atp.save_path = "."; // save in current dir + lt::torrent_handle h = ses.add_torrent(std::move(atp)); + + for (;;) { + std::vector alerts; + ses.pop_alerts(&alerts); + + for (lt::alert const* a : alerts) { + std::cout << a->message() << std::endl; + // if we receive the finished alert or an error, we're done + if (lt::alert_cast(a)) { + goto done; + } + if (lt::alert_cast(a)) { + goto done; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + done: + std::cout << "done, shutting down" << std::endl; +} +catch (std::exception& e) +{ + std::cerr << "Error: " << e.what() << std::endl; +} + diff --git a/examples/bt-get2.cpp b/examples/bt-get2.cpp new file mode 100644 index 0000000..48a47f3 --- /dev/null +++ b/examples/bt-get2.cpp @@ -0,0 +1,182 @@ +/* + +Copyright (c) 2016-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using clk = std::chrono::steady_clock; + +// return the name of a torrent status enum +char const* state(lt::torrent_status::state_t s) +{ +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcovered-switch-default" +#endif + switch(s) { + case lt::torrent_status::checking_files: return "checking"; + case lt::torrent_status::downloading_metadata: return "dl metadata"; + case lt::torrent_status::downloading: return "downloading"; + case lt::torrent_status::finished: return "finished"; + case lt::torrent_status::seeding: return "seeding"; + case lt::torrent_status::checking_resume_data: return "checking resume"; + default: return "<>"; + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +} + +} // anonymous namespace + +int main(int argc, char const* argv[]) try +{ + if (argc != 2) { + std::cerr << "usage: " << argv[0] << " " << std::endl; + return 1; + } + + lt::settings_pack pack; + pack.set_int(lt::settings_pack::alert_mask + , lt::alert_category::error + | lt::alert_category::storage + | lt::alert_category::status); + + lt::session ses(pack); + clk::time_point last_save_resume = clk::now(); + + // load resume data from disk and pass it in as we add the magnet link + std::ifstream ifs(".resume_file", std::ios_base::binary); + ifs.unsetf(std::ios_base::skipws); + std::vector buf{std::istream_iterator(ifs) + , std::istream_iterator()}; + + lt::add_torrent_params magnet = lt::parse_magnet_uri(argv[1]); + if (buf.size()) { + lt::add_torrent_params atp = lt::read_resume_data(buf); + if (atp.info_hashes == magnet.info_hashes) magnet = std::move(atp); + } + magnet.save_path = "."; // save in current dir + ses.async_add_torrent(std::move(magnet)); + + // this is the handle we'll set once we get the notification of it being + // added + lt::torrent_handle h; + + // set when we're exiting + bool done = false; + for (;;) { + std::vector alerts; + ses.pop_alerts(&alerts); + + for (lt::alert const* a : alerts) { + if (auto at = lt::alert_cast(a)) { + h = at->handle; + } + // if we receive the finished alert or an error, we're done + if (lt::alert_cast(a)) { + h.save_resume_data(lt::torrent_handle::only_if_modified + | lt::torrent_handle::save_info_dict); + done = true; + } + if (lt::alert_cast(a)) { + std::cout << a->message() << std::endl; + done = true; + h.save_resume_data(lt::torrent_handle::only_if_modified + | lt::torrent_handle::save_info_dict); + } + + // when resume data is ready, save it + if (auto rd = lt::alert_cast(a)) { + std::ofstream of(".resume_file", std::ios_base::binary); + of.unsetf(std::ios_base::skipws); + auto const b = write_resume_data_buf(rd->params); + of.write(b.data(), int(b.size())); + if (done) goto done; + } + + if (lt::alert_cast(a)) { + if (done) goto done; + } + + if (auto st = lt::alert_cast(a)) { + if (st->status.empty()) continue; + + // we only have a single torrent, so we know which one + // the status is for + lt::torrent_status const& s = st->status[0]; + std::cout << '\r' << state(s.state) << ' ' + << (s.download_payload_rate / 1000) << " kB/s " + << (s.total_done / 1000) << " kB (" + << (s.progress_ppm / 10000) << "%) downloaded (" + << s.num_peers << " peers)\x1b[K"; + std::cout.flush(); + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + // ask the session to post a state_update_alert, to update our + // state output for the torrent + ses.post_torrent_updates(); + + // save resume data once every 30 seconds + if (clk::now() - last_save_resume > std::chrono::seconds(30)) { + h.save_resume_data(lt::torrent_handle::only_if_modified + | lt::torrent_handle::save_info_dict); + last_save_resume = clk::now(); + } + } + +done: + std::cout << "\ndone, shutting down" << std::endl; +} +catch (std::exception& e) +{ + std::cerr << "Error: " << e.what() << std::endl; +} + diff --git a/examples/bt-get3.cpp b/examples/bt-get3.cpp new file mode 100644 index 0000000..612d647 --- /dev/null +++ b/examples/bt-get3.cpp @@ -0,0 +1,217 @@ +/* + +Copyright (c) 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using clk = std::chrono::steady_clock; + +// return the name of a torrent status enum +char const* state(lt::torrent_status::state_t s) +{ +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcovered-switch-default" +#endif + switch(s) { + case lt::torrent_status::checking_files: return "checking"; + case lt::torrent_status::downloading_metadata: return "dl metadata"; + case lt::torrent_status::downloading: return "downloading"; + case lt::torrent_status::finished: return "finished"; + case lt::torrent_status::seeding: return "seeding"; + case lt::torrent_status::checking_resume_data: return "checking resume"; + default: return "<>"; + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +} + +std::vector load_file(char const* filename) +{ + std::ifstream ifs(filename, std::ios_base::binary); + ifs.unsetf(std::ios_base::skipws); + return {std::istream_iterator(ifs), std::istream_iterator()}; +} + +// set when we're exiting +std::atomic shut_down{false}; + +void sighandler(int) { shut_down = true; } + +} // anonymous namespace + +int main(int argc, char const* argv[]) try +{ + if (argc != 2) { + std::cerr << "usage: " << argv[0] << " " << std::endl; + return 1; + } + + // load session parameters + auto session_params = load_file(".session"); + lt::session_params params = session_params.empty() + ? lt::session_params() : lt::read_session_params(session_params); + params.settings.set_int(lt::settings_pack::alert_mask + , lt::alert_category::error + | lt::alert_category::storage + | lt::alert_category::status); + + lt::session ses(params); + clk::time_point last_save_resume = clk::now(); + + // load resume data from disk and pass it in as we add the magnet link + auto buf = load_file(".resume_file"); + + lt::add_torrent_params magnet = lt::parse_magnet_uri(argv[1]); + if (buf.size()) { + lt::add_torrent_params atp = lt::read_resume_data(buf); + if (atp.info_hashes == magnet.info_hashes) magnet = std::move(atp); + } + magnet.save_path = "."; // save in current dir + ses.async_add_torrent(std::move(magnet)); + + // this is the handle we'll set once we get the notification of it being + // added + lt::torrent_handle h; + + std::signal(SIGINT, &sighandler); + + // set when we're exiting + bool done = false; + for (;;) { + std::vector alerts; + ses.pop_alerts(&alerts); + + if (shut_down) { + shut_down = false; + auto const handles = ses.get_torrents(); + if (handles.size() == 1) { + handles[0].save_resume_data(lt::torrent_handle::only_if_modified + | lt::torrent_handle::save_info_dict); + done = true; + } + } + + for (lt::alert const* a : alerts) { + if (auto at = lt::alert_cast(a)) { + h = at->handle; + } + // if we receive the finished alert or an error, we're done + if (lt::alert_cast(a)) { + h.save_resume_data(lt::torrent_handle::only_if_modified + | lt::torrent_handle::save_info_dict); + done = true; + } + if (lt::alert_cast(a)) { + std::cout << a->message() << std::endl; + done = true; + h.save_resume_data(lt::torrent_handle::only_if_modified + | lt::torrent_handle::save_info_dict); + } + + // when resume data is ready, save it + if (auto rd = lt::alert_cast(a)) { + std::ofstream of(".resume_file", std::ios_base::binary); + of.unsetf(std::ios_base::skipws); + auto const b = write_resume_data_buf(rd->params); + of.write(b.data(), int(b.size())); + if (done) goto done; + } + + if (lt::alert_cast(a)) { + if (done) goto done; + } + + if (auto st = lt::alert_cast(a)) { + if (st->status.empty()) continue; + + // we only have a single torrent, so we know which one + // the status is for + lt::torrent_status const& s = st->status[0]; + std::cout << '\r' << state(s.state) << ' ' + << (s.download_payload_rate / 1000) << " kB/s " + << (s.total_done / 1000) << " kB (" + << (s.progress_ppm / 10000) << "%) downloaded (" + << s.num_peers << " peers)\x1b[K"; + std::cout.flush(); + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + // ask the session to post a state_update_alert, to update our + // state output for the torrent + ses.post_torrent_updates(); + + // save resume data once every 30 seconds + if (clk::now() - last_save_resume > std::chrono::seconds(30)) { + h.save_resume_data(lt::torrent_handle::only_if_modified + | lt::torrent_handle::save_info_dict); + last_save_resume = clk::now(); + } + } + +done: + std::cout << "\nsaving session state" << std::endl; + { + std::ofstream of(".session", std::ios_base::binary); + of.unsetf(std::ios_base::skipws); + auto const b = write_session_params_buf(ses.session_state() + , lt::save_state_flags_t::all()); + of.write(b.data(), int(b.size())); + } + + std::cout << "\ndone, shutting down" << std::endl; +} +catch (std::exception& e) +{ + std::cerr << "Error: " << e.what() << std::endl; +} + + diff --git a/examples/check_files.cpp b/examples/check_files.cpp new file mode 100644 index 0000000..dc7d416 --- /dev/null +++ b/examples/check_files.cpp @@ -0,0 +1,170 @@ +/* + +Copyright (c) 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using std::chrono::milliseconds; +using std::chrono::seconds; +using std::chrono::duration_cast; + +int main(int argc, char* argv[]) try +{ + if (argc != 4) { + std::cerr << "usage: ./check_files torrent-file download-dir output-resume-file\n"; + return 1; + } + + lt::session_params ses_params; + lt::settings_pack& pack = ses_params.settings; + // start an off-line session. No listen sockets, no DHT or LSD and no port + // forwarding + pack.set_bool(lt::settings_pack::enable_dht, false); + pack.set_bool(lt::settings_pack::enable_lsd, false); + pack.set_bool(lt::settings_pack::enable_upnp, false); + pack.set_bool(lt::settings_pack::enable_natpmp, false); + pack.set_str(lt::settings_pack::listen_interfaces, ""); + lt::session ses(ses_params); + lt::add_torrent_params p = lt::load_torrent_file(argv[1]); + p.save_path = argv[2]; + + // stop_when_ready will stop the torrent immediately when it's done + // checking. + p.flags |= lt::torrent_flags::stop_when_ready; + // start the torrent in non-paused mode + p.flags &= ~(lt::torrent_flags::paused | lt::torrent_flags::auto_managed); + + auto const total_size = p.ti->total_size(); + lt::torrent_handle h = ses.add_torrent(std::move(p)); + + ses.post_torrent_updates(); + + using clock_type = std::chrono::system_clock; + auto const start_time = clock_type::now(); + + std::vector alerts; + for (;;) + { + ses.pop_alerts(&alerts); + for (auto a : alerts) + { + if (auto const* su = lt::alert_cast(a)) + { + ses.post_torrent_updates(); + for (lt::torrent_status const& st : su->status) + { + if (st.handle != h) continue; + + if (st.state != lt::torrent_status::checking_files + && st.state != lt::torrent_status::checking_resume_data) + { + h.save_resume_data(); + std::puts("\nrequest resume data"); + goto done_checking; + } + + // the number of bytes we've checked so far + auto const bytes_progress = double(st.progress_ppm) + / 1000000. * double(total_size); + auto const now = clock_type::now(); + // the amount of time we've spent so far, in seconds + auto const duration = duration_cast(now - start_time) / 1000.; + auto const rate = bytes_progress / duration.count(); + + std::printf("\rchecking %5.2f%% %7.2f MB/s pieces: %-5d ETA: %.1fs " + , st.progress_ppm / 10000.0 + , rate / 1000000. + , st.num_pieces + , (total_size - bytes_progress) / rate); + std::fflush(stdout); + } + } + if (auto const* err = lt::alert_cast(a)) + { + std::printf("\nfile error: (%s) %s\nfile: %s\n" + , operation_name(err->op) + , err->error.message().c_str() + , err->filename()); + return 1; + } + if (auto const* err = lt::alert_cast(a)) + { + std::printf("\ntorrent error: %s\nfile: %s\n" + , err->error.message().c_str() + , err->filename()); + return 1; + } + } + std::this_thread::sleep_for(milliseconds(100)); + } + done_checking: + + for (;;) + { + ses.wait_for_alert(seconds(1)); + ses.pop_alerts(&alerts); + for (auto a : alerts) + { + if (auto const* srd = lt::alert_cast(a)) + { + std::printf("saving resume data \"%s\"\n", argv[3]); + std::ofstream of(argv[3], std::ios_base::binary); + of.unsetf(std::ios_base::skipws); + auto const b = lt::write_resume_data_buf(srd->params); + of.write(b.data(), int(b.size())); + goto done_saving; + } + if (auto const* rdf = lt::alert_cast(a)) + { + std::printf("resume data failed: %s\n", rdf->error.message().c_str()); + return 1; + } + } + } + done_saving: + + return 0; +} +catch (std::exception const& e) { + std::cerr << "ERROR: " << e.what() << "\n"; + return 1; +} diff --git a/examples/client_test.cpp b/examples/client_test.cpp new file mode 100644 index 0000000..59e68ef --- /dev/null +++ b/examples/client_test.cpp @@ -0,0 +1,2372 @@ +/* + +Copyright (c) 2003-2022, Arvid Norberg +Copyright (c) 2015, Mike Tzou +Copyright (c) 2016, 2018-2019, Alden Torres +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2017, AllSeeingEyeTolledEweSew +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2019, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include // for snprintf +#include // for atoi +#include +#include +#include +#include +#include +#include // for min()/max() + +#include "libtorrent/config.hpp" + +#ifdef TORRENT_WINDOWS +#include // for _mkdir and _getcwd +#include // for _stat +#include +#endif + +#ifdef TORRENT_UTP_LOG_ENABLE +#include "libtorrent/utp_stream.hpp" +#endif + +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/read_resume_data.hpp" +#include "libtorrent/write_resume_data.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/disk_interface.hpp" // for open_file_state +#include "libtorrent/disabled_disk_io.hpp" // for disabled_disk_io_constructor +#include "libtorrent/load_torrent.hpp" + +#include "torrent_view.hpp" +#include "session_view.hpp" +#include "print.hpp" + + +#ifdef _WIN32 + +#include +#include + +#else + +#include +#include +#include +#include +#include + +#endif + +namespace { + +using lt::total_milliseconds; +using lt::alert; +using lt::piece_index_t; +using lt::file_index_t; +using lt::torrent_handle; +using lt::add_torrent_params; +using lt::total_seconds; +using lt::torrent_flags_t; +using lt::seconds; +using lt::operator "" _sv; +using lt::address_v4; +using lt::address_v6; +using lt::make_address_v6; +using lt::make_address_v4; +using lt::make_address; + +using std::chrono::duration_cast; +using std::stoi; + +#ifdef _WIN32 + +bool sleep_and_input(int* c, lt::time_duration const sleep) +{ + for (int i = 0; i < 2; ++i) + { + if (_kbhit()) + { + *c = _getch(); + return true; + } + std::this_thread::sleep_for(sleep / 2); + } + return false; +} + +#else + +struct set_keypress +{ + enum terminal_mode { + echo = 1, + canonical = 2 + }; + + explicit set_keypress(std::uint8_t const mode = 0) + { + using ul = unsigned long; + + termios new_settings; + tcgetattr(0, &stored_settings); + new_settings = stored_settings; + // Disable canonical mode, and set buffer size to 1 byte + // and disable echo + if (mode & echo) new_settings.c_lflag |= ECHO; + else new_settings.c_lflag &= ul(~ECHO); + + if (mode & canonical) new_settings.c_lflag |= ICANON; + else new_settings.c_lflag &= ul(~ICANON); + + new_settings.c_cc[VTIME] = 0; + new_settings.c_cc[VMIN] = 1; + tcsetattr(0,TCSANOW,&new_settings); + } + ~set_keypress() { tcsetattr(0, TCSANOW, &stored_settings); } +private: + termios stored_settings; +}; + +bool sleep_and_input(int* c, lt::time_duration const sleep) +{ + lt::time_point const done = lt::clock_type::now() + sleep; + int ret = 0; +retry: + fd_set set; + FD_ZERO(&set); + FD_SET(0, &set); + auto const delay = total_milliseconds(done - lt::clock_type::now()); + timeval tv = {int(delay / 1000), int((delay % 1000) * 1000) }; + ret = select(1, &set, nullptr, nullptr, &tv); + if (ret > 0) + { + *c = getc(stdin); + return true; + } + if (errno == EINTR) + { + if (lt::clock_type::now() < done) + goto retry; + return false; + } + + if (ret < 0 && errno != 0 && errno != ETIMEDOUT) + { + std::fprintf(stderr, "select failed: %s\n", strerror(errno)); + std::this_thread::sleep_for(lt::milliseconds(500)); + } + + return false; +} + +#endif + +bool print_trackers = false; +bool print_peers = false; +bool print_peers_legend = false; +bool print_connecting_peers = false; +bool print_log = false; +bool print_downloads = false; +bool print_matrix = false; +bool print_file_progress = false; +bool print_piece_availability = false; +bool show_pad_files = false; +bool show_dht_status = false; + +bool print_ip = true; +bool print_peaks = false; +bool print_local_ip = false; +bool print_timers = false; +bool print_block = false; +bool print_fails = false; +bool print_send_bufs = true; +bool print_disk_stats = false; + +// the number of times we've asked to save resume data +// without having received a response (successful or failure) +int num_outstanding_resume_data = 0; + +#ifndef TORRENT_DISABLE_DHT +std::vector dht_active_requests; +std::vector dht_routing_table; +#endif + +std::string to_hex(lt::sha1_hash const& s) +{ + std::stringstream ret; + ret << s; + return ret.str(); +} + +bool load_file(std::string const& filename, std::vector& v + , int limit = 8000000) +{ + std::fstream f(filename, std::ios_base::in | std::ios_base::binary); + f.seekg(0, std::ios_base::end); + auto const s = f.tellg(); + if (s > limit || s < 0) return false; + f.seekg(0, std::ios_base::beg); + v.resize(static_cast(s)); + if (s == std::fstream::pos_type(0)) return !f.fail(); + f.read(v.data(), int(v.size())); + return !f.fail(); +} + +bool is_absolute_path(std::string const& f) +{ + if (f.empty()) return false; +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + int i = 0; + // match the xx:\ or xx:/ form + while (f[i] && strchr("abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ", f[i])) ++i; + if (i < int(f.size()-1) && f[i] == ':' && (f[i+1] == '\\' || f[i+1] == '/')) + return true; + + // match the \\ form + if (int(f.size()) >= 2 && f[0] == '\\' && f[1] == '\\') + return true; + return false; +#else + if (f[0] == '/') return true; + return false; +#endif +} + +std::string path_append(std::string const& lhs, std::string const& rhs) +{ + if (lhs.empty() || lhs == ".") return rhs; + if (rhs.empty() || rhs == ".") return lhs; + +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) +#define TORRENT_SEPARATOR "\\" + bool need_sep = lhs[lhs.size()-1] != '\\' && lhs[lhs.size()-1] != '/'; +#else +#define TORRENT_SEPARATOR "/" + bool need_sep = lhs[lhs.size()-1] != '/'; +#endif + return lhs + (need_sep?TORRENT_SEPARATOR:"") + rhs; +} + +std::string make_absolute_path(std::string const& p) +{ + if (is_absolute_path(p)) return p; + std::string ret; +#if defined TORRENT_WINDOWS + char* cwd = ::_getcwd(nullptr, 0); + ret = path_append(cwd, p); + std::free(cwd); +#else + char* cwd = ::getcwd(nullptr, 0); + ret = path_append(cwd, p); + std::free(cwd); +#endif + return ret; +} + +std::string print_endpoint(lt::tcp::endpoint const& ep) +{ + using namespace lt; + char buf[200]; + address const& addr = ep.address(); + if (addr.is_v6()) + std::snprintf(buf, sizeof(buf), "[%s]:%d", addr.to_string().c_str(), ep.port()); + else + std::snprintf(buf, sizeof(buf), "%s:%d", addr.to_string().c_str(), ep.port()); + return buf; +} + +using lt::torrent_status; + +FILE* g_log_file = nullptr; + +int peer_index(lt::tcp::endpoint addr, std::vector const& peers) +{ + using namespace lt; + auto i = std::find_if(peers.begin(), peers.end() + , [&addr](peer_info const& pi) { return pi.ip == addr; }); + if (i == peers.end()) return -1; + + return int(i - peers.begin()); +} + +#if TORRENT_USE_I2P +void base32encode_i2p(lt::sha256_hash const& s, std::string& out, int limit) +{ + static char const base32_table[] = + { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '2', '3', '4', '5', '6', '7' + }; + + static std::array const input_output_mapping{{0, 2, 4, 5, 7, 8}}; + + std::array inbuf; + std::array outbuf; + + TORRENT_ASSERT(s.size() % 5 ); + for (auto i = s.begin(); i != s.end();) + { + int const available_input = std::min(int(inbuf.size()), int(s.end() - i)); + + // clear input buffer + inbuf.fill(0); + + // read a chunk of input into inbuf + std::copy(i, i + available_input, inbuf.begin()); + i += available_input; + + // encode inbuf to outbuf + outbuf[0] = (inbuf[0] & 0xf8) >> 3; + outbuf[1] = (((inbuf[0] & 0x07) << 2) | ((inbuf[1] & 0xc0) >> 6)) & 0xff; + outbuf[2] = ((inbuf[1] & 0x3e) >> 1); + outbuf[3] = (((inbuf[1] & 0x01) << 4) | ((inbuf[2] & 0xf0) >> 4)) & 0xff; + outbuf[4] = (((inbuf[2] & 0x0f) << 1) | ((inbuf[3] & 0x80) >> 7)) & 0xff; + outbuf[5] = ((inbuf[3] & 0x7c) >> 2); + outbuf[6] = (((inbuf[3] & 0x03) << 3) | ((inbuf[4] & 0xe0) >> 5)) & 0xff; + outbuf[7] = inbuf[4] & 0x1f; + + // write output + int const num_out = input_output_mapping[std::size_t(available_input)]; + for (int j = 0; j < num_out; ++j) + { + out += base32_table[outbuf[std::size_t(j)]]; + --limit; + if (limit <= 0) return; + } + } +} +#endif + +// returns the number of lines printed +int print_peer_info(std::string& out + , std::vector const& peers, int max_lines) +{ + using namespace lt; + int pos = 0; + if (print_ip) out += "IP "; + if (print_local_ip) out += "local IP "; + out += "progress down (total"; + if (print_peaks) out += " | peak "; + out += " ) up (total"; + if (print_peaks) out += " | peak "; + out += " ) sent-req tmo bsy rcv flags dn up source "; + if (print_fails) out += "fail hshf "; + if (print_send_bufs) out += " rq sndb (recvb |alloc | wmrk ) q-bytes "; + if (print_timers) out += "inactive wait timeout q-time "; + out += " v disk ^ rtt "; + if (print_block) out += "block-progress "; + out += "client \x1b[K\n"; + ++pos; + + char str[500]; + for (std::vector::const_iterator i = peers.begin(); + i != peers.end(); ++i) + { + if ((i->flags & (peer_info::handshake | peer_info::connecting) + && !print_connecting_peers)) + { + continue; + } + + if (print_ip) + { +#if TORRENT_USE_I2P + if (i->flags & peer_info::i2p_socket) + { + base32encode_i2p(i->i2p_destination(), out, 31); + } + else +#endif + { + std::snprintf(str, sizeof(str), "%-30s ", ::print_endpoint(i->ip).c_str()); + out += str; + } + } + if (print_local_ip) + { +#if TORRENT_USE_I2P + if (i->flags & peer_info::i2p_socket) + out += " "; + else +#endif + { + std::snprintf(str, sizeof(str), "%-30s ", ::print_endpoint(i->local_endpoint).c_str()); + out += str; + } + } + + char temp[10]; + std::snprintf(temp, sizeof(temp), "%d/%d" + , i->download_queue_length + , i->target_dl_queue_length); + temp[7] = 0; + + char peer_progress[10]; + std::snprintf(peer_progress, sizeof(peer_progress), "%.1f%%", i->progress_ppm / 10000.0); + std::snprintf(str, sizeof(str) + , "%s %s%s (%s%s) %s%s (%s%s) %s%7s %4d%4d%4d %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s %s%s%s %s%s%s %s%s%s%s%s%s " + , progress_bar(i->progress_ppm / 1000, 15, col_green, '#', '-', peer_progress).c_str() + , esc("32"), add_suffix(i->down_speed, "/s").c_str() + , add_suffix(i->total_download).c_str() + , print_peaks ? ("|" + add_suffix(i->download_rate_peak, "/s")).c_str() : "" + , esc("31"), add_suffix(i->up_speed, "/s").c_str(), add_suffix(i->total_upload).c_str() + , print_peaks ? ("|" + add_suffix(i->upload_rate_peak, "/s")).c_str() : "" + , esc("0") + + , temp // sent requests and target number of outstanding reqs. + , i->timed_out_requests + , i->busy_requests + , i->upload_queue_length + + , color("I", (i->flags & peer_info::interesting)?col_white:col_blue).c_str() + , color("C", (i->flags & peer_info::choked)?col_white:col_blue).c_str() + , color("i", (i->flags & peer_info::remote_interested)?col_white:col_blue).c_str() + , color("c", (i->flags & peer_info::remote_choked)?col_white:col_blue).c_str() + , color("x", (i->flags & peer_info::supports_extensions)?col_white:col_blue).c_str() + , color("o", (i->flags & peer_info::local_connection)?col_white:col_blue).c_str() + , color("p", (i->flags & peer_info::on_parole)?col_white:col_blue).c_str() + , color("O", (i->flags & peer_info::optimistic_unchoke)?col_white:col_blue).c_str() + , color("S", (i->flags & peer_info::snubbed)?col_white:col_blue).c_str() + , color("U", (i->flags & peer_info::upload_only)?col_white:col_blue).c_str() + , color("e", (i->flags & peer_info::endgame_mode)?col_white:col_blue).c_str() + , color("E", (i->flags & peer_info::rc4_encrypted)?col_white:(i->flags & peer_info::plaintext_encrypted)?col_cyan:col_blue).c_str() + , color("h", (i->flags & peer_info::holepunched)?col_white:col_blue).c_str() + , color("s", (i->flags & peer_info::seed)?col_white:col_blue).c_str() + , color("u", (i->flags & peer_info::utp_socket)?col_white:col_blue).c_str() + , color("I", (i->flags & peer_info::i2p_socket)?col_white:col_blue).c_str() + + , color("d", (i->read_state & peer_info::bw_disk)?col_white:col_blue).c_str() + , color("l", (i->read_state & peer_info::bw_limit)?col_white:col_blue).c_str() + , color("n", (i->read_state & peer_info::bw_network)?col_white:col_blue).c_str() + , color("d", (i->write_state & peer_info::bw_disk)?col_white:col_blue).c_str() + , color("l", (i->write_state & peer_info::bw_limit)?col_white:col_blue).c_str() + , color("n", (i->write_state & peer_info::bw_network)?col_white:col_blue).c_str() + + , color("t", (i->source & peer_info::tracker)?col_white:col_blue).c_str() + , color("p", (i->source & peer_info::pex)?col_white:col_blue).c_str() + , color("d", (i->source & peer_info::dht)?col_white:col_blue).c_str() + , color("l", (i->source & peer_info::lsd)?col_white:col_blue).c_str() + , color("r", (i->source & peer_info::resume_data)?col_white:col_blue).c_str() + , color("i", (i->source & peer_info::incoming)?col_white:col_blue).c_str()); + out += str; + + if (print_fails) + { + std::snprintf(str, sizeof(str), "%4d %4d " + , i->failcount, i->num_hashfails); + out += str; + } + if (print_send_bufs) + { + std::snprintf(str, sizeof(str), "%3d %6s %6s|%6s|%6s%7s " + , i->requests_in_buffer + , add_suffix(i->used_send_buffer).c_str() + , add_suffix(i->used_receive_buffer).c_str() + , add_suffix(i->receive_buffer_size).c_str() + , add_suffix(i->receive_buffer_watermark).c_str() + , add_suffix(i->queue_bytes).c_str()); + out += str; + } + if (print_timers) + { + char req_timeout[20] = "-"; + // timeout is only meaningful if there is at least one outstanding + // request to the peer + if (i->download_queue_length > 0) + std::snprintf(req_timeout, sizeof(req_timeout), "%d", i->request_timeout); + + std::snprintf(str, sizeof(str), "%8d %4d %7s %6d " + , int(total_seconds(i->last_active)) + , int(total_seconds(i->last_request)) + , req_timeout + , int(total_seconds(i->download_queue_time))); + out += str; + } + std::snprintf(str, sizeof(str), "%s|%s %5d " + , add_suffix(i->pending_disk_bytes).c_str() + , add_suffix(i->pending_disk_read_bytes).c_str() + , i->rtt); + out += str; + + if (print_block) + { + if (i->downloading_piece_index >= piece_index_t(0)) + { + char buf[50]; + std::snprintf(buf, sizeof(buf), "%d:%d" + , static_cast(i->downloading_piece_index), i->downloading_block_index); + out += progress_bar( + i->downloading_progress * 1000 / i->downloading_total, 14, col_green, '-', '#', buf); + } + else + { + out += progress_bar(0, 14); + } + } + + out += " "; + + if (i->flags & lt::peer_info::handshake) + { + out += esc("31"); + out += " waiting for handshake"; + out += esc("0"); + } + else if (i->flags & lt::peer_info::connecting) + { + out += esc("31"); + out += " connecting to peer"; + out += esc("0"); + } + else + { + out += " "; + out += i->client; + } + out += "\x1b[K\n"; + ++pos; + if (pos >= max_lines) break; + } + return pos; +} + +// returns the number of lines printed +int print_peer_legend(std::string& out, int max_lines) +{ +#ifdef _MSC_VER +#pragma warning(push, 1) +// warning C4566: character represented by universal-character-name '\u256F' +// cannot be represented in the current code page (1252) +#pragma warning(disable: 4566) +#endif + + std::array lines{{ + " we are interested \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2502\u2502\u2502\u2570\u2500\u2500\u2500 incoming\x1b[K\n", + " we have choked \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2502\u2502\u2570\u2500\u2500\u2500 resume data\x1b[K\n", + "remote is interested \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2502\u2570\u2500\u2500\u2500 local peer discovery\x1b[K\n", + " remote has choked \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2570\u2500\u2500\u2500 DHT\x1b[K\n", + " supports extensions \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2570\u2500\u2500\u2500 peer exchange\x1b[K\n", + " outgoing connection \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2502 \u2570\u2500\u2500\u2500 tracker\x1b[K\n", + " on parole \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502 \u2502\u2502\u2570\u2500\u253c\u253c\u2534\u2500\u2500\u2500 network\x1b[K\n", + " optimistic unchoke \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2502\u2502\u2502\u2502\u2502 \u2502\u2570\u2500\u2500\u253c\u2534\u2500\u2500\u2500 rate limit\x1b[K\n", + " snubbed \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2502\u2502\u2502\u2502 \u2570\u2500\u2500\u2500\u2534\u2500\u2500\u2500 disk\x1b[K\n", + " upload only \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2502\u2502\u2570\u2500\u2500\u2500 i2p\x1b[K\n", + " end-game mode \u2500\u2500\u2500\u256f\u2502\u2502\u2502\u2570\u2500\u2500\u2500 uTP\x1b[K\n", + " obfuscation level \u2500\u2500\u2500\u256f\u2502\u2570\u2500\u2500\u2500 seed\x1b[K\n", + " hole-punched \u2500\u2500\u2500\u256f\x1b[K\n", + }}; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + char const* ip = " "; + char const* indentation = " "; + int ret = 0; + for (auto const& l : lines) + { + if (max_lines <= 0) break; + ++ret; + out += indentation; + if (print_ip) + out += ip; + if (print_local_ip) + out += ip; + out += l; + } + return ret; +} + +lt::storage_mode_t allocation_mode = lt::storage_mode_sparse; +std::string save_path("."); +int torrent_upload_limit = 0; +int torrent_download_limit = 0; +std::string monitor_dir; +int poll_interval = 5; +int max_connections_per_torrent = 50; +bool seed_mode = false; +bool stats_enabled = false; +bool exit_on_finish = false; + +bool share_mode = false; + +bool quit = false; + +#ifndef _WIN32 +void signal_handler(int) +{ + // make the main loop terminate + quit = true; +} +#endif + +// if non-empty, a peer that will be added to all torrents +std::string peer; + +void print_settings(int const start, int const num + , char const* const type) +{ + for (int i = start; i < start + num; ++i) + { + char const* name = lt::name_for_setting(i); + if (!name || name[0] == '\0') continue; + std::printf("%s=<%s>\n", name, type); + } +} + +void assign_setting(lt::settings_pack& settings, std::string const& key, char const* value) +{ + int const sett_name = lt::setting_by_name(key); + if (sett_name < 0) + { + std::fprintf(stderr, "unknown setting: \"%s\"\n", key.c_str()); + std::exit(1); + } + + using lt::settings_pack; + + switch (sett_name & settings_pack::type_mask) + { + case settings_pack::string_type_base: + settings.set_str(sett_name, value); + break; + case settings_pack::bool_type_base: + if (value == "1"_sv || value == "on"_sv || value == "true"_sv) + { + settings.set_bool(sett_name, true); + } + else if (value == "0"_sv || value == "off"_sv || value == "false"_sv) + { + settings.set_bool(sett_name, false); + } + else + { + std::fprintf(stderr, "invalid value for \"%s\". expected 0 or 1\n" + , key.c_str()); + std::exit(1); + } + break; + case settings_pack::int_type_base: + using namespace lt::literals; + static std::map const enums = { + {"no_piece_suggestions"_sv, settings_pack::no_piece_suggestions}, + {"suggest_read_cache"_sv, settings_pack::suggest_read_cache}, + {"fixed_slots_choker"_sv, settings_pack::fixed_slots_choker}, + {"rate_based_choker"_sv, settings_pack::rate_based_choker}, + {"round_robin"_sv, settings_pack::round_robin}, + {"fastest_upload"_sv, settings_pack::fastest_upload}, + {"anti_leech"_sv, settings_pack::anti_leech}, + {"enable_os_cache"_sv, settings_pack::enable_os_cache}, + {"disable_os_cache"_sv, settings_pack::disable_os_cache}, + {"write_through"_sv, settings_pack::write_through}, + {"prefer_tcp"_sv, settings_pack::prefer_tcp}, + {"peer_proportional"_sv, settings_pack::peer_proportional}, + {"pe_forced"_sv, settings_pack::pe_forced}, + {"pe_enabled"_sv, settings_pack::pe_enabled}, + {"pe_disabled"_sv, settings_pack::pe_disabled}, + {"pe_plaintext"_sv, settings_pack::pe_plaintext}, + {"pe_rc4"_sv, settings_pack::pe_rc4}, + {"pe_both"_sv, settings_pack::pe_both}, + {"none"_sv, settings_pack::none}, + {"socks4"_sv, settings_pack::socks4}, + {"socks5"_sv, settings_pack::socks5}, + {"socks5_pw"_sv, settings_pack::socks5_pw}, + {"http"_sv, settings_pack::http}, + {"http_pw"_sv, settings_pack::http_pw}, + }; + + { + auto const it = enums.find(lt::string_view(value)); + if (it != enums.end()) + { + settings.set_int(sett_name, it->second); + break; + } + } + + static std::map const alert_categories = { + {"error"_sv, lt::alert_category::error}, + {"peer"_sv, lt::alert_category::peer}, + {"port_mapping"_sv, lt::alert_category::port_mapping}, + {"storage"_sv, lt::alert_category::storage}, + {"tracker"_sv, lt::alert_category::tracker}, + {"connect"_sv, lt::alert_category::connect}, + {"status"_sv, lt::alert_category::status}, + {"ip_block"_sv, lt::alert_category::ip_block}, + {"performance_warning"_sv, lt::alert_category::performance_warning}, + {"dht"_sv, lt::alert_category::dht}, + {"session_log"_sv, lt::alert_category::session_log}, + {"torrent_log"_sv, lt::alert_category::torrent_log}, + {"peer_log"_sv, lt::alert_category::peer_log}, + {"incoming_request"_sv, lt::alert_category::incoming_request}, + {"dht_log"_sv, lt::alert_category::dht_log}, + {"dht_operation"_sv, lt::alert_category::dht_operation}, + {"port_mapping_log"_sv, lt::alert_category::port_mapping_log}, + {"picker_log"_sv, lt::alert_category::picker_log}, + {"file_progress"_sv, lt::alert_category::file_progress}, + {"piece_progress"_sv, lt::alert_category::piece_progress}, + {"upload"_sv, lt::alert_category::upload}, + {"block_progress"_sv, lt::alert_category::block_progress}, + {"all"_sv, lt::alert_category::all}, + }; + + std::stringstream flags(value); + std::string f; + lt::alert_category_t val; + while (std::getline(flags, f, ',')) try + { + auto const it = alert_categories.find(f); + if (it == alert_categories.end()) + val |= lt::alert_category_t{unsigned(std::stoi(f))}; + else + val |= it->second; + } + catch (std::invalid_argument const&) + { + std::fprintf(stderr, "invalid value for \"%s\". expected integer or enum value\n" + , key.c_str()); + std::exit(1); + } + + settings.set_int(sett_name, val); + break; + } +} + +std::string resume_file(lt::info_hash_t const& info_hash) +{ + return path_append(save_path, path_append(".resume" + , to_hex(info_hash.get_best()) + ".resume")); +} + +void set_torrent_params(lt::add_torrent_params& p) +{ + p.max_connections = max_connections_per_torrent; + p.max_uploads = -1; + p.upload_limit = torrent_upload_limit; + p.download_limit = torrent_download_limit; + + if (seed_mode) p.flags |= lt::torrent_flags::seed_mode; + if (share_mode) p.flags |= lt::torrent_flags::share_mode; + p.save_path = save_path; + p.storage_mode = allocation_mode; +} + +void add_magnet(lt::session& ses, lt::string_view uri) +{ + lt::error_code ec; + lt::add_torrent_params p = lt::parse_magnet_uri(uri.to_string(), ec); + + if (ec) + { + std::printf("invalid magnet link \"%s\": %s\n" + , uri.to_string().c_str(), ec.message().c_str()); + return; + } + + std::vector resume_data; + if (load_file(resume_file(p.info_hashes), resume_data)) + { + p = lt::read_resume_data(resume_data, ec); + if (ec) std::printf(" failed to load resume data: %s\n", ec.message().c_str()); + } + + set_torrent_params(p); + + std::printf("adding magnet: %s\n", uri.to_string().c_str()); + ses.async_add_torrent(std::move(p)); +} + +// return false on failure +bool add_torrent(lt::session& ses, std::string torrent) try +{ + using lt::storage_mode_t; + + static int counter = 0; + + std::printf("[%d] %s\n", counter++, torrent.c_str()); + + lt::error_code ec; + lt::add_torrent_params atp = lt::load_torrent_file(torrent); + + std::vector resume_data; + if (load_file(resume_file(atp.info_hashes), resume_data)) + { + lt::add_torrent_params rd = lt::read_resume_data(resume_data, ec); + if (ec) std::printf(" failed to load resume data: %s\n", ec.message().c_str()); + else atp = rd; + } + + set_torrent_params(atp); + + atp.flags &= ~lt::torrent_flags::duplicate_is_error; + ses.async_add_torrent(std::move(atp)); + return true; +} +catch (lt::system_error const& e) +{ + std::printf("failed to load torrent \"%s\": %s\n" + , torrent.c_str(), e.code().message().c_str()); + return false; +} + +std::vector list_dir(std::string path + , bool (*filter_fun)(lt::string_view) + , lt::error_code& ec) +{ + std::vector ret; +#ifdef TORRENT_WINDOWS + if (!path.empty() && path[path.size()-1] != '\\') path += "\\*"; + else path += "*"; + + WIN32_FIND_DATAA fd; + HANDLE handle = FindFirstFileA(path.c_str(), &fd); + if (handle == INVALID_HANDLE_VALUE) + { + ec.assign(GetLastError(), boost::system::system_category()); + return ret; + } + + do + { + lt::string_view p = fd.cFileName; + if (filter_fun(p)) + ret.push_back(p.to_string()); + + } while (FindNextFileA(handle, &fd)); + FindClose(handle); +#else + + if (!path.empty() && path[path.size()-1] == '/') + path.resize(path.size()-1); + + DIR* handle = opendir(path.c_str()); + if (handle == nullptr) + { + ec.assign(errno, boost::system::system_category()); + return ret; + } + + struct dirent* de; + while ((de = readdir(handle))) + { + lt::string_view p(de->d_name); + if (filter_fun(p)) + ret.push_back(p.to_string()); + } + closedir(handle); +#endif + return ret; +} + +void scan_dir(std::string const& dir_path, lt::session& ses) +{ + using namespace lt; + + error_code ec; + std::vector ents = list_dir(dir_path + , [](lt::string_view p) { return p.size() > 8 && p.substr(p.size() - 8) == ".torrent"; }, ec); + if (ec) + { + std::fprintf(stderr, "failed to list directory: (%s : %d) %s\n" + , ec.category().name(), ec.value(), ec.message().c_str()); + return; + } + + for (auto const& e : ents) + { + std::string const file = path_append(dir_path, e); + + // there's a new file in the monitor directory, load it up + if (add_torrent(ses, file)) + { + if (::remove(file.c_str()) < 0) + { + std::fprintf(stderr, "failed to remove torrent file: \"%s\"\n" + , file.c_str()); + } + } + } +} + +char const* timestamp() +{ + time_t t = std::time(nullptr); +#ifdef TORRENT_WINDOWS + std::tm const* timeinfo = localtime(&t); +#else + std::tm buf; + std::tm const* timeinfo = localtime_r(&t, &buf); +#endif + static char str[200]; + std::strftime(str, 200, "%b %d %X", timeinfo); + return str; +} + +void print_alert(lt::alert const* a, std::string& str) +{ + using namespace lt; + + if (a->category() & alert_category::error) + { + str += esc("31"); + } + else if (a->category() & (alert_category::peer | alert_category::storage)) + { + str += esc("33"); + } + str += "["; + str += timestamp(); + str += "] "; + str += a->message(); + str += esc("0"); + + static auto const first_ts = a->timestamp(); + + if (g_log_file) + std::fprintf(g_log_file, "[%" PRId64 "] %s\n" + , std::int64_t(duration_cast(a->timestamp() - first_ts).count()) + , a->message().c_str()); +} + +int save_file(std::string const& filename, std::vector const& v) +{ + std::fstream f(filename, std::ios_base::trunc | std::ios_base::out | std::ios_base::binary); + f.write(v.data(), int(v.size())); + return !f.fail(); +} + +struct client_state_t +{ + torrent_view& view; + session_view& ses_view; + std::deque events; + std::vector peers; + std::vector file_progress; + std::vector download_queue; + std::vector download_queue_block_info; + std::vector piece_availability; + std::vector trackers; + + void clear() + { + peers.clear(); + file_progress.clear(); + download_queue.clear(); + download_queue_block_info.clear(); + piece_availability.clear(); + trackers.clear(); + } +}; + +// returns true if the alert was handled (and should not be printed to the log) +// returns false if the alert was not handled +bool handle_alert(client_state_t& client_state, lt::alert* a) +{ + using namespace lt; + + if (session_stats_alert* s = alert_cast(a)) + { + client_state.ses_view.update_counters(s->counters(), s->timestamp()); + return !stats_enabled; + } + + if (auto* p = alert_cast(a)) + { + if (client_state.view.get_active_torrent().handle == p->handle) + client_state.peers = std::move(p->peer_info); + return true; + } + + if (auto* p = alert_cast(a)) + { + if (client_state.view.get_active_torrent().handle == p->handle) + client_state.file_progress = std::move(p->files); + return true; + } + + if (auto* p = alert_cast(a)) + { + if (client_state.view.get_active_torrent().handle == p->handle) + { + client_state.download_queue = std::move(p->piece_info); + client_state.download_queue_block_info = std::move(p->block_data); + } + return true; + } + + if (auto* p = alert_cast(a)) + { + if (client_state.view.get_active_torrent().handle == p->handle) + client_state.piece_availability = std::move(p->piece_availability); + return true; + } + + if (auto* p = alert_cast(a)) + { + if (client_state.view.get_active_torrent().handle == p->handle) + client_state.trackers = std::move(p->trackers); + return true; + } + +#ifndef TORRENT_DISABLE_DHT + if (dht_stats_alert* p = alert_cast(a)) + { + dht_active_requests = p->active_requests; + dht_routing_table = p->routing_table; + return true; + } +#endif + +#ifdef TORRENT_SSL_PEERS + if (torrent_need_cert_alert* p = alert_cast(a)) + { + torrent_handle h = p->handle; + std::string base_name = path_append("certificates", to_hex(h.info_hash())); + std::string cert = base_name + ".pem"; + std::string priv = base_name + "_key.pem"; + +#ifdef TORRENT_WINDOWS + struct ::_stat st; + int ret = ::_stat(cert.c_str(), &st); + if (ret < 0 || (st.st_mode & _S_IFREG) == 0) +#else + struct ::stat st; + int ret = ::stat(cert.c_str(), &st); + if (ret < 0 || (st.st_mode & S_IFREG) == 0) +#endif + { + char msg[256]; + std::snprintf(msg, sizeof(msg), "ERROR. could not load certificate %s: %s\n" + , cert.c_str(), std::strerror(errno)); + if (g_log_file) std::fprintf(g_log_file, "[%s] %s\n", timestamp(), msg); + return true; + } + +#ifdef TORRENT_WINDOWS + ret = ::_stat(priv.c_str(), &st); + if (ret < 0 || (st.st_mode & _S_IFREG) == 0) +#else + ret = ::stat(priv.c_str(), &st); + if (ret < 0 || (st.st_mode & S_IFREG) == 0) +#endif + { + char msg[256]; + std::snprintf(msg, sizeof(msg), "ERROR. could not load private key %s: %s\n" + , priv.c_str(), std::strerror(errno)); + if (g_log_file) std::fprintf(g_log_file, "[%s] %s\n", timestamp(), msg); + return true; + } + + char msg[256]; + std::snprintf(msg, sizeof(msg), "loaded certificate %s and key %s\n", cert.c_str(), priv.c_str()); + if (g_log_file) std::fprintf(g_log_file, "[%s] %s\n", timestamp(), msg); + + h.set_ssl_certificate(cert, priv, "certificates/dhparams.pem", "1234"); + h.resume(); + } +#endif + + // don't log every peer we try to connect to + if (alert_cast(a)) return true; + + if (peer_disconnected_alert* pd = alert_cast(a)) + { + // ignore failures to connect and peers not responding with a + // handshake. The peers that we successfully connect to and then + // disconnect is more interesting. + if (pd->op == operation_t::connect + || pd->error == errors::timed_out_no_handshake) + return true; + } + +#ifdef _MSC_VER +// it seems msvc makes the definitions of 'p' escape the if-statement here +#pragma warning(push) +#pragma warning(disable: 4456) +#endif + + if (metadata_received_alert* p = alert_cast(a)) + { + torrent_handle h = p->handle; + h.save_resume_data(torrent_handle::save_info_dict); + ++num_outstanding_resume_data; + } + + if (add_torrent_alert* p = alert_cast(a)) + { + if (p->error) + { + std::fprintf(stderr, "failed to add torrent: %s %s\n" + , p->params.ti ? p->params.ti->name().c_str() : p->params.name.c_str() + , p->error.message().c_str()); + } + else + { + torrent_handle h = p->handle; + + h.save_resume_data(torrent_handle::save_info_dict | torrent_handle::if_metadata_changed); + ++num_outstanding_resume_data; + + // if we have a peer specified, connect to it + if (!peer.empty()) + { + auto port = peer.find_last_of(':'); + if (port != std::string::npos) + { + peer[port++] = '\0'; + char const* ip = peer.data(); + int const peer_port = atoi(peer.data() + port); + error_code ec; + if (peer_port > 0) + h.connect_peer(tcp::endpoint(make_address(ip, ec), std::uint16_t(peer_port))); + } + } + } + } + + if (torrent_finished_alert* p = alert_cast(a)) + { + p->handle.set_max_connections(max_connections_per_torrent / 2); + + // write resume data for the finished torrent + // the alert handler for save_resume_data_alert + // will save it to disk + torrent_handle h = p->handle; + h.save_resume_data(torrent_handle::save_info_dict | torrent_handle::if_download_progress); + ++num_outstanding_resume_data; + if (exit_on_finish) quit = true; + } + + if (save_resume_data_alert* p = alert_cast(a)) + { + --num_outstanding_resume_data; + auto const buf = write_resume_data_buf(p->params); + save_file(resume_file(p->params.info_hashes), buf); + } + + if (save_resume_data_failed_alert* p = alert_cast(a)) + { + --num_outstanding_resume_data; + // don't print the error if it was just that we didn't need to save resume + // data. Returning true means "handled" and not printed to the log + return p->error == lt::errors::resume_data_not_modified; + } + + if (torrent_paused_alert* p = alert_cast(a)) + { + if (!quit) + { + // write resume data for the finished torrent + // the alert handler for save_resume_data_alert + // will save it to disk + torrent_handle h = p->handle; + h.save_resume_data(torrent_handle::save_info_dict); + ++num_outstanding_resume_data; + } + } + + if (state_update_alert* p = alert_cast(a)) + { + lt::torrent_handle const prev = client_state.view.get_active_handle(); + client_state.view.update_torrents(std::move(p->status)); + + // when the active torrent changes, we need to clear the peers, trackers, files, etc. + if (client_state.view.get_active_handle() != prev) + client_state.clear(); + return true; + } + + if (torrent_removed_alert* p = alert_cast(a)) + { + client_state.view.remove_torrent(std::move(p->handle)); + } + return false; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +} + +void pop_alerts(client_state_t& client_state, lt::session& ses) +{ + std::vector alerts; + ses.pop_alerts(&alerts); + for (auto a : alerts) + { + if (::handle_alert(client_state, a)) continue; + + // if we didn't handle the alert, print it to the log + std::string event_string; + print_alert(a, event_string); + client_state.events.push_back(event_string); + if (client_state.events.size() >= 20) client_state.events.pop_front(); + } +} + +void print_compact_piece(lt::partial_piece_info const& pp, std::string& out) +{ + using namespace lt; + + char str[50]; + int const piece = static_cast(pp.piece_index); + int const num_blocks = pp.blocks_in_piece; + + std::snprintf(str, sizeof(str), "%5d:[", piece); + out += str; + out += esc("32"); + lt::bitfield blocks(num_blocks); + for (int j = 0; j < num_blocks; ++j) + if (pp.blocks[j].state == block_info::finished) blocks.set_bit(j); + int height = 0; + out += piece_matrix(blocks, num_blocks / 4, &height); + out += esc("0"); + out += "]"; +} + +void print_piece(lt::partial_piece_info const& pp + , std::vector const& peers + , std::string& out) +{ + using namespace lt; + + char str[1024]; + int const piece = static_cast(pp.piece_index); + int const num_blocks = pp.blocks_in_piece; + + std::snprintf(str, sizeof(str), "%5d:[", piece); + out += str; + string_view last_color; + for (int j = 0; j < num_blocks; ++j) + { + int const index = peer_index(pp.blocks[j].peer(), peers) % 36; + bool const snubbed = index >= 0 ? bool(peers[std::size_t(index)].flags & lt::peer_info::snubbed) : false; + char const* chr = " "; + char const* color = ""; + + if (pp.blocks[j].bytes_progress > 0 + && pp.blocks[j].state == block_info::requested) + { + if (pp.blocks[j].num_peers > 1) color = esc("0;1"); + else color = snubbed ? esc("0;35") : esc("0;33"); + +#ifndef TORRENT_WINDOWS + static char const* const progress[] = { + "\u2581", "\u2582", "\u2583", "\u2584", + "\u2585", "\u2586", "\u2587", "\u2588" + }; + chr = progress[pp.blocks[j].bytes_progress * 8 / pp.blocks[j].block_size]; +#else + static char const* const progress[] = { "\xb0", "\xb1", "\xb2" }; + chr = progress[pp.blocks[j].bytes_progress * 3 / pp.blocks[j].block_size]; +#endif + } + else if (pp.blocks[j].state == block_info::finished) color = esc("32;7"); + else if (pp.blocks[j].state == block_info::writing) color = esc("36;7"); + else if (pp.blocks[j].state == block_info::requested) + { + color = snubbed ? esc("0;35") : esc("0"); + chr = "="; + } + else { color = esc("0"); chr = " "; } + + if (last_color != color) + { + out += color; + last_color = color; + } + out += chr; + } + out += esc("0"); + out += "]"; +} + +bool is_resume_file(std::string const& s) +{ + static std::string const hex_digit = "0123456789abcdef"; + if (s.size() != 40 + 7) return false; + if (s.substr(40) != ".resume") return false; + for (char const c : s.substr(0, 40)) + { + if (hex_digit.find(c) == std::string::npos) return false; + } + return true; +} + +void print_usage() +{ + std::fprintf(stderr, R"(usage: client_test [OPTIONS] [TORRENT|MAGNETURL] +OPTIONS: + +CLIENT OPTIONS + -h print this message + -f logs all events to the given file + -s sets the save path for downloads. This also determines + the resume data save directory. Torrents from the resume + directory are automatically added to the session on + startup. + -m sets the .torrent monitor directory. torrent files + dropped in the directory are added the session and the + resume data directory, and removed from the monitor dir. + -t sets the scan interval of the monitor dir + -F sets the UI refresh rate. This is the number of + milliseconds between screen refreshes. + -k enable high performance settings. This overwrites any other + previous command line options, so be sure to specify this first + -G Add torrents in seed-mode (i.e. assume all pieces + are present and check hashes on-demand) + -e exit client after the specified number of iterations + through the main loop + -O print session stats counters to the log + -1 exit on first torrent completing (useful for benchmarks))" +#ifdef TORRENT_UTP_LOG_ENABLE +R"( + -q Enable uTP transport-level verbose logging +)" +#endif +R"( +LIBTORRENT SETTINGS + --= + set the libtorrent setting to + --list-settings print all libtorrent settings and exit + +BITTORRENT OPTIONS + -T sets the max number of connections per torrent + -U sets per-torrent upload rate + -D sets per-torrent download rate + -Q enables share mode. Share mode attempts to maximize + share ratio rather than downloading + -r connect to specified peer + +NETWORK OPTIONS + -x loads an emule IP-filter file + -Y Rate limit local peers +)" +#if TORRENT_USE_I2P +R"( -i the hostname to an I2P SAM bridge to use +)" +#endif +R"( +DISK OPTIONS + -a sets the allocation mode. [sparse|allocate] + -0 disable disk I/O, read garbage and don't flush to disk + +TORRENT is a path to a .torrent file +MAGNETURL is a magnet link + +alert mask flags: + error peer port_mapping storage tracker connect status ip_block + performance_warning dht session_log torrent_log peer_log incoming_request + dht_log dht_operation port_mapping_log picker_log file_progress piece_progress + upload block_progress all + +examples: + --alert_mask=error,port_mapping,tracker,connect,session_log + --alert_mask=error,session_log,torrent_log,peer_log + --alert_mask=error,dht,dht_log,dht_operation + --alert_mask=all +)") ; +} + +} // anonymous namespace + +int main(int argc, char* argv[]) +{ +#ifndef _WIN32 + // sets the terminal to single-character mode + // and resets when destructed + set_keypress s_; +#endif + + if (argc == 1) + { + print_usage(); + return 0; + } + + using lt::settings_pack; + using lt::session_handle; + + torrent_view view; + session_view ses_view; + + lt::session_params params; + +#ifndef TORRENT_DISABLE_DHT + + std::vector in; + if (load_file(".ses_state", in)) + params = read_session_params(in, session_handle::save_dht_state); +#endif + + auto& settings = params.settings; + + settings.set_str(settings_pack::user_agent, "client_test/" LIBTORRENT_VERSION); + settings.set_int(settings_pack::alert_mask + , lt::alert_category::error + | lt::alert_category::peer + | lt::alert_category::port_mapping + | lt::alert_category::storage + | lt::alert_category::tracker + | lt::alert_category::connect + | lt::alert_category::status + | lt::alert_category::ip_block + | lt::alert_category::performance_warning + | lt::alert_category::dht + | lt::alert_category::incoming_request + | lt::alert_category::dht_operation + | lt::alert_category::port_mapping_log + | lt::alert_category::file_progress); + + lt::time_duration refresh_delay = lt::milliseconds(500); + bool rate_limit_locals = false; + + client_state_t client_state{ + view, ses_view, {}, {}, {}, {}, {}, {}, {} + }; + int loop_limit = -1; + + lt::time_point next_dir_scan = lt::clock_type::now(); + + // load the torrents given on the commandline + std::vector torrents; + lt::ip_filter loaded_ip_filter; + + for (int i = 1; i < argc; ++i) + { + if (argv[i][0] != '-') + { + torrents.push_back(argv[i]); + continue; + } + + if (argv[i] == "--list-settings"_sv) + { + // print all libtorrent settings and exit + print_settings(settings_pack::string_type_base + , settings_pack::num_string_settings, "string"); + print_settings(settings_pack::bool_type_base + , settings_pack::num_bool_settings, "bool"); + print_settings(settings_pack::int_type_base + , settings_pack::num_int_settings, "int"); + return 0; + } + + // maybe this is an assignment of a libtorrent setting + if (argv[i][1] == '-' && strchr(argv[i], '=') != nullptr) + { + char const* equal = strchr(argv[i], '='); + char const* start = argv[i]+2; + // +2 is to skip the -- + std::string const key(start, std::size_t(equal - start)); + char const* value = equal + 1; + + assign_setting(settings, key, value); + continue; + } + + // command line switches that don't take an argument + switch (argv[i][1]) + { + case 'k': settings = lt::high_performance_seed(); continue; + case 'G': seed_mode = true; continue; + case 'O': stats_enabled = true; continue; + case '1': exit_on_finish = true; continue; +#ifdef TORRENT_UTP_LOG_ENABLE + case 'q': + lt::set_utp_stream_logging(true); + continue; +#endif + case 'Q': share_mode = true; continue; + case 'Y': rate_limit_locals = true; continue; + case '0': params.disk_io_constructor = lt::disabled_disk_io_constructor; continue; + case 'h': print_usage(); return 0; + } + + // if there's a flag but no argument following, ignore it + if (argc == i + 1) + { + std::fprintf(stderr, "invalid command line argument or missing parameter: %s\n", argv[i]); + return 1; + } + char const* arg = argv[i+1]; + if (arg == nullptr) arg = ""; + + switch (argv[i][1]) + { + case 'f': g_log_file = std::fopen(arg, "w+"); break; + case 's': save_path = make_absolute_path(arg); break; + case 'U': torrent_upload_limit = atoi(arg) * 1000; break; + case 'D': torrent_download_limit = atoi(arg) * 1000; break; + case 'm': monitor_dir = make_absolute_path(arg); break; + case 't': poll_interval = atoi(arg); break; + case 'F': refresh_delay = lt::milliseconds(atoi(arg)); break; + case 'a': allocation_mode = (arg == std::string("sparse")) + ? lt::storage_mode_sparse + : lt::storage_mode_allocate; + break; + case 'x': + { + std::fstream filter(arg, std::ios_base::in); + if (!filter.fail()) + { + std::regex regex(R"(^\s*([0-9\.]+)\s*-\s*([0-9\.]+)\s+([0-9]+)$)"); + + std::string line; + while (std::getline(filter, line)) + { + std::smatch m; + if (std::regex_match(line, m, regex)) + { + address_v4 start = make_address_v4(m[1]); + address_v4 last = make_address_v4(m[2]); + loaded_ip_filter.add_rule(start, last, stoi(m[3]) <= 127 ? lt::ip_filter::blocked : 0); + } + } + } + } + break; + case 'T': max_connections_per_torrent = atoi(arg); break; + case 'r': peer = arg; break; + case 'e': + { + loop_limit = atoi(arg); + break; + } + } + ++i; // skip the argument + } + + // create directory for resume files +#ifdef TORRENT_WINDOWS + int mkdir_ret = _mkdir(path_append(save_path, ".resume").c_str()); +#else + int mkdir_ret = mkdir(path_append(save_path, ".resume").c_str(), 0777); +#endif + if (mkdir_ret < 0 && errno != EEXIST) + { + std::fprintf(stderr, "failed to create resume file directory: (%d) %s\n" + , errno, strerror(errno)); + } + + lt::session ses(std::move(params)); + + if (rate_limit_locals) + { + lt::ip_filter pcf; + pcf.add_rule(make_address_v4("0.0.0.0") + , make_address_v4("255.255.255.255") + , 1 << static_cast(lt::session::global_peer_class_id)); + pcf.add_rule(make_address_v6("::") + , make_address_v6("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), 1); + ses.set_peer_class_filter(pcf); + } + + ses.set_ip_filter(loaded_ip_filter); + + for (auto const& i : torrents) + { + if (i.substr(0, 7) == "magnet:") add_magnet(ses, i); + else add_torrent(ses, i.to_string()); + } + + std::thread resume_data_loader([&ses] + { + // load resume files + lt::error_code ec; + std::string const resume_dir = path_append(save_path, ".resume"); + std::vector ents = list_dir(resume_dir + , [](lt::string_view p) { return p.size() > 7 && p.substr(p.size() - 7) == ".resume"; }, ec); + if (ec) + { + std::fprintf(stderr, "failed to list resume directory \"%s\": (%s : %d) %s\n" + , resume_dir.c_str(), ec.category().name(), ec.value(), ec.message().c_str()); + } + else + { + for (auto const& e : ents) + { + // only load resume files of the form .resume + if (!is_resume_file(e)) continue; + std::string const file = path_append(resume_dir, e); + + std::vector resume_data; + if (!load_file(file, resume_data)) + { + std::printf(" failed to load resume file \"%s\": %s\n" + , file.c_str(), ec.message().c_str()); + continue; + } + add_torrent_params p = lt::read_resume_data(resume_data, ec); + if (ec) + { + std::printf(" failed to parse resume data \"%s\": %s\n" + , file.c_str(), ec.message().c_str()); + continue; + } + + ses.async_add_torrent(std::move(p)); + } + } + }); + + // main loop + +#ifndef _WIN32 + signal(SIGTERM, signal_handler); + signal(SIGINT, signal_handler); +#endif + + while (!quit && loop_limit != 0) + { + if (loop_limit > 0) --loop_limit; + + ses.post_torrent_updates(); + ses.post_session_stats(); + ses.post_dht_stats(); + + int terminal_width = 80; + int terminal_height = 50; + std::tie(terminal_width, terminal_height) = terminal_size(); + + // the ratio of torrent-list and details below depend on the number of + // torrents we have in the session + int const height = std::min(terminal_height / 2 + , std::max(5, view.num_visible_torrents() + 2)); + view.set_size(terminal_width, height); + ses_view.set_pos(height); + ses_view.set_width(terminal_width); + + int c = 0; + if (sleep_and_input(&c, refresh_delay)) + { + +#ifdef _WIN32 + constexpr int escape_seq = 224; + constexpr int left_arrow = 75; + constexpr int right_arrow = 77; + constexpr int up_arrow = 72; + constexpr int down_arrow = 80; +#else + constexpr int escape_seq = 27; + constexpr int left_arrow = 68; + constexpr int right_arrow = 67; + constexpr int up_arrow = 65; + constexpr int down_arrow = 66; +#endif + + torrent_handle h = view.get_active_handle(); + + if (c == EOF) + { + quit = true; + break; + } + do + { + if (c == escape_seq) + { + // escape code, read another character +#ifdef _WIN32 + int c2 = _getch(); +#else + int c2 = getc(stdin); + if (c2 == EOF) + { + quit = true; + break; + } + if (c2 != '[') continue; + c2 = getc(stdin); +#endif + if (c2 == EOF) + { + quit = true; + break; + } + if (c2 == left_arrow) + { + int const filter = view.filter(); + if (filter > 0) + { + client_state.clear(); + view.set_filter(filter - 1); + h = view.get_active_handle(); + } + } + else if (c2 == right_arrow) + { + int const filter = view.filter(); + if (filter < torrent_view::torrents_max - 1) + { + client_state.clear(); + view.set_filter(filter + 1); + h = view.get_active_handle(); + } + } + else if (c2 == up_arrow) + { + client_state.clear(); + view.arrow_up(); + h = view.get_active_handle(); + } + else if (c2 == down_arrow) + { + client_state.clear(); + view.arrow_down(); + h = view.get_active_handle(); + } + } + + if (c == '<') + { + int const order = view.sort_order(); + if (order > 0) + view.set_sort_order(order - 1); + } + + if (c == '>') + { + int const order = view.sort_order(); + if (order < 2) + view.set_sort_order(order + 1); + } + + if (c == '[' && h.is_valid()) + { + h.queue_position_up(); + } + + if (c == ']' && h.is_valid()) + { + h.queue_position_down(); + } + + // add magnet link + if (c == 'm') + { + char url[4096]; + url[0] = '\0'; + puts("Enter magnet link:\n"); + +#ifndef _WIN32 + // enable terminal echo temporarily + set_keypress echo_(set_keypress::echo | set_keypress::canonical); +#endif + if (std::scanf("%4095s", url) == 1) add_magnet(ses, url); + else std::printf("failed to read magnet link\n"); + } + + if (c == 'q') + { + quit = true; + break; + } + + if (c == 'W' && h.is_valid()) + { + std::set seeds = h.url_seeds(); + for (auto const& s : seeds) + h.remove_url_seed(s); + + seeds = h.http_seeds(); + for (auto const& s : seeds) + h.remove_http_seed(s); + } + + if (c == 'D' && h.is_valid()) + { + torrent_status const& st = view.get_active_torrent(); + std::printf("\n\nARE YOU SURE YOU WANT TO DELETE THE FILES FOR '%s'. THIS OPERATION CANNOT BE UNDONE. (y/N)" + , st.name.c_str()); +#ifndef _WIN32 + // enable terminal echo temporarily + set_keypress echo_(set_keypress::echo | set_keypress::canonical); +#endif + char response = 'n'; + int scan_ret = std::scanf("%c", &response); + if (scan_ret == 1 && response == 'y') + { + // also delete the resume file + std::string const rpath = resume_file(st.info_hashes); + if (::remove(rpath.c_str()) < 0) + std::printf("failed to delete resume file (\"%s\")\n" + , rpath.c_str()); + + if (st.handle.is_valid()) + { + ses.remove_torrent(st.handle, lt::session::delete_files); + } + else + { + std::printf("failed to delete torrent, invalid handle: %s\n" + , st.name.c_str()); + } + client_state.clear(); + } + } + + if (c == 'j' && h.is_valid()) + { + h.force_recheck(); + } + + if (c == 'r' && h.is_valid()) + { + h.force_reannounce(); + } + + if (c == 's' && h.is_valid()) + { + torrent_status const& ts = view.get_active_torrent(); + h.set_flags(~ts.flags, lt::torrent_flags::sequential_download); + } + + if (c == 'R') + { + // save resume data for all torrents + std::vector const torr = ses.get_torrent_status( + [](torrent_status const& st) + { return st.need_save_resume; }, {}); + for (torrent_status const& st : torr) + { + st.handle.save_resume_data(torrent_handle::save_info_dict); + ++num_outstanding_resume_data; + } + } + + if (c == 'o' && h.is_valid()) + { + torrent_status const& ts = view.get_active_torrent(); + int num_pieces = ts.num_pieces; + if (num_pieces > 300) num_pieces = 300; + for (piece_index_t i(0); i < piece_index_t(num_pieces); ++i) + { + h.set_piece_deadline(i, (static_cast(i)+5) * 1000 + , torrent_handle::alert_when_available); + } + } + + if (c == 'v' && h.is_valid()) + { + h.scrape_tracker(); + } + + if (c == 'p' && h.is_valid()) + { + torrent_status const& ts = view.get_active_torrent(); + if ((ts.flags & (lt::torrent_flags::auto_managed + | lt::torrent_flags::paused)) == lt::torrent_flags::paused) + { + h.set_flags(lt::torrent_flags::auto_managed); + } + else + { + h.unset_flags(lt::torrent_flags::auto_managed); + h.pause(torrent_handle::graceful_pause); + } + } + + // toggle force-start + if (c == 'k' && h.is_valid()) + { + torrent_status const& ts = view.get_active_torrent(); + h.set_flags( + ~(ts.flags & lt::torrent_flags::auto_managed), + lt::torrent_flags::auto_managed); + if ((ts.flags & lt::torrent_flags::auto_managed) + && (ts.flags & lt::torrent_flags::paused)) + { + h.resume(); + } + } + + if (c == 'c' && h.is_valid()) + { + h.clear_error(); + } + + // toggle displays + if (c == 't') print_trackers = !print_trackers; + if (c == 'i') print_peers = !print_peers; + if (c == 'I') print_peers_legend = !print_peers_legend; + if (c == 'l') print_log = !print_log; + if (c == 'd') print_downloads = !print_downloads; + if (c == 'y') print_matrix = !print_matrix; + if (c == 'f') print_file_progress = !print_file_progress; + if (c == 'a') print_piece_availability = !print_piece_availability; + if (c == 'P') show_pad_files = !show_pad_files; + if (c == 'g') show_dht_status = !show_dht_status; + if (c == 'x') print_disk_stats = !print_disk_stats; + // toggle columns + if (c == '1') print_ip = !print_ip; + if (c == '2') print_connecting_peers = !print_connecting_peers; + if (c == '3') print_timers = !print_timers; + if (c == '4') print_block = !print_block; + if (c == '5') print_peaks = !print_peaks; + if (c == '6') print_fails = !print_fails; + if (c == '7') print_send_bufs = !print_send_bufs; + if (c == '8') print_local_ip = !print_local_ip; + if (c == 'h') + { + clear_screen(); + set_cursor_pos(0,0); + print( +R"(HELP SCREEN (press any key to dismiss) + +CLIENT OPTIONS + +[q] quit client [m] add magnet link + +TORRENT ACTIONS +[p] pause/resume selected torrent [W] remove all web seeds +[s] toggle sequential download [j] force recheck +[space] toggle session pause [c] clear error +[v] scrape [D] delete torrent and data +[r] force reannounce [R] save resume data for all torrents +[o] set piece deadlines (sequential dl) [P] toggle auto-managed +[k] toggle force-started [W] remove all web seeds + [ move queue position closer to beginning + ] move queue position closer to end + +DISPLAY OPTIONS +left/right arrow keys: select torrent filter +up/down arrow keys: select torrent +[i] toggle show peers [d] toggle show downloading pieces +[P] show pad files (in file list) [f] toggle show files +[g] show DHT [x] toggle disk cache stats +[t] show trackers [l] toggle show log +[y] toggle show piece matrix [I] toggle show peer flag legend +[a] toggle show piece availability + +COLUMN OPTIONS +[1] toggle IP column [2] toggle show peer connection attempts +[3] toggle timers column [4] toggle block progress column +[5] toggle print peak rates [6] toggle failures column +[7] toggle send buffers column [8] toggle local IP column +)"); + int tmp; + while (sleep_and_input(&tmp, lt::milliseconds(500)) == false); + } + + } while (sleep_and_input(&c, lt::milliseconds(0))); + if (c == 'q') + { + quit = true; + break; + } + } + + pop_alerts(client_state, ses); + + std::string out; + + char str[500]; + + int pos = view.height() + ses_view.height(); + set_cursor_pos(0, pos); + + torrent_handle h = view.get_active_handle(); + +#ifndef TORRENT_DISABLE_DHT + if (show_dht_status) + { + // TODO: 3 expose these counters as performance counters +/* + std::snprintf(str, sizeof(str), "DHT nodes: %d DHT cached nodes: %d " + "total DHT size: %" PRId64 " total observers: %d\n" + , sess_stat.dht_nodes, sess_stat.dht_node_cache, sess_stat.dht_global_nodes + , sess_stat.dht_total_allocations); + out += str; +*/ + + int bucket = 0; + for (lt::dht_routing_bucket const& n : dht_routing_table) + { + char const* progress_bar = + "################################" + "################################" + "################################" + "################################"; + char const* short_progress_bar = "--------"; + std::snprintf(str, sizeof(str) + , "%3d [%3d, %d] %s%s\x1b[K\n" + , bucket, n.num_nodes, n.num_replacements + , progress_bar + (128 - n.num_nodes) + , short_progress_bar + (8 - std::min(8, n.num_replacements))); + out += str; + pos += 1; + ++bucket; + } + + for (lt::dht_lookup const& l : dht_active_requests) + { + std::snprintf(str, sizeof(str) + , " %10s target: %s " + "[limit: %2d] " + "in-flight: %-2d " + "left: %-3d " + "1st-timeout: %-2d " + "timeouts: %-2d " + "responses: %-2d " + "last_sent: %-2d " + "\x1b[K\n" + , l.type + , to_hex(l.target).c_str() + , l.branch_factor + , l.outstanding_requests + , l.nodes_left + , l.first_timeout + , l.timeouts + , l.responses + , l.last_sent); + out += str; + pos += 1; + } + } +#endif + lt::time_point const now = lt::clock_type::now(); + if (h.is_valid()) + { + torrent_status const& s = view.get_active_torrent(); + + if (!print_matrix) { + print((piece_bar(s.pieces, terminal_width - 2) + "\x1b[K\n").c_str()); + pos += 1; + } + + if ((print_downloads && s.state != torrent_status::seeding) + || print_peers) + h.post_peer_info(); + + auto& peers = client_state.peers; + if (print_peers && !peers.empty()) + { + using lt::peer_info; + // sort connecting towards the bottom of the list, and by peer_id + // otherwise, to keep the list as stable as possible + std::sort(peers.begin(), peers.end() + , [](peer_info const& lhs, peer_info const& rhs) + { + { + bool const l = bool(lhs.flags & peer_info::connecting); + bool const r = bool(rhs.flags & peer_info::connecting); + if (l != r) return l < r; + } + + { + bool const l = bool(lhs.flags & peer_info::handshake); + bool const r = bool(rhs.flags & peer_info::handshake); + if (l != r) return l < r; + } + + return lhs.pid < rhs.pid; + }); + pos += print_peer_info(out, peers, terminal_height - pos - 2); + if (print_peers_legend) + { + pos += print_peer_legend(out, terminal_height - pos - 2); + } + } + + if (print_trackers) + { + snprintf(str, sizeof(str), "next_announce: %4" PRId64 " | current tracker: %s\x1b[K\n" + , std::int64_t(duration_cast(s.next_announce).count()) + , s.current_tracker.c_str()); + out += str; + pos += 1; + h.post_trackers(); + for (lt::announce_entry const& ae : client_state.trackers) + { + std::snprintf(str, sizeof(str), "%2d %-55s %s\x1b[K\n" + , ae.tier, ae.url.c_str(), ae.verified?"OK ":"- "); + out += str; + pos += 1; + int idx = 0; + for (auto const& ep : ae.endpoints) + { + ++idx; + if (pos + 1 >= terminal_height) break; + if (!ep.enabled) continue; + for (lt::protocol_version const v : {lt::protocol_version::V1, lt::protocol_version::V2}) + { + if (!s.info_hashes.has(v)) continue; + auto const& av = ep.info_hashes[v]; + + std::snprintf(str, sizeof(str), " [%2d] %s fails: %-3d (%-3d) %s %5d \"%s\" %s\x1b[K\n" + , idx + , v == lt::protocol_version::V1 ? "v1" : "v2" + , av.fails, ae.fail_limit + , to_string(int(total_seconds(av.next_announce - now)), 8).c_str() + , av.min_announce > now ? int(total_seconds(av.min_announce - now)) : 0 + , av.last_error ? av.last_error.message().c_str() : "" + , av.message.c_str()); + out += str; + pos += 1; + // we only need to show this error once, not for every + // endpoint + if (av.last_error == boost::asio::error::host_not_found) + goto done; + } + } +done: + + if (pos + 1 >= terminal_height) break; + } + } + + if (print_matrix) + { + int height_out = 0; + print(piece_matrix(s.pieces, terminal_width, &height_out).c_str()); + print("\n"); + pos += height_out; + } + + if (print_piece_availability) + { + h.post_piece_availability(); + if (!client_state.piece_availability.empty()) + print(avail_bar(client_state.piece_availability, terminal_width, pos).c_str()); + } + + if (print_downloads) + { + h.post_download_queue(); + + int p = 0; // this is horizontal position + for (lt::partial_piece_info const& i : client_state.download_queue) + { + if (pos + 3 >= terminal_height) break; + + int const num_blocks = i.blocks_in_piece; + p += num_blocks + 8; + if (8 + num_blocks > terminal_width) + { + print_compact_piece(i, out); + } + else + { + print_piece(i, peers, out); + } + if (p + num_blocks + 8 > terminal_width) + { + out += "\x1b[K\n"; + pos += 1; + p = 0; + } + } + if (p != 0) + { + out += "\x1b[K\n"; + pos += 1; + } + + std::snprintf(str, sizeof(str), "%s %s downloading | %s %s writing | %s %s flushed | %s %s snubbed | = requested\x1b[K\n" + , esc("33;7"), esc("0") // downloading + , esc("36;7"), esc("0") // writing + , esc("32;7"), esc("0") // flushed + , esc("35;7"), esc("0") // snubbed + ); + out += str; + pos += 1; + } + + if (print_file_progress && s.has_metadata && h.is_valid()) + { + h.post_file_progress({}); + std::vector file_status = h.file_status(); + std::vector file_prio = h.get_file_priorities(); + auto f = file_status.begin(); + std::shared_ptr ti = s.torrent_file.lock(); + + // TODO: ti may be nullptr here, we should check + + auto const& file_progress = client_state.file_progress; + int p = 0; // this is horizontal position + for (file_index_t const i : ti->files().file_range()) + { + auto const idx = std::size_t(static_cast(i)); + if (pos + 1 >= terminal_height) break; + + bool const pad_file = ti->files().pad_file_at(i); + if (pad_file && !show_pad_files) continue; + + if (idx >= file_progress.size()) break; + + int const progress = ti->files().file_size(i) > 0 + ? int(file_progress[idx] * 1000 / ti->files().file_size(i)) : 1000; + TORRENT_ASSERT(file_progress[idx] <= ti->files().file_size(i)); + + bool const complete = file_progress[idx] == ti->files().file_size(i); + + std::string title = ti->files().file_name(i).to_string(); + if (!complete) + { + std::snprintf(str, sizeof(str), " (%.1f%%)", progress / 10.0); + title += str; + } + + if (f != file_status.end() && f->file_index == i) + { + title += " [ "; + if ((f->open_mode & lt::file_open_mode::rw_mask) == lt::file_open_mode::read_write) title += "read/write "; + else if ((f->open_mode & lt::file_open_mode::rw_mask) == lt::file_open_mode::read_only) title += "read "; + else if ((f->open_mode & lt::file_open_mode::rw_mask) == lt::file_open_mode::write_only) title += "write "; + if (f->open_mode & lt::file_open_mode::random_access) title += "random_access "; + if (f->open_mode & lt::file_open_mode::sparse) title += "sparse "; + if (f->open_mode & lt::file_open_mode::mmapped) title += "mmapped "; + title += "]"; + ++f; + } + + const int file_progress_width = pad_file ? 10 : 65; + + // do we need to line-break? + if (p + file_progress_width + 13 > terminal_width) + { + out += "\x1b[K\n"; + pos += 1; + p = 0; + } + + std::snprintf(str, sizeof(str), "%s %7s p: %d ", + progress_bar(progress, file_progress_width + , pad_file ? col_blue + : complete ? col_green : col_yellow + , '-', '#', title.c_str()).c_str() + , add_suffix(file_progress[idx]).c_str() + , static_cast(file_prio[idx])); + + p += file_progress_width + 13; + out += str; + } + + if (p != 0) + { + out += "\x1b[K\n"; + pos += 1; + } + } + } + + if (print_log) + { + for (auto const& e : client_state.events) + { + if (pos + 1 >= terminal_height) break; + out += e; + out += "\x1b[K\n"; + pos += 1; + } + } + + // clear rest of screen + out += "\x1b[J"; + print(out.c_str()); + + std::fflush(stdout); + + if (!monitor_dir.empty() && next_dir_scan < now) + { + scan_dir(monitor_dir, ses); + next_dir_scan = now + seconds(poll_interval); + } + } + + resume_data_loader.join(); + + quit = true; + ses.pause(); + std::printf("saving resume data\n"); + + // get all the torrent handles that we need to save resume data for + std::vector const temp = ses.get_torrent_status( + [](torrent_status const& st) + { + return st.handle.is_valid() && st.has_metadata && st.need_save_resume; + }, {}); + + int idx = 0; + for (auto const& st : temp) + { + // save_resume_data will generate an alert when it's done + st.handle.save_resume_data(torrent_handle::save_info_dict); + ++num_outstanding_resume_data; + ++idx; + if ((idx % 32) == 0) + { + std::printf("\r%d ", num_outstanding_resume_data); + pop_alerts(client_state, ses); + } + } + std::printf("\nwaiting for resume data [%d]\n", num_outstanding_resume_data); + + while (num_outstanding_resume_data > 0) + { + alert const* a = ses.wait_for_alert(seconds(10)); + if (a == nullptr) continue; + pop_alerts(client_state, ses); + } + + if (g_log_file) std::fclose(g_log_file); + + // we're just saving the DHT state +#ifndef TORRENT_DISABLE_DHT + std::printf("\nsaving session state\n"); + { + std::vector out = write_session_params_buf(ses.session_state(lt::session::save_dht_state)); + save_file(".ses_state", out); + } +#endif + + std::printf("closing session\n"); + + return 0; +} + diff --git a/examples/cmake/FindLibtorrentRasterbar.cmake b/examples/cmake/FindLibtorrentRasterbar.cmake new file mode 100644 index 0000000..ee61821 --- /dev/null +++ b/examples/cmake/FindLibtorrentRasterbar.cmake @@ -0,0 +1,190 @@ +# - Try to find libtorrent-rasterbar +# +# This module tries to locate libtorrent-rasterbar Config.cmake files and fallbacks to pkg-config. +# If that does not work, you can pre-set LibtorrentRasterbar_CUSTOM_DEFINITIONS +# for definitions unrelated to Boost's separate compilation (which are already +# decided by the LibtorrentRasterbar_USE_STATIC_LIBS variable). +# +# Once done this will define +# LibtorrentRasterbar_FOUND - System has libtorrent-rasterbar +# LibtorrentRasterbar_OPENSSL_ENABLED - libtorrent-rasterbar uses and links against OpenSSL +# LibtorrentRasterbar::torrent-rasterbar imported target will be created + +function(_try_config_mode) + set(_exactKeyword "") + if (${${CMAKE_FIND_PACKAGE_NAME}_FIND_VERSION_EXACT}) + set(_exactKeyword "EXACT") + endif() + + find_package(LibtorrentRasterbar ${${CMAKE_FIND_PACKAGE_NAME}_FIND_VERSION} ${_exactKeyword} CONFIG) + + if (LibtorrentRasterbar_FOUND) + if (NOT ${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY) + message(STATUS "${CMAKE_FIND_PACKAGE_NAME} package found in ${LibtorrentRasterbar_DIR}") + message(STATUS "${CMAKE_FIND_PACKAGE_NAME} version: ${LibtorrentRasterbar_VERSION}") + endif() + # Extract target properties into this module variables + get_target_property(_iface_link_libs LibtorrentRasterbar::torrent-rasterbar INTERFACE_LINK_LIBRARIES) + list(FIND _iface_link_libs "OpenSSL::SSL" _openssl_lib_index) + if (_openssl_lib_index GREATER -1) + set(LibtorrentRasterbar_OPENSSL_ENABLED TRUE PARENT_SCOPE) + else() + set(LibtorrentRasterbar_OPENSSL_ENABLED FALSE PARENT_SCOPE) + endif() + endif() +endfunction() + +function(_try_pkgconfig_mode) + if (${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY) + set(_quietKeyword "QUIET") + endif() + find_package(Threads ${_quietKeyword} REQUIRED) + find_package(PkgConfig ${_quietKeyword}) + if(PKG_CONFIG_FOUND) + set(_moduleSpec "libtorrent-rasterbar") + if (${CMAKE_FIND_PACKAGE_NAME}_FIND_VERSION) + if (${CMAKE_FIND_PACKAGE_NAME}_FIND_VERSION_EXACT) + set(_moduleSpec "${_moduleSpec}=${${CMAKE_FIND_PACKAGE_NAME}_FIND_VERSION}") + else() + set(_moduleSpec "${_moduleSpec}>=${${CMAKE_FIND_PACKAGE_NAME}_FIND_VERSION}") + endif() + endif() + + pkg_check_modules(PC_LIBTORRENT_RASTERBAR ${_quietKeyword} IMPORTED_TARGET GLOBAL ${_moduleSpec}) + if (PC_LIBTORRENT_RASTERBAR_FOUND) + add_library(LibtorrentRasterbar::torrent-rasterbar ALIAS PkgConfig::PC_LIBTORRENT_RASTERBAR) + list(FIND PC_LIBTORRENT_RASTERBAR_LIBRARIES "ssl" _openssl_lib_index) + if (_openssl_lib_index GREATER -1) + set(LibtorrentRasterbar_OPENSSL_ENABLED TRUE PARENT_SCOPE) + else() + set(LibtorrentRasterbar_OPENSSL_ENABLED FALSE PARENT_SCOPE) + endif() + set(LibtorrentRasterbar_FOUND TRUE PARENT_SCOPE) + else() + set(LibtorrentRasterbar_FOUND FALSE PARENT_SCOPE) + endif() + endif() +endfunction() + +function(_try_generic_mode) + if(LibtorrentRasterbar_USE_STATIC_LIBS) + set(LibtorrentRasterbar_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) + set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX}) + endif() + if(LibtorrentRasterbar_CUSTOM_DEFINITIONS) + set(LibtorrentRasterbar_DEFINITIONS ${LibtorrentRasterbar_CUSTOM_DEFINITIONS}) + else() + # Without pkg-config, we can't possibly figure out the correct build flags. + # libtorrent is very picky about those. Let's take a set of defaults and + # hope that they apply. If not, you the user are on your own. + set(LibtorrentRasterbar_DEFINITIONS + -DTORRENT_USE_OPENSSL + -DTORRENT_DISABLE_GEO_IP + -DBOOST_ASIO_ENABLE_CANCELIO + -D_FILE_OFFSET_BITS=64) + endif() + + if(NOT LibtorrentRasterbar_USE_STATIC_LIBS) + list(APPEND LibtorrentRasterbar_DEFINITIONS + -DTORRENT_LINKING_SHARED + -DBOOST_SYSTEM_DYN_LINK) + endif() + + find_path(LibtorrentRasterbar_INCLUDE_DIR libtorrent + HINTS ${PC_LIBTORRENT_RASTERBAR_INCLUDEDIR} ${PC_LIBTORRENT_RASTERBAR_INCLUDE_DIRS} + PATH_SUFFIXES libtorrent-rasterbar) + + find_library(LibtorrentRasterbar_LIBRARY NAMES torrent-rasterbar + HINTS ${PC_LIBTORRENT_RASTERBAR_LIBDIR} ${PC_LIBTORRENT_RASTERBAR_LIBRARY_DIRS}) + + if(LibtorrentRasterbar_USE_STATIC_LIBS) + set(CMAKE_FIND_LIBRARY_SUFFIXES ${LibtorrentRasterbar_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES}) + endif() + + if (NOT ${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY) + message(STATUS "${CMAKE_FIND_PACKAGE_NAME} definitions: ${LibtorrentRasterbar_DEFINITIONS}") + if (LibtorrentRasterbar_INCLUDE_DIR) + message(STATUS "${CMAKE_FIND_PACKAGE_NAME} include dir: ${LibtorrentRasterbar_INCLUDE_DIR}") + endif() + if (LibtorrentRasterbar_LIBRARY) + message(STATUS "${CMAKE_FIND_PACKAGE_NAME} library: ${LibtorrentRasterbar_LIBRARY}") + endif() + endif() + + mark_as_advanced(LibtorrentRasterbar_LIBRARY LibtorrentRasterbar_INCLUDE_DIR) + + if(NOT LibtorrentRasterbar_LIBRARY OR NOT LibtorrentRasterbar_INCLUDE_DIR) + set(LibtorrentRasterbar_FOUND FALSE PARENT_SCOPE) + return() + endif() + + set(LibtorrentRasterbar_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) + + find_package(Boost QUIET REQUIRED) + if (Boost_MAJOR_VERSION LESS_EQUAL 1 AND Boost_MINOR_VERSION LESS 69) + if (NOT Boost_SYSTEM_FOUND) + find_package(Boost QUIET REQUIRED COMPONENTS system) + endif() + list(APPEND LibtorrentRasterbar_LIBRARIES Boost::system) + endif() + + list(FIND LibtorrentRasterbar_DEFINITIONS -DTORRENT_USE_OPENSSL _ENCRYPTION_INDEX) + if(_ENCRYPTION_INDEX GREATER -1) + find_package(OpenSSL QUIET REQUIRED) + list(APPEND LibtorrentRasterbar_LIBRARIES OpenSSL::SSL) + if (LibtorrentRasterbar_USE_STATIC_LIBS) + list(APPEND LibtorrentRasterbar_LIBRARIES OpenSSL::Crypto) + endif() + set(LibtorrentRasterbar_OPENSSL_ENABLED ON PARENT_SCOPE) + else() + set(LibtorrentRasterbar_OPENSSL_ENABLED OFF PARENT_SCOPE) + endif() + + set(LibtorrentRasterbar_FOUND TRUE PARENT_SCOPE) + + if (NOT TARGET LibtorrentRasterbar::torrent-rasterbar) + if (LibtorrentRasterbar_USE_STATIC_LIBS) + set(_libType "STATIC") + else() + set(_libType "SHARED") + endif() + + add_library(LibtorrentRasterbar::torrent-rasterbar ${_libType} IMPORTED) + + # LibtorrentRasterbar_DEFINITIONS var contains a mix of -D, -f, and possible -std options + # let's split them into definitions and options (that are not definitions) + set(LibtorrentRasterbar_defines "${LibtorrentRasterbar_DEFINITIONS}") + set(LibtorrentRasterbar_options "${LibtorrentRasterbar_DEFINITIONS}") + list(FILTER LibtorrentRasterbar_defines INCLUDE REGEX "(^|;)-D.+") + list(FILTER LibtorrentRasterbar_options EXCLUDE REGEX "(^|;)-D.+") + # remove '-D' from LibtorrentRasterbar_defines + string(REGEX REPLACE "(^|;)(-D)" "\\1" LibtorrentRasterbar_defines "${LibtorrentRasterbar_defines}") + + set_target_properties(LibtorrentRasterbar::torrent-rasterbar PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + IMPORTED_LOCATION "${LibtorrentRasterbar_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${LibtorrentRasterbar_INCLUDE_DIRS}" + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${LibtorrentRasterbar_INCLUDE_DIRS}" + INTERFACE_LINK_LIBRARIES "${LibtorrentRasterbar_LIBRARIES}" + INTERFACE_COMPILE_DEFINITIONS "${LibtorrentRasterbar_defines}" + INTERFACE_COMPILE_OPTIONS "${LibtorrentRasterbar_options}" + ) + endif() +endfunction() + +if (NOT LibtorrentRasterbar_FOUND) + _try_config_mode() +endif() + +if (NOT LibtorrentRasterbar_FOUND) + _try_pkgconfig_mode() +endif() + +if (NOT LibtorrentRasterbar_FOUND) + _try_generic_mode() +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LibtorrentRasterbar_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args(LibtorrentRasterbar DEFAULT_MSG LibtorrentRasterbar_FOUND) diff --git a/examples/connection_tester.cpp b/examples/connection_tester.cpp new file mode 100644 index 0000000..fdbc732 --- /dev/null +++ b/examples/connection_tester.cpp @@ -0,0 +1,1217 @@ +/* + +Copyright (c) 2010-2022, Arvid Norberg +Copyright (c) 2015, Mike Tzou +Copyright (c) 2016, 2018, Alden Torres +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016-2017, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/session.hpp" // for default_disk_io_constructor +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef BOOST_ASIO_DYN_LINK +#include +#endif + +namespace { + +using namespace lt; +using namespace lt::aux; // for write_* and read_* +using lt::make_address_v4; + +using namespace std::placeholders; + +void generate_block(span buffer, piece_index_t const piece + , int const offset) +{ + std::uint32_t const fill = static_cast( + (static_cast(piece) << 8) | ((offset / 0x4000) & 0xff)); + for (auto& w : buffer) w = fill; +} + +// in order to circumvent the restriction of only +// one connection per IP that most clients implement +// all sockets created by this tester are bound to +// unique local IPs in the range (127.0.0.1 - 127.255.255.255) +// it's only enabled if the target is also on the loopback +int local_if_counter = 0; +bool local_bind = false; + +// when set to true, blocks downloaded are verified to match +// the test torrents +bool verify_downloads = false; + +// if this is true, one block in 1000 will be sent corrupt. +// this only applies to dual and upload tests +bool test_corruption = false; + +// number of seeds we've spawned. The test is terminated +// when this reaches zero, for dual tests +std::atomic num_seeds(0); + +// the kind of test to run. Upload sends data to a +// bittorrent client, download requests data from +// a client and dual uploads and downloads from a client +// at the same time (this is presumably the most realistic +// test) +enum test_mode_t{ none, upload_test, download_test, dual_test }; +test_mode_t test_mode = none; + +// the number of suggest messages received (total across all peers) +std::atomic num_suggest(0); + +// the number of requests made from suggested pieces +std::atomic num_suggested_requests(0); + +std::string leaf_path(std::string f) +{ + if (f.empty()) return ""; + char const* first = f.c_str(); + char const* sep = strrchr(first, '/'); +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + char const* altsep = strrchr(first, '\\'); + if (sep == 0 || altsep > sep) sep = altsep; +#endif + if (sep == nullptr) return f; + + if (sep - first == int(f.size()) - 1) + { + // if the last character is a / (or \) + // ignore it + std::size_t len = 0; + while (sep > first) + { + --sep; + if (*sep == '/' +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + || *sep == '\\' +#endif + ) + return std::string(sep + 1, len); + ++len; + } + return std::string(first, len); + } + return std::string(sep + 1); +} + +namespace { +std::random_device dev; +std::mt19937 rng(dev()); +} + +struct peer_conn +{ + peer_conn(io_context& ios, int piece_count, int blocks_pp, tcp::endpoint const& ep + , char const* ih, bool seed_, int churn_, bool corrupt_) + : s(ios) + , read_pos(0) + , state(handshaking) + , choked(true) + , current_piece(-1) + , current_piece_is_allowed(false) + , block(0) + , blocks_per_piece(blocks_pp) + , info_hash(ih) + , outstanding_requests(0) + , seed(seed_) + , fast_extension(false) + , blocks_received(0) + , blocks_sent(0) + , num_pieces(piece_count) + , start_time(clock_type::now()) + , churn(churn_) + , corrupt(corrupt_) + , endpoint(ep) + , restarting(false) + { + corruption_counter = rand() % 1000; + if (seed) ++num_seeds; + pieces.reserve(std::size_t(piece_count)); + start_conn(); + } + + void start_conn() + { + if (local_bind) + { + error_code ec; + s.open(endpoint.protocol(), ec); + if (ec) + { + close("ERROR OPEN", ec); + return; + } + tcp::endpoint bind_if(address_v4( + (127 << 24) + unsigned (local_if_counter + 1)), 0); + ++local_if_counter; + s.bind(bind_if, ec); + if (ec) + { + close("ERROR BIND", ec); + return; + } + } + restarting = false; + s.async_connect(endpoint, std::bind(&peer_conn::on_connect, this, _1)); + } + + tcp::socket s; + char write_buf_proto[100]; + std::uint32_t write_buffer[17*1024/4]; + std::uint32_t buffer[17*1024/4]; + int read_pos; + int corruption_counter; + + enum state_t + { + handshaking, + sending_request, + receiving_message + }; + int state; + std::vector pieces; + std::vector suggested_pieces; + std::vector allowed_fast; + bool choked; + piece_index_t current_piece; // the piece we're currently requesting blocks from + bool current_piece_is_allowed; + int block; + int blocks_per_piece; + char const* info_hash; + int outstanding_requests; + // if this is true, this connection is a seed + bool seed; + bool fast_extension; + int blocks_received; + int blocks_sent; + int num_pieces; + time_point start_time; + time_point end_time; + int churn; + bool corrupt; + tcp::endpoint endpoint; + bool restarting; + + void on_connect(error_code const& ec) + { + if (ec) + { + close("ERROR CONNECT", ec); + return; + } + + char handshake[] = "\x13" "BitTorrent protocol\0\0\0\0\0\0\0\x04" + " " // space for info-hash + "aaaaaaaaaaaaaaaaaaaa" // peer-id + "\0\0\0\x01\x02"; // interested + char* h = static_cast(malloc(sizeof(handshake))); + memcpy(h, handshake, sizeof(handshake)); + std::memcpy(h + 28, info_hash, 20); + std::generate(h + 48, h + 68, [] { return char(rand()); }); + // for seeds, don't send the interested message + boost::asio::async_write(s, boost::asio::buffer(h, (sizeof(handshake) - 1) - (seed ? 5 : 0)) + , std::bind(&peer_conn::on_handshake, this, h, _1, _2)); + } + + void on_handshake(char* h, error_code const& ec, size_t) + { + free(h); + if (ec) + { + close("ERROR SEND HANDSHAKE", ec); + return; + } + + // read handshake + boost::asio::async_read(s, boost::asio::buffer(buffer, 68) + , std::bind(&peer_conn::on_handshake2, this, _1, _2)); + } + + void on_handshake2(error_code const& ec, size_t) + { + if (ec) + { + close("ERROR READ HANDSHAKE", ec); + return; + } + + // buffer is the full 68 byte handshake + // look at the extension bits + + fast_extension = (reinterpret_cast(buffer)[27] & 4) != 0; + + if (seed) + { + write_have_all(); + } + else + { + work_download(); + } + } + + void write_have_all() + { + if (fast_extension) + { + char* ptr = write_buf_proto; + // have_all + write_uint32(1, ptr); + write_uint8(0xe, ptr); + // unchoke + write_uint32(1, ptr); + write_uint8(1, ptr); + boost::asio::async_write(s, boost::asio::buffer(write_buf_proto, std::size_t(ptr - write_buf_proto)) + , std::bind(&peer_conn::on_sent, this, _1, _2, "ERROR SENT HAVE ALL")); + } + else + { + // bitfield + int len = (num_pieces + 7) / 8; + char* ptr = reinterpret_cast(buffer); + write_uint32(len + 1, ptr); + write_uint8(5, ptr); + memset(ptr, 255, std::size_t(len)); + ptr += len; + // unchoke + write_uint32(1, ptr); + write_uint8(1, ptr); + boost::asio::async_write(s, boost::asio::buffer(buffer, std::size_t(len + 10)) + , std::bind(&peer_conn::on_sent, this, _1, _2, "ERROR SENT HAVE ALL")); + } + } + + void on_sent(error_code const& ec, size_t, char const* msg) + { + if (ec) + { + close(msg, ec); + return; + } + + // read message + boost::asio::async_read(s, boost::asio::buffer(buffer, 4) + , std::bind(&peer_conn::on_msg_length, this, _1, _2)); + } + + bool write_request() + { + // if we're choked (and there are no allowed-fast pieces left) + if (choked && allowed_fast.empty() && !current_piece_is_allowed) return false; + + // if there are no pieces left to request + if (pieces.empty() && suggested_pieces.empty() + && current_piece == piece_index_t(-1)) + { + return false; + } + + if (current_piece == piece_index_t(-1)) + { + // pick a new piece + if (choked && allowed_fast.size() > 0) + { + current_piece = allowed_fast.front(); + allowed_fast.erase(allowed_fast.begin()); + current_piece_is_allowed = true; + } + else if (suggested_pieces.size() > 0) + { + current_piece = suggested_pieces.front(); + suggested_pieces.erase(suggested_pieces.begin()); + ++num_suggested_requests; + current_piece_is_allowed = false; + } + else if (pieces.size() > 0) + { + current_piece = pieces.front(); + pieces.erase(pieces.begin()); + current_piece_is_allowed = false; + } + else + { + TORRENT_ASSERT_FAIL(); + } + } + char msg[] = "\0\0\0\xd\x06" + " " // piece + " " // offset + " "; // length + char* m = static_cast(malloc(sizeof(msg))); + memcpy(m, msg, sizeof(msg)); + char* ptr = m + 5; + write_uint32(static_cast(current_piece), ptr); + write_uint32(block * 16 * 1024, ptr); + write_uint32(16 * 1024, ptr); + boost::asio::async_write(s, boost::asio::buffer(m, sizeof(msg) - 1) + , std::bind(&peer_conn::on_req_sent, this, m, _1, _2)); + + ++outstanding_requests; + ++block; + if (block == blocks_per_piece) + { + block = 0; + current_piece = piece_index_t(-1); + current_piece_is_allowed = false; + } + return true; + } + + void on_req_sent(char* m, error_code const& ec, size_t) + { + free(m); + if (ec) + { + close("ERROR SEND REQUEST", ec); + return; + } + + work_download(); + } + + void close(char const* msg, error_code const& ec) + { + end_time = clock_type::now(); + char tmp[1024]; + std::snprintf(tmp, sizeof(tmp), "%s: %s", msg, ec ? ec.message().c_str() : ""); + int time = int(total_milliseconds(end_time - start_time)); + if (time == 0) time = 1; + double const up = (std::int64_t(blocks_sent) * 0x4000) / time / 1000.0; + double const down = (std::int64_t(blocks_received) * 0x4000) / time / 1000.0; + error_code e; + + char ep_str[200]; + address const& addr = s.local_endpoint(e).address(); + if (addr.is_v6()) + std::snprintf(ep_str, sizeof(ep_str), "[%s]:%d", addr.to_string().c_str() + , s.local_endpoint(e).port()); + else + std::snprintf(ep_str, sizeof(ep_str), "%s:%d", addr.to_string().c_str() + , s.local_endpoint(e).port()); + std::printf("%s ep: %s sent: %d received: %d duration: %d ms up: %.1fMB/s down: %.1fMB/s\n" + , tmp, ep_str, blocks_sent, blocks_received, time, up, down); + if (seed) --num_seeds; + } + + void work_download() + { + if (pieces.empty() + && suggested_pieces.empty() + && current_piece == piece_index_t(-1) + && outstanding_requests == 0 + && blocks_received >= num_pieces * blocks_per_piece) + { + close("COMPLETED DOWNLOAD", error_code()); + return; + } + + // send requests + if (outstanding_requests < 40) + { + if (write_request()) return; + } + + // read message + boost::asio::async_read(s, boost::asio::buffer(buffer, 4) + , std::bind(&peer_conn::on_msg_length, this, _1, _2)); + } + + void on_msg_length(error_code const& ec, size_t) + { + if ((ec == boost::asio::error::operation_aborted || ec == boost::asio::error::bad_descriptor) + && restarting) + { + start_conn(); + return; + } + + if (ec) + { + close("ERROR RECEIVE MESSAGE PREFIX", ec); + return; + } + char* ptr = reinterpret_cast(buffer); + unsigned int length = read_uint32(ptr); + if (length > sizeof(buffer)) + { + std::fprintf(stderr, "len: %u\n", length); + close("ERROR RECEIVE MESSAGE PREFIX: packet too big", error_code()); + return; + } + boost::asio::async_read(s, boost::asio::buffer(buffer, length) + , std::bind(&peer_conn::on_message, this, _1, _2)); + } + + void on_message(error_code const& ec, size_t bytes_transferred) + { + if ((ec == boost::asio::error::operation_aborted || ec == boost::asio::error::bad_descriptor) + && restarting) + { + start_conn(); + return; + } + + if (ec) + { + close("ERROR RECEIVE MESSAGE", ec); + return; + } + char* ptr = reinterpret_cast(buffer); + int msg = read_uint8(ptr); + + if (test_mode == dual_test && num_seeds == 0) + { + TORRENT_ASSERT(!seed); + close("NO MORE SEEDS, test done", error_code()); + return; + } + + //std::printf("msg: %d len: %d\n", msg, int(bytes_transferred)); + + if (seed) + { + if (msg == 6) + { + if (bytes_transferred != 13) + { + close("REQUEST packet has invalid size", error_code()); + return; + } + piece_index_t const piece = piece_index_t(aux::read_int32(ptr)); + int const start = aux::read_int32(ptr); + int const length = aux::read_int32(ptr); + write_piece(piece, start, length); + } + else if (msg == 3) // not-interested + { + close("DONE", error_code()); + return; + } + else + { + // read another message + boost::asio::async_read(s, boost::asio::buffer(buffer, 4) + , std::bind(&peer_conn::on_msg_length, this, _1, _2)); + } + } + else + { + if (msg == 0xe) // have_all + { + // build a list of all pieces and request them all! + pieces.resize(std::size_t(num_pieces)); + for (std::size_t i = 0; i < pieces.size(); ++i) + pieces[i] = piece_index_t(int(i)); + std::shuffle(pieces.begin(), pieces.end(), rng); + } + else if (msg == 4) // have + { + piece_index_t const piece(aux::read_int32(ptr)); + if (pieces.empty()) pieces.push_back(piece); + else pieces.insert(pieces.begin() + (unsigned(rand()) % pieces.size()), piece); + } + else if (msg == 5) // bitfield + { + pieces.reserve(std::size_t(num_pieces)); + piece_index_t piece(0); + for (int i = 0; i < int(bytes_transferred); ++i) + { + int mask = 0x80; + for (int k = 0; k < 8; ++k) + { + if (piece > piece_index_t(num_pieces)) break; + if (*ptr & mask) pieces.push_back(piece); + mask >>= 1; + ++piece; + } + ++ptr; + } + std::shuffle(pieces.begin(), pieces.end(), rng); + } + else if (msg == 7) // piece + { + if (verify_downloads) + { + piece_index_t const piece(read_int32(ptr)); + int start = read_int32(ptr); + int size = int(bytes_transferred) - 9; + verify_piece(piece, start, ptr, size); + } + ++blocks_received; + --outstanding_requests; + piece_index_t const piece = piece_index_t(aux::read_int32(ptr)); + int start = aux::read_int32(ptr); + + if (churn && (blocks_received % churn) == 0) { + outstanding_requests = 0; + restarting = true; + s.close(); + return; + } + if ((start + int(bytes_transferred)) / 0x4000 == blocks_per_piece) + { + write_have(piece); + return; + } + } + else if (msg == 13) // suggest + { + piece_index_t const piece(aux::read_int32(ptr)); + auto i = std::find(pieces.begin(), pieces.end(), piece); + if (i != pieces.end()) + { + pieces.erase(i); + suggested_pieces.push_back(piece); + ++num_suggest; + } + } + else if (msg == 16) // reject request + { + piece_index_t const piece(aux::read_int32(ptr)); + int start = aux::read_int32(ptr); + int length = aux::read_int32(ptr); + + // put it back! + if (current_piece != piece) + { + if (pieces.empty() || pieces.back() != piece) + pieces.push_back(piece); + } + else + { + block = std::min(start / 0x4000, block); + if (block == 0) + { + pieces.push_back(current_piece); + current_piece = piece_index_t(-1); + current_piece_is_allowed = false; + } + } + --outstanding_requests; + std::fprintf(stderr, "REJECT: [ piece: %d start: %d length: %d ]\n" + , static_cast(piece), start, length); + } + else if (msg == 0) // choke + { + choked = true; + } + else if (msg == 1) // unchoke + { + choked = false; + } + else if (msg == 17) // allowed_fast + { + piece_index_t const piece = piece_index_t(aux::read_int32(ptr)); + auto i = std::find(pieces.begin(), pieces.end(), piece); + if (i != pieces.end()) + { + pieces.erase(i); + allowed_fast.push_back(piece); + } + } + work_download(); + } + } + + bool verify_piece(piece_index_t const piece, int start, char const* ptr, int size) + { + std::uint32_t const* buf = reinterpret_cast(ptr); + std::uint32_t const fill = static_cast( + (static_cast(piece) << 8) | ((start / 0x4000) & 0xff)); + for (int i = 0; i < size / 4; ++i) + { + if (buf[i] != fill) + { + std::fprintf(stderr, "received invalid block. piece %d block %d\n" + , static_cast(piece), start / 0x4000); + exit(1); + } + } + return true; + } + + void write_piece(piece_index_t const piece, int start, int length) + { + generate_block({write_buffer, length / 4} + , piece, start); + + if (corrupt) + { + --corruption_counter; + if (corruption_counter == 0) + { + corruption_counter = 1000; + std::memset(write_buffer, 0, 10); + } + } + char* ptr = write_buf_proto; + write_uint32(9 + length, ptr); + assert(length == 0x4000); + write_uint8(7, ptr); + write_uint32(static_cast(piece), ptr); + write_uint32(start, ptr); + std::array vec; + vec[0] = boost::asio::buffer(write_buf_proto, std::size_t(ptr - write_buf_proto)); + vec[1] = boost::asio::buffer(write_buffer, std::size_t(length)); + boost::asio::async_write(s, vec, std::bind(&peer_conn::on_sent, this, _1, _2, "ERROR SENT PIECE")); + ++blocks_sent; + if (churn && (blocks_sent % churn) == 0 && seed) { + outstanding_requests = 0; + restarting = true; + s.close(); + } + } + + void write_have(piece_index_t const piece) + { + char* ptr = write_buf_proto; + write_uint32(5, ptr); + write_uint8(4, ptr); + write_uint32(static_cast(piece), ptr); + boost::asio::async_write(s, boost::asio::buffer(write_buf_proto, 9), std::bind(&peer_conn::on_sent, this, _1, _2, "ERROR SENT HAVE")); + } +}; + +[[noreturn]] void print_usage() +{ + std::fprintf(stderr, "usage: connection_tester command [options]\n\n" + "command is one of:\n" + " gen-torrent generate a test torrent\n" + " options for this command:\n" + " -s the size of the torrent in megabytes\n" + " -n the number of files in the test torrent\n" + " -t the file to save the .torrent file to\n" + " gen-data generate the data file(s) for the test torrent\n" + " options for this command:\n" + " -t the torrent file that was previously generated\n" + " -P the path to where the data should be stored\n\n" + " gen-test-torrents generate many test torrents (cannot be used for up/down tests)\n" + " options for this command:\n" + " -N number of torrents to generate\n" + " -n number of files in each torrent\n" + " -t base name of torrent files (index is appended)\n\n" + " -T add the specified tracker URL to each torrent\n" + " this option may appear multiple times\n\n" + " upload start an uploader test\n" + " download start a downloader test\n" + " dual start a download and upload test\n" + " options for these commands:\n" + " -c the number of connections to make to the target\n" + " -d the IP address of the target\n" + " -p the port the target listens on\n" + " -t the torrent file previously generated by gen-torrent\n" + " -C send corrupt pieces sometimes (applies to upload and dual)\n" + " -r churn - number of reconnects per second\n\n" + "examples:\n\n" + "connection_tester gen-torrent -s 1024 -n 4 -t test.torrent\n" + "connection_tester upload -c 200 -d 127.0.0.1 -p 6881 -t test.torrent\n" + "connection_tester download -c 200 -d 127.0.0.1 -p 6881 -t test.torrent\n" + "connection_tester dual -c 200 -d 127.0.0.1 -p 6881 -t test.torrent\n"); + exit(1); +} + +void hasher_thread(lt::aux::vector* output + , lt::file_storage const& fs + , piece_index_t const start_piece + , piece_index_t const end_piece + , bool print) +{ + if (print) std::fprintf(stderr, "\n"); + std::uint32_t piece[0x4000 / 4]; + int const piece_size = fs.piece_length(); + + std::vector files = fs.map_block(start_piece, 0 + , std::min(static_cast(end_piece - start_piece) * std::int64_t(piece_size) + , fs.total_size() - static_cast(start_piece) * std::int64_t(piece_size))); + + for (piece_index_t i = start_piece; i < end_piece; ++i) + { + hasher ph; + for (int j = 0; j < piece_size; j += 0x4000) + { + generate_block(piece, i, j); + + // if any part of this block overlaps with a pad-file, we need to + // clear those bytes to 0 + for (int k = 0; k < 0x4000; ) + { + if (files.empty()) + { + TORRENT_ASSERT(i == prev(end_piece)); + TORRENT_ASSERT(k > 0); + TORRENT_ASSERT(k < 0x4000); + // this is the last piece of the torrent, and the piece + // extends a bit past the end of the last file. This part + // should be truncated + ph.update(reinterpret_cast(piece), k); + goto out; + } + auto& f = files.front(); + int const range = int(std::min(std::int64_t(0x4000 - k), f.size)); + if (fs.pad_file_at(f.file_index)) + std::memset(reinterpret_cast(piece) + k, 0, std::size_t(range)); + + f.offset += range; + f.size -= range; + k += range; + if (f.size == 0) files.erase(files.begin()); + } + ph.update(reinterpret_cast(piece), 0x4000); + } +out: + (*output)[i] = ph.final(); + int const range = static_cast(end_piece) - static_cast(start_piece); + if (print && (static_cast(i) & 1)) + { + int const delta_piece = static_cast(i) - static_cast(start_piece); + std::fprintf(stderr, "\r%.1f %% ", double(delta_piece * 100) / double(range)); + } + } + if (print) std::fprintf(stderr, "\n"); +} + +// size is in megabytes +void generate_torrent(std::vector& buf, int num_pieces, int num_files + , char const* torrent_name) +{ + file_storage fs; + // 1 MiB piece size + const int piece_size = 1024 * 1024; + const std::int64_t total_size = std::int64_t(piece_size) * num_pieces; + + std::int64_t s = total_size; + int file_index = 0; + std::int64_t file_size = total_size / num_files; + while (s > 0) + { + char b[100]; + std::snprintf(b, sizeof(b), "%s/stress_test%d", torrent_name, file_index); + ++file_index; + fs.add_file(b, std::min(s, file_size)); + s -= file_size; + file_size += 200; + } + + lt::create_torrent t(fs, piece_size, lt::create_torrent::v1_only); + + num_pieces = t.num_pieces(); + + int const num_threads = std::thread::hardware_concurrency() + ? int(std::thread::hardware_concurrency()) : 4; + std::printf("hashing in %d threads\n", num_threads); + + std::vector threads; + threads.reserve(std::size_t(num_threads)); + lt::aux::vector hashes{static_cast(num_pieces)}; + for (int i = 0; i < num_threads; ++i) + { + threads.emplace_back(&hasher_thread, &hashes, t.files() + , piece_index_t(i * num_pieces / num_threads) + , piece_index_t((i + 1) * num_pieces / num_threads) + , i == 0); + } + + for (auto& i : threads) + i.join(); + + for (auto i : t.piece_range()) + t.set_hash(i, hashes[i]); + + bencode(std::back_inserter(buf), t.generate()); +} + +void write_handler(file_storage const& fs + , disk_interface& disk, storage_holder& st + , piece_index_t& piece, int& offset + , lt::storage_error const& error) +{ + if (error) + { + std::fprintf(stderr, "storage error: %s\n", error.ec.message().c_str()); + return; + } + + + if (static_cast(piece) & 1) + { + std::fprintf(stderr, "\r%.1f %% " + , double(static_cast(piece) * 100) / double(fs.num_pieces())); + } + + if (piece >= fs.end_piece()) return; + offset += 0x4000; + if (offset >= fs.piece_size(piece)) + { + offset = 0; + ++piece; + } + if (piece >= fs.end_piece()) + { + disk.abort(false); + return; + } + + std::uint32_t buffer[0x4000 / 4]; + generate_block(buffer, piece, offset); + + int const left_in_piece = fs.piece_size(piece) - offset; + if (left_in_piece <= 0) return; + + disk.async_write(st, { piece, offset, std::min(left_in_piece, 0x4000)} + , reinterpret_cast(buffer) + , std::shared_ptr() + , [&](lt::storage_error const& e) + { write_handler(fs, disk, st, piece, offset, e); }); + + disk.submit_jobs(); +} + +void generate_data(std::string const path, torrent_info const& ti) +{ + io_context ios; + counters stats_counters; + settings_pack sett = default_settings(); + std::unique_ptr disk = default_disk_io_constructor(ios, sett, stats_counters); + + file_storage const& fs = ti.files(); + + aux::vector priorities; + sha1_hash info_hash; + storage_params params{ + fs, + nullptr, + path, + storage_mode_sparse, + priorities, + info_hash + }; + + storage_holder st = disk->new_torrent(params, std::shared_ptr()); + + piece_index_t piece(0); + int offset = 0; + + std::uint32_t buffer[0x4000 / 4]; + generate_block(buffer, piece, offset); + + disk->async_write(st, { piece, offset, std::min(fs.piece_size(piece), 0x4000)} + , reinterpret_cast(buffer) + , std::shared_ptr() + , [&](lt::storage_error const& error) + { write_handler(fs, *disk, st, piece, offset, error); }); + + // keep 10 writes in flight at all times + for (int i = 0; i < 10; ++i) + { + write_handler(fs, *disk, st, piece, offset, lt::storage_error()); + } + + disk->submit_jobs(); + + ios.run(); +} + +void io_thread(io_context* ios) try +{ + ios->run(); +} +catch (std::exception const& e) +{ + std::fprintf(stderr, "ERROR: %s\n", e.what()); +} + +} // anonymous namespace + +int main(int argc, char* argv[]) +{ + if (argc <= 1) print_usage(); + + char const* command = argv[1]; + int size = 1000; + int num_files = 10; + int num_torrents = 1; + char const* torrent_file = "benchmark.torrent"; + char const* data_path = "."; + int num_connections = 50; + char const* destination_ip = "127.0.0.1"; + int destination_port = 6881; + int churn = 0; + std::vector trackers; + + argv += 2; + argc -= 2; + + while (argc > 0) + { + char const* optname = argv[0]; + ++argv; + --argc; + + if (optname[0] != '-' || strlen(optname) != 2) + { + std::fprintf(stderr, "unknown option: %s\n", optname); + continue; + } + + // options with no arguments + switch (optname[1]) + { + case 'C': test_corruption = true; continue; + } + + if (argc == 0) + { + std::fprintf(stderr, "missing argument for option: %s\n", optname); + break; + } + + char const* opt = argv[0]; + ++argv; + --argc; + + switch (optname[1]) + { + case 's': size = atoi(opt); break; + case 'n': num_files = atoi(opt); break; + case 'N': num_torrents = atoi(opt); break; + case 't': torrent_file = opt; break; + case 'T': trackers.push_back(opt); break; + case 'P': data_path = opt; break; + case 'c': num_connections = atoi(opt); break; + case 'p': destination_port = atoi(opt); break; + case 'd': destination_ip = opt; break; + case 'r': churn = atoi(opt); break; + default: std::fprintf(stderr, "unknown option: %s\n", optname); + } + } + + if (command == "gen-torrent"_sv) + { + std::vector tmp; + std::string name = leaf_path(torrent_file); + name = name.substr(0, name.find_last_of('.')); + std::printf("generating torrent: %s\n", name.c_str()); + generate_torrent(tmp, size ? size : 1024, num_files ? num_files : 1 + , name.c_str() ); + + FILE* output = stdout; + if ("-"_sv != torrent_file) + { + if( (output = std::fopen(torrent_file, "wb+")) == nullptr) + { + std::fprintf(stderr, "Could not open file '%s' for writing: %s\n" + , torrent_file, std::strerror(errno)); + exit(2); + } + } + std::fprintf(stderr, "writing file to: %s\n", torrent_file); + fwrite(&tmp[0], 1, tmp.size(), output); + if (output != stdout) + std::fclose(output); + + return 0; + } + else if (command == "gen-data"_sv) + { + error_code ec; + torrent_info ti(torrent_file, ec); + if (ec) + { + std::fprintf(stderr, "ERROR LOADING .TORRENT: %s\n", ec.message().c_str()); + return 1; + } + generate_data(data_path, ti); + return 0; + } + else if (command == "gen-test-torrents"_sv) + { + std::vector buf; + for (int i = 0; i < num_torrents; ++i) + { + char torrent_name[100]; + std::snprintf(torrent_name, sizeof(torrent_name), "%s-%d.torrent", torrent_file, i); + + file_storage fs; + for (int j = 0; j < num_files; ++j) + { + char file_name[100]; + std::snprintf(file_name, sizeof(file_name), "%s-%d/file-%d", torrent_file, i, j); + fs.add_file(file_name, std::int64_t(j + i + 1) * 251); + } + // 1 MiB piece size + const int piece_size = 1024 * 1024; + lt::create_torrent t(fs, piece_size, lt::create_torrent::v1_only); + sha1_hash dummy("abcdefghijklmnopqrst"); + for (auto const k : t.piece_range()) + t.set_hash(k, dummy); + + int tier = 0; + for (auto const& tr : trackers) + t.add_tracker(tr, tier++); + + buf.clear(); + std::back_insert_iterator> out(buf); + bencode(out, t.generate()); + FILE* f = std::fopen(torrent_name, "w+"); + if (f == nullptr) + { + std::fprintf(stderr, "Could not open file '%s' for writing: %s\n" + , torrent_name, std::strerror(errno)); + return 1; + } + size_t ret = fwrite(buf.data(), 1, buf.size(), f); + if (ret != buf.size()) + { + std::fprintf(stderr, "write returned: %d (expected %d)\n", int(ret), int(buf.size())); + std::fclose(f); + return 1; + } + std::printf("wrote %s\n", torrent_name); + std::fclose(f); + } + return 0; + } + else if (command == "upload"_sv) + { + test_mode = upload_test; + } + else if (command == "download"_sv) + { + test_mode = download_test; + } + else if (command == "dual"_sv) + { + test_mode = dual_test; + } + else + { + std::fprintf(stderr, "unknown command: %s\n\n", command); + print_usage(); + } + + error_code ec; + address_v4 addr = make_address_v4(destination_ip, ec); + if (ec) + { + std::fprintf(stderr, "ERROR RESOLVING %s: %s\n", destination_ip, ec.message().c_str()); + return 1; + } + tcp::endpoint ep(addr, std::uint16_t(destination_port)); + +#if !defined __APPLE__ + // apparently darwin doesn't seems to let you bind to + // loopback on any other IP than 127.0.0.1 + std::uint32_t const ip = addr.to_uint(); + if ((ip & 0xff000000) == 0x7f000000) + { + local_bind = true; + } +#endif + + torrent_info ti(torrent_file, ec); + if (ec) + { + std::fprintf(stderr, "ERROR LOADING .TORRENT: %s\n", ec.message().c_str()); + return 1; + } + + std::vector conns; + conns.reserve(std::size_t(num_connections)); + int const num_threads = 2; + io_context ios[num_threads]; + lt::sha1_hash const ih = ti.info_hash(); + for (int i = 0; i < num_connections; ++i) + { + bool corrupt = test_corruption && (i & 1) == 0; + bool seed = false; + if (test_mode == upload_test) seed = true; + else if (test_mode == dual_test) seed = (i & 1); + conns.push_back(new peer_conn(ios[i % num_threads], ti.num_pieces(), ti.piece_length() / 16 / 1024 + , ep, ih.data(), seed, churn, corrupt)); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ios[i % num_threads].poll_one(); + } + + std::thread t1(&io_thread, &ios[0]); + std::thread t2(&io_thread, &ios[1]); + + t1.join(); + t2.join(); + + double up = 0.0; + double down = 0.0; + std::int64_t total_sent = 0; + std::int64_t total_received = 0; + + for (peer_conn* p : conns) + { + int time = int(total_milliseconds(p->end_time - p->start_time)); + if (time == 0) time = 1; + total_sent += p->blocks_sent; + total_received += p->blocks_received; + up += (std::int64_t(p->blocks_sent) * 0x4000) / time / 1000.0; + down += (std::int64_t(p->blocks_received) * 0x4000) / time / 1000.0; + delete p; + } + + std::printf("=========================\n" + "suggests: %d suggested-requests: %d\n" + "total sent: %.1f %% received: %.1f %%\n" + "rate sent: %.1f MB/s received: %.1f MB/s\n" + , int(num_suggest), int(num_suggested_requests) + , total_sent * 0x4000 * 100.0 / double(ti.total_size()) + , total_received * 0x4000 * 100.0 / double(ti.total_size()) + , up, down); + + return 0; +} diff --git a/examples/custom_storage.cpp b/examples/custom_storage.cpp new file mode 100644 index 0000000..6295038 --- /dev/null +++ b/examples/custom_storage.cpp @@ -0,0 +1,328 @@ +/* + +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2019-2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include "libtorrent/libtorrent.hpp" + +#include + +namespace { + +// -- example begin +struct temp_storage +{ + explicit temp_storage(lt::file_storage const& fs) : m_files(fs) {} + + lt::span readv(lt::peer_request const r, lt::storage_error& ec) const + { + auto const i = m_file_data.find(r.piece); + if (i == m_file_data.end()) + { + ec.operation = lt::operation_t::file_read; + ec.ec = boost::asio::error::eof; + return {}; + } + if (int(i->second.size()) <= r.start) + { + ec.operation = lt::operation_t::file_read; + ec.ec = boost::asio::error::eof; + return {}; + } + return { i->second.data() + r.start, std::min(r.length, int(i->second.size()) - r.start) }; + } + void writev(lt::span const b, lt::piece_index_t const piece, int const offset) + { + auto& data = m_file_data[piece]; + if (data.empty()) + { + // allocate the whole piece, otherwise we'll invalidate the pointers + // we have returned back to libtorrent + int const size = piece_size(piece); + data.resize(std::size_t(size)); + } + TORRENT_ASSERT(offset + b.size() <= int(data.size())); + std::memcpy(data.data() + offset, b.data(), std::size_t(b.size())); + } + lt::sha1_hash hash(lt::piece_index_t const piece + , lt::span const block_hashes, lt::storage_error& ec) const + { + auto const i = m_file_data.find(piece); + if (i == m_file_data.end()) + { + ec.operation = lt::operation_t::file_read; + ec.ec = boost::asio::error::eof; + return {}; + } + if (!block_hashes.empty()) + { + int const piece_size2 = m_files.piece_size2(piece); + int const blocks_in_piece2 = m_files.blocks_in_piece2(piece); + char const* buf = i->second.data(); + std::int64_t offset = 0; + for (int k = 0; k < blocks_in_piece2; ++k) + { + lt::hasher256 h2; + std::ptrdiff_t const len2 = std::min(lt::default_block_size, int(piece_size2 - offset)); + h2.update({ buf, len2 }); + buf += len2; + offset += len2; + block_hashes[k] = h2.final(); + } + } + return lt::hasher(i->second).final(); + } + lt::sha256_hash hash2(lt::piece_index_t const piece, int const offset, lt::storage_error& ec) + { + auto const i = m_file_data.find(piece); + if (i == m_file_data.end()) + { + ec.operation = lt::operation_t::file_read; + ec.ec = boost::asio::error::eof; + return {}; + } + + int const piece_size = m_files.piece_size2(piece); + + std::ptrdiff_t const len = std::min(lt::default_block_size, piece_size - offset); + + lt::span b = {i->second.data() + offset, len}; + return lt::hasher256(b).final(); + } + +private: + int piece_size(lt::piece_index_t piece) const + { + int const num_pieces = static_cast((m_files.total_size() + m_files.piece_length() - 1) / m_files.piece_length()); + return static_cast(piece) < num_pieces - 1 + ? m_files.piece_length() : static_cast(m_files.total_size() - std::int64_t(num_pieces - 1) * m_files.piece_length()); + } + + lt::file_storage const& m_files; + std::map> m_file_data; +}; + +lt::storage_index_t pop(std::vector& q) +{ + TORRENT_ASSERT(!q.empty()); + lt::storage_index_t const ret = q.back(); + q.pop_back(); + return ret; +} + +struct temp_disk_io final : lt::disk_interface + , lt::buffer_allocator_interface +{ + explicit temp_disk_io(lt::io_context& ioc): m_ioc(ioc) {} + + void settings_updated() override {} + + lt::storage_holder new_torrent(lt::storage_params const& params + , std::shared_ptr const&) override + { + lt::storage_index_t const idx = m_free_slots.empty() + ? m_torrents.end_index() + : pop(m_free_slots); + auto storage = std::make_unique(params.files); + if (idx == m_torrents.end_index()) m_torrents.emplace_back(std::move(storage)); + else m_torrents[idx] = std::move(storage); + return lt::storage_holder(idx, *this); + } + + void remove_torrent(lt::storage_index_t const idx) override + { + m_torrents[idx].reset(); + m_free_slots.push_back(idx); + } + + void abort(bool) override {} + + void async_read(lt::storage_index_t storage, lt::peer_request const& r + , std::function handler + , lt::disk_job_flags_t) override + { + // this buffer is owned by the storage. It will remain valid for as + // long as the torrent remains in the session. We don't need any lifetime + // management of it. + lt::storage_error error; + lt::span b = m_torrents[storage]->readv(r, error); + + post(m_ioc, [handler, error, b, this] + { handler(lt::disk_buffer_holder(*this, const_cast(b.data()), int(b.size())), error); }); + } + + bool async_write(lt::storage_index_t storage, lt::peer_request const& r + , char const* buf, std::shared_ptr + , std::function handler + , lt::disk_job_flags_t) override + { + lt::span const b = { buf, r.length }; + + m_torrents[storage]->writev(b, r.piece, r.start); + + post(m_ioc, [=]{ handler(lt::storage_error()); }); + return false; + } + + void async_hash(lt::storage_index_t storage, lt::piece_index_t const piece + , lt::span block_hashes, lt::disk_job_flags_t + , std::function handler) override + { + lt::storage_error error; + lt::sha1_hash const hash = m_torrents[storage]->hash(piece, block_hashes, error); + post(m_ioc, [=]{ handler(piece, hash, error); }); + } + + void async_hash2(lt::storage_index_t storage, lt::piece_index_t const piece + , int const offset, lt::disk_job_flags_t + , std::function handler) override + { + lt::storage_error error; + lt::sha256_hash const hash = m_torrents[storage]->hash2(piece, offset, error); + post(m_ioc, [=]{ handler(piece, hash, error); }); + } + + void async_move_storage(lt::storage_index_t, std::string p, lt::move_flags_t + , std::function handler) override + { + post(m_ioc, [=]{ + handler(lt::status_t::fatal_disk_error, p + , lt::storage_error(lt::error_code(boost::system::errc::operation_not_supported, lt::system_category()))); + }); + } + + void async_release_files(lt::storage_index_t, std::function) override {} + + void async_delete_files(lt::storage_index_t, lt::remove_flags_t + , std::function handler) override + { + post(m_ioc, [=]{ handler(lt::storage_error()); }); + } + + void async_check_files(lt::storage_index_t + , lt::add_torrent_params const* + , lt::aux::vector + , std::function handler) override + { + post(m_ioc, [=]{ handler(lt::status_t::no_error, lt::storage_error()); }); + } + + void async_rename_file(lt::storage_index_t + , lt::file_index_t const idx + , std::string const name + , std::function handler) override + { + post(m_ioc, [=]{ handler(name, idx, lt::storage_error()); }); + } + + void async_stop_torrent(lt::storage_index_t, std::function handler) override + { + post(m_ioc, handler); + } + + void async_set_file_priority(lt::storage_index_t + , lt::aux::vector prio + , std::function)> handler) override + { + post(m_ioc, [=]{ + handler(lt::storage_error(lt::error_code( + boost::system::errc::operation_not_supported, lt::system_category())), std::move(prio)); + }); + } + + void async_clear_piece(lt::storage_index_t, lt::piece_index_t index + , std::function handler) override + { + post(m_ioc, [=]{ handler(index); }); + } + + // implements buffer_allocator_interface + void free_disk_buffer(char*) override + { + // never free any buffer. We only return buffers owned by the storage + // object + } + + void update_stats_counters(lt::counters&) const override {} + + std::vector get_status(lt::storage_index_t) const override + { return {}; } + + void submit_jobs() override {} + +private: + + lt::aux::vector, lt::storage_index_t> m_torrents; + + // slots that are unused in the m_torrents vector + std::vector m_free_slots; + + // callbacks are posted on this + lt::io_context& m_ioc; +}; + +std::unique_ptr temp_disk_constructor( + lt::io_context& ioc, lt::settings_interface const&, lt::counters&) +{ + return std::make_unique(ioc); +} +// -- example end + +} // anonymous namespace + +int main(int argc, char* argv[]) try +{ + if (argc != 2) { + std::cerr << "usage: ./custom_storage torrent-file\n" + "to stop the client, press return.\n"; + return 1; + } + + lt::session_params ses_params; + ses_params.disk_io_constructor = temp_disk_constructor; + lt::session s(ses_params); + lt::add_torrent_params p; + p.save_path = "./"; + p.ti = std::make_shared(argv[1]); + s.add_torrent(p); + + // wait for the user to end + char a; + int ret = std::scanf("%c\n", &a); + (void)ret; // ignore + return 0; +} +catch (std::exception const& e) { + std::cerr << "ERROR: " << e.what() << "\n"; +} + diff --git a/examples/dump_bdecode.cpp b/examples/dump_bdecode.cpp new file mode 100644 index 0000000..44acc9b --- /dev/null +++ b/examples/dump_bdecode.cpp @@ -0,0 +1,120 @@ +/* + +Copyright (c) 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include + +#include "libtorrent/bencode.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/span.hpp" + +namespace { + +std::vector load_file(char const* filename) +{ + std::fstream in; + in.exceptions(std::ifstream::failbit); + in.open(filename, std::ios_base::in | std::ios_base::binary); + in.seekg(0, std::ios_base::end); + size_t const size = size_t(in.tellg()); + in.seekg(0, std::ios_base::beg); + std::vector ret(size); + in.read(ret.data(), int(size)); + return ret; +} + +[[noreturn]] void print_usage() +{ + std::cerr << R"(usage: dump_bdecode file [options] + OPTIONS: + --items-limit set the upper limit of the number of bencode items + in the bencoded file. + --depth-limit set the recursion limit in the bdecoder +)"; + std::exit(1); +} + +} // anonymous namespace + +int main(int argc, char const* argv[]) try +{ + lt::span args(argv, argc); + + // strip executable name + args = args.subspan(1); + + if (args.empty()) print_usage(); + + char const* filename = args[0]; + args = args.subspan(1); + + int max_decode_depth = 1000; + int max_decode_tokens = 2000000; + + using namespace lt::literals; + + while (!args.empty()) + { + if (args[0] == "--items-limit"_sv && args.size() > 1) + { + max_decode_tokens = atoi(args[1]); + args = args.subspan(2); + } + else if (args[0] == "--depth-limit"_sv && args.size() > 1) + { + max_decode_depth = atoi(args[1]); + args = args.subspan(2); + } + else + { + std::cerr << "unknown option: " << args[0] << "\n"; + print_usage(); + } + } + + std::vector buf = load_file(filename); + int pos = -1; + lt::error_code ec; + lt::bdecode_node const e = lt::bdecode(buf, ec, &pos, max_decode_depth + , max_decode_tokens); + + if (ec) { + std::cerr << "failed to decode: '" << ec.message() << "' at character: " << pos<< "\n"; + return 1; + } + + std::printf("%s\n", print_entry(e).c_str()); +} +catch (std::exception const& e) +{ + std::cerr << "ERROR: " << e.what() << "\n"; +} diff --git a/examples/dump_torrent.cpp b/examples/dump_torrent.cpp new file mode 100644 index 0000000..664e13b --- /dev/null +++ b/examples/dump_torrent.cpp @@ -0,0 +1,198 @@ +/* + +Copyright (c) 2018, Steven Siloti +Copyright (c) 2003-2004, 2008-2010, 2013, 2015-2020, Arvid Norberg +Copyright (c) 2018, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include // for snprintf +#include // for PRId64 et.al. +#include +#include + +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/load_torrent.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/span.hpp" + +namespace { + +[[noreturn]] void print_usage() +{ + std::cerr << R"(usage: dump_torrent torrent-file [options] + OPTIONS: + --items-limit set the upper limit of the number of bencode items + in the torrent file. + --depth-limit set the recursion limit in the bdecoder + --show-padfiles show pad files in file list + --max-pieces set the upper limit on the number of pieces to + load in the torrent. + --max-size reject files larger than this size limit +)"; + std::exit(1); +} + +} + +int main(int argc, char const* argv[]) try +{ + lt::span args(argv, argc); + + // strip executable name + args = args.subspan(1); + + lt::load_torrent_limits cfg; + bool show_pad = false; + + if (args.empty()) print_usage(); + + char const* filename = args[0]; + args = args.subspan(1); + + using namespace lt::literals; + + while (!args.empty()) + { + if (args[0] == "--items-limit"_sv && args.size() > 1) + { + cfg.max_decode_tokens = atoi(args[1]); + args = args.subspan(2); + } + else if (args[0] == "--depth-limit"_sv && args.size() > 1) + { + cfg.max_decode_depth = atoi(args[1]); + args = args.subspan(2); + } + else if (args[0] == "--max-pieces"_sv && args.size() > 1) + { + cfg.max_pieces = atoi(args[1]); + args = args.subspan(2); + } + else if (args[0] == "--max-size"_sv && args.size() > 1) + { + cfg.max_buffer_size = atoi(args[1]) * 1024 * 1024; + args = args.subspan(2); + } + else if (args[0] == "--show-padfiles"_sv) + { + show_pad = true; + args = args.subspan(1); + } + else + { + std::cerr << "unknown option: " << args[0] << "\n"; + print_usage(); + } + } + + lt::add_torrent_params const atp = lt::load_torrent_file(filename, cfg); + + // print info about torrent + if (!atp.dht_nodes.empty()) + { + std::printf("nodes:\n"); + for (auto const& i : atp.dht_nodes) + std::printf("%s: %d\n", i.first.c_str(), i.second); + } + + if (!atp.trackers.empty()) + { + puts("trackers:\n"); + auto tier_it = atp.tracker_tiers.begin(); + int tier = 0; + for (auto const& i : atp.trackers) + { + if (tier_it != atp.tracker_tiers.end()) + { + tier = *tier_it; + ++tier_it; + } + std::printf("%2d: %s\n", tier, i.c_str()); + } + } + + std::stringstream ih; + ih << atp.info_hashes.v1; + if (atp.info_hashes.has_v2()) + ih << ", " << atp.info_hashes.v2; + std::printf("number of pieces: %d\n" + "piece length: %d\n" + "info hash: %s\n" + "comment: %s\n" + "created by: %s\n" + "magnet link: %s\n" + "name: %s\n" + "number of files: %d\n" + "files:\n" + , atp.ti->num_pieces() + , atp.ti->piece_length() + , ih.str().c_str() + , atp.ti->comment().c_str() + , atp.ti->creator().c_str() + , make_magnet_uri(atp).c_str() + , atp.name.c_str() + , atp.ti->num_files()); + lt::file_storage const& st = atp.ti->files(); + for (auto const i : st.file_range()) + { + auto const first = st.map_file(i, 0, 0).piece; + auto const last = st.map_file(i, std::max(std::int64_t(st.file_size(i)) - 1, std::int64_t(0)), 0).piece; + auto const flags = st.file_flags(i); + if ((flags & lt::file_storage::flag_pad_file) && !show_pad) continue; + std::stringstream file_root; + if (!st.root(i).is_all_zeros()) + file_root << st.root(i); + std::printf(" %8" PRIx64 " %11" PRId64 " %c%c%c%c [ %5d, %5d ] %7u %s %s %s%s\n" + , st.file_offset(i) + , st.file_size(i) + , ((flags & lt::file_storage::flag_pad_file)?'p':'-') + , ((flags & lt::file_storage::flag_executable)?'x':'-') + , ((flags & lt::file_storage::flag_hidden)?'h':'-') + , ((flags & lt::file_storage::flag_symlink)?'l':'-') + , static_cast(first) + , static_cast(last) + , std::uint32_t(st.mtime(i)) + , file_root.str().c_str() + , st.file_path(i).c_str() + , (flags & lt::file_storage::flag_symlink) ? "-> " : "" + , (flags & lt::file_storage::flag_symlink) ? st.symlink(i).c_str() : ""); + } + std::printf("web seeds:\n"); + for (auto const& ws : atp.url_seeds) + std::printf("%s\n", ws.c_str()); + + return 0; +} +catch (std::exception const& e) +{ + std::cerr << "ERROR: " << e.what() << "\n"; +} diff --git a/examples/magnet2torrent.cpp b/examples/magnet2torrent.cpp new file mode 100644 index 0000000..e94c0a9 --- /dev/null +++ b/examples/magnet2torrent.cpp @@ -0,0 +1,125 @@ +/* + +Copyright (c) 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include // for write_torrent_file + +int main(int argc, char const* argv[]) try +{ + if (argc != 3) { + std::cerr << "usage: " << argv[0] << " " << std::endl; + return 1; + } + lt::session_params params; + params.disk_io_constructor = lt::disabled_disk_io_constructor; + + params.settings.set_int(lt::settings_pack::alert_mask + , lt::alert_category::status | lt::alert_category::error); + + lt::session ses(std::move(params)); + + lt::add_torrent_params atp = lt::parse_magnet_uri(argv[1]); + atp.save_path = "."; + atp.flags &= ~(lt::torrent_flags::auto_managed | lt::torrent_flags::paused); + atp.file_priorities.resize(100, lt::dont_download); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + lt::torrent_handle h = ses.add_torrent(std::move(atp)); + + for (;;) { + std::vector alerts; + ses.pop_alerts(&alerts); + + for (lt::alert* a : alerts) + { + std::cout << a->message() << std::endl; + if (auto const* mra = lt::alert_cast(a)) + { + std::cerr << "metadata received" << std::endl; + auto const handle = mra->handle; + std::shared_ptr ti = handle.torrent_file(); + if (!ti) + { + std::cerr << "unexpected missing torrent info" << std::endl; + goto done; + } + + // in order to create valid v2 torrents, we need to download the + // piece hashes. libtorrent currently only downloads the hashes + // on-demand, so we would have to download all the content. + // Instead, produce an invalid v2 torrent that's missing piece + // layers + if (ti->v2()) + { + std::cerr << "found v2 torrent, generating a torrent missing piece hashes" << std::endl; + } + handle.save_resume_data(lt::torrent_handle::save_info_dict); + handle.set_flags(lt::torrent_flags::paused); + } + else if (auto* rda = lt::alert_cast(a)) + { + // don't include piece layers + rda->params.merkle_trees.clear(); + lt::entry e = lt::write_torrent_file(rda->params, lt::write_flags::allow_missing_piece_layer); + std::vector torrent; + lt::bencode(std::back_inserter(torrent), e); + std::ofstream out(argv[2]); + out.write(torrent.data(), int(torrent.size())); + goto done; + } + else if (auto const* rdf = lt::alert_cast(a)) + { + std::cerr << "failed to save resume data: " << rdf->message() << std::endl; + goto done; + } + } + ses.wait_for_alert(std::chrono::milliseconds(200)); + } + done: + std::cerr<< "done, shutting down" << std::endl; +} +catch (std::exception& e) +{ + std::cerr << "Error: " << e.what() << std::endl; +} diff --git a/examples/make_torrent.cpp b/examples/make_torrent.cpp new file mode 100644 index 0000000..f1d517f --- /dev/null +++ b/examples/make_torrent.cpp @@ -0,0 +1,319 @@ +/* + +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2004, 2008-2013, 2015-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/create_torrent.hpp" + +#include +#include +#include +#include +#include + +#ifdef TORRENT_WINDOWS +#include // for _getcwd +#endif + +namespace { + +using namespace std::placeholders; + +std::vector load_file(std::string const& filename) +{ + std::fstream in; + in.exceptions(std::ifstream::failbit); + in.open(filename.c_str(), std::ios_base::in | std::ios_base::binary); + in.seekg(0, std::ios_base::end); + size_t const size = size_t(in.tellg()); + in.seekg(0, std::ios_base::beg); + std::vector ret(size); + in.read(ret.data(), int(ret.size())); + return ret; +} + +std::string branch_path(std::string const& f) +{ + if (f.empty()) return f; + +#ifdef TORRENT_WINDOWS + if (f == "\\\\") return ""; +#endif + if (f == "/") return ""; + + auto len = f.size(); + // if the last character is / or \ ignore it + if (f[len-1] == '/' || f[len-1] == '\\') --len; + while (len > 0) { + --len; + if (f[len] == '/' || f[len] == '\\') + break; + } + + if (f[len] == '/' || f[len] == '\\') ++len; + return std::string(f.c_str(), len); +} + +// do not include files and folders whose +// name starts with a . +bool file_filter(std::string const& f) +{ + if (f.empty()) return false; + + char const* first = f.c_str(); + char const* sep = strrchr(first, '/'); +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + char const* altsep = strrchr(first, '\\'); + if (sep == nullptr || altsep > sep) sep = altsep; +#endif + // if there is no parent path, just set 'sep' + // to point to the filename. + // if there is a parent path, skip the '/' character + if (sep == nullptr) sep = first; + else ++sep; + + // return false if the first character of the filename is a . + if (sep[0] == '.') return false; + + std::cerr << f << "\n"; + return true; +} + +[[noreturn]] void print_usage() +{ + std::cerr << R"(usage: make_torrent FILE [OPTIONS] + +Generates a torrent file from the specified file +or directory and writes it to standard out + + +OPTIONS: +-w url adds a web seed to the torrent with + the specified url +-t url adds the specified tracker to the + torrent. For multiple trackers, specify more + -t options. Specify a dash character "-" as a tracker to indicate + the following trackers should be in a higher tier. +-c comment sets the comment to the specified string +-C creator sets the created-by field to the specified string +-s bytes specifies a piece size for the torrent + This has to be a power of 2, minimum 16kiB +-l Don't follow symlinks, instead encode them as + links in the torrent file +-o file specifies the output filename of the torrent file + If this is not specified, the torrent file is + printed to the standard out, except on windows + where the filename defaults to a.torrent +-r file add root certificate to the torrent, to verify + the HTTPS tracker +-S info-hash add a similar torrent by info-hash. The similar + torrent is expected to share some files with this one +-L collection add a collection name to this torrent. Other torrents + in the same collection is expected to share files + with this one. +-2 Only generate V2 metadata +-T Include file timestamps in the .torrent file. +)"; + std::exit(1); +} + +} // anonymous namespace + +int main(int argc_, char const* argv_[]) try +{ + lt::span args(argv_, argc_); + std::string creator_str = "libtorrent"; + std::string comment_str; + + if (args.size() < 2) print_usage(); + + std::vector web_seeds; + std::vector trackers; + std::vector collections; + std::vector similar; + int piece_size = 0; + lt::create_flags_t flags = {}; + std::string root_cert; + + std::string outfile; +#ifdef TORRENT_WINDOWS + // don't ever write binary data to the console on windows + // it will just be interpreted as text and corrupted + outfile = "a.torrent"; +#endif + + std::string full_path = args[1]; + args = args.subspan(2); + + for (; !args.empty(); args = args.subspan(1)) { + if (args[0][0] != '-') print_usage(); + + char const flag = args[0][1]; + + switch (flag) + { + case 'l': + flags |= lt::create_torrent::symlinks; + continue; + case '2': + flags |= lt::create_torrent::v2_only; + continue; + case 'T': + flags |= lt::create_torrent::modification_time; + continue; + } + + if (args.size() < 2) print_usage(); + + switch (flag) + { + case 'w': web_seeds.push_back(args[1]); break; + case 't': trackers.push_back(args[1]); break; + case 's': piece_size = atoi(args[1]); break; + case 'o': outfile = args[1]; break; + case 'C': creator_str = args[1]; break; + case 'c': comment_str = args[1]; break; + case 'r': root_cert = args[1]; break; + case 'L': collections.push_back(args[1]); break; + case 'S': { + if (strlen(args[1]) != 40) { + std::cerr << "invalid info-hash for -S. " + "Expected 40 hex characters\n"; + print_usage(); + } + std::stringstream hash(args[1]); + lt::sha1_hash ih; + hash >> ih; + if (hash.fail()) { + std::cerr << "invalid info-hash for -S\n"; + print_usage(); + } + similar.push_back(ih); + break; + } + default: + print_usage(); + } + args = args.subspan(1); + } + + lt::file_storage fs; +#ifdef TORRENT_WINDOWS + if (full_path[1] != ':') +#else + if (full_path[0] != '/') +#endif + { + char cwd[2048]; +#ifdef TORRENT_WINDOWS +#define getcwd_ _getcwd +#else +#define getcwd_ getcwd +#endif + + char const* ret = getcwd_(cwd, sizeof(cwd)); + if (ret == nullptr) { + std::cerr << "failed to get current working directory: " + << strerror(errno) << "\n"; + return 1; + } + +#undef getcwd_ +#ifdef TORRENT_WINDOWS + full_path = cwd + ("\\" + full_path); +#else + full_path = cwd + ("/" + full_path); +#endif + } + + lt::add_files(fs, full_path, file_filter, flags); + if (fs.num_files() == 0) { + std::cerr << "no files specified.\n"; + return 1; + } + + lt::create_torrent t(fs, piece_size, flags); + int tier = 0; + for (std::string const& tr : trackers) { + if (tr == "-") ++tier; + else t.add_tracker(tr, tier); + } + + for (std::string const& ws : web_seeds) + t.add_url_seed(ws); + + for (std::string const& c : collections) + t.add_collection(c); + + for (lt::sha1_hash const& s : similar) + t.add_similar_torrent(s); + + auto const num = t.num_pieces(); + lt::set_piece_hashes(t, branch_path(full_path) + , [num] (lt::piece_index_t const p) { + std::cerr << "\r" << p << "/" << num; + }); + + std::cerr << "\n"; + t.set_creator(creator_str.c_str()); + if (!comment_str.empty()) { + t.set_comment(comment_str.c_str()); + } + + if (!root_cert.empty()) { + std::vector const pem = load_file(root_cert); + t.set_root_cert(std::string(&pem[0], pem.size())); + } + + // create the torrent and print it to stdout + std::vector torrent; + lt::bencode(back_inserter(torrent), t.generate()); + if (!outfile.empty()) { + std::fstream out; + out.exceptions(std::ifstream::failbit); + out.open(outfile.c_str(), std::ios_base::out | std::ios_base::binary); + out.write(torrent.data(), int(torrent.size())); + } + else { + std::cout.write(torrent.data(), int(torrent.size())); + } + + return 0; +} +catch (std::exception& e) { + std::cerr << "ERROR: " << e.what() << "\n"; + return 1; +} diff --git a/examples/print.cpp b/examples/print.cpp new file mode 100644 index 0000000..2e18a75 --- /dev/null +++ b/examples/print.cpp @@ -0,0 +1,605 @@ +#ifdef _WIN32 + +#include +#include + +#else + +#include // for close() +#include // for open() +#include + +#endif + +#include "libtorrent/config.hpp" + +#include "print.hpp" + +#include // for atoi +#include // for strlen +#include +#include // for std::min +#include // for back_inserter + +char const* esc(char const* code) +{ + // this is a silly optimization + // to avoid copying of strings + enum { num_strings = 200 }; + static char buf[num_strings][20]; + static int round_robin = 0; + char* ret = buf[round_robin]; + ++round_robin; + if (round_robin >= num_strings) round_robin = 0; + ret[0] = '\033'; + ret[1] = '['; + int i = 2; + int j = 0; + while (code[j]) ret[i++] = code[j++]; + ret[i++] = 'm'; + ret[i++] = 0; + return ret; +} + +std::string to_string(int v, int width) +{ + char buf[100]; + std::snprintf(buf, sizeof(buf), "%*d", width, v); + return buf; +} + +std::string add_suffix_float(double val, char const* suffix) +{ + if (val < 0.001) + { + std::string ret; + ret.resize(4 + 2, ' '); + if (suffix) ret.resize(4 + 2 + strlen(suffix), ' '); + return ret; + } + + const char* prefix[] = {"kB", "MB", "GB", "TB", "PB"}; + const int num_prefix = sizeof(prefix) / sizeof(const char*); + int i = 0; + for (; i < num_prefix - 1; ++i) + { + val /= 1000.; + if (std::fabs(val) < 1000.) break; + } + char ret[100]; + std::snprintf(ret, sizeof(ret), "%4.*f%s%s", val < 99 ? 1 : 0, val, prefix[i], suffix ? suffix : ""); + return ret; +} + +std::string color(std::string const& s, color_code c) +{ + if (c == col_none) return s; + if (std::count(s.begin(), s.end(), ' ') == int(s.size())) return s; + + char buf[1024]; + std::snprintf(buf, sizeof(buf), "\x1b[3%dm%s\x1b[39m", c, s.c_str()); + return buf; +} + +std::string const& progress_bar(int progress, int width, color_code c + , char fill, char bg, std::string caption, int flags) +{ + static std::string bar; + bar.clear(); + bar.reserve(size_t(width + 10)); + + auto const progress_chars = static_cast((progress * width + 500) / 1000); + + if (caption.empty()) + { + char code[10]; + std::snprintf(code, sizeof(code), "\x1b[3%dm", c); + bar = code; + std::fill_n(std::back_inserter(bar), progress_chars, fill); + std::fill_n(std::back_inserter(bar), std::size_t(width) - progress_chars, bg); + bar += esc("39"); + } + else + { + // foreground color (depends a bit on background color) + color_code tc = col_black; + if (c == col_black || c == col_blue) + tc = col_white; + + caption.resize(size_t(width), ' '); + +#ifdef _WIN32 + char const* background = "40"; +#else + char const* background = "48;5;238"; +#endif + + char str[256]; + if (flags & progress_invert) + std::snprintf(str, sizeof(str), "\x1b[%sm\x1b[37m%s\x1b[4%d;3%dm%s\x1b[49;39m" + , background, caption.substr(0, progress_chars).c_str(), c, tc + , caption.substr(progress_chars).c_str()); + else + std::snprintf(str, sizeof(str), "\x1b[4%d;3%dm%s\x1b[%sm\x1b[37m%s\x1b[49;39m" + , c, tc, caption.substr(0, progress_chars).c_str(), background + , caption.substr(progress_chars).c_str()); + bar = str; + } + return bar; +} + +std::string const& piece_bar(lt::bitfield const& p, int width) +{ +#ifdef _WIN32 + int const table_size = 5; +#else + int const table_size = 18; + width *= 2; // we only print one character for every two "slots" +#endif + + double const piece_per_char = p.size() / double(width); + static std::string bar; + bar.clear(); + + if (width <= 0) return bar; + + bar.reserve(std::size_t(width) * 6); + bar += "["; + if (p.size() == 0) + { + for (int i = 0; i < width; ++i) bar += ' '; + bar += "]"; + return bar; + } + + // the [piece, piece + pieces_per_char) range is the pieces that are represented by each character + double piece = 0; + + // we print two blocks at a time, so calculate the color in pair +#ifndef _WIN32 + int color[2]; + int last_color[2] = { -1, -1}; +#endif + + for (int i = 0; i < width; ++i, piece += piece_per_char) + { + int num_pieces = 0; + int num_have = 0; + int end = (std::max)(int(piece + piece_per_char), int(piece) + 1); + for (int k = int(piece); k < end; ++k, ++num_pieces) + if (p[k]) ++num_have; + int const c = int(std::ceil(num_have / float((std::max)(num_pieces, 1)) * (table_size - 1))); + +#ifndef _WIN32 + color[i & 1] = c; + + if ((i & 1) == 1) + { + // now, print color[0] and [1] + // bg determines whether we're settings foreground or background color + static int const bg[] = { 38, 48}; + for (int k = 0; k < 2; ++k) + { + if (color[k] != last_color[k]) + { + char buf[40]; + std::snprintf(buf, sizeof(buf), "\x1b[%d;5;%dm", bg[k & 1], 232 + color[k]); + last_color[k] = color[k]; + bar += buf; + } + } + bar += "\u258C"; + } +#else + static char const table[] = {' ', '\xb0', '\xb1', '\xb2', '\xdb'}; + bar += table[c]; +#endif + } + bar += esc("0"); + bar += "]"; + return bar; +} + +std::string avail_bar(lt::span avail, int const width, int& pos) +{ + std::string ret; + int const max_avail = (std::max)(1, *std::max_element(avail.begin(), avail.end())); + int cursor = 0; +#ifndef _WIN32 + for (int piece = 0; piece < avail.size(); piece += 2) + { + int p[2]; + p[0] = avail[piece] * 22 / max_avail; + p[1] = piece + 1 < avail.size() ? avail[piece + 1] * 22 / max_avail : 0; + assert(p[0] >= 0); + assert(p[0] < 23); + assert(p[1] >= 0); + assert(p[1] < 23); + char buf[50]; + std::snprintf(buf, sizeof(buf), "\x1b[38;5;%dm\x1b[48;5;%dm\u258c" + , 232 + p[0], 232 + p[1]); + ret += buf; + cursor += 1; + if (cursor >= width) + { + cursor = 0; + pos += 1; + ret += "\n"; + } + } +#else + for (int piece = 0; piece < avail.size(); ++piece) + { + static char const table[] = {' ', '\xb0', '\xb1', '\xb2', '\xdb'}; + int const p = avail[piece] * 4 / max_avail; + assert(p >= 0); + assert(p < 5); + ret += table[p]; + cursor += 1; + if (cursor >= width) + { + cursor = 0; + pos += 1; + ret += "\n"; + } + } +#endif + if (cursor > 0) + ret += "\x1b[K\n"; + return ret; +} + +namespace { +int get_piece(lt::bitfield const& p, int index) +{ + if (index < 0 || index >= p.size()) return 0; + return p.get_bit(index) ? 1 : 0; +} +} + +#ifndef _WIN32 +// this function uses the block characters that splits up the glyph in 4 +// segments and provide all combinations of a segment lit or not. This allows us +// to print 4 pieces per character. +std::string piece_matrix(lt::bitfield const& p, int width, int* height) +{ + if (width <= 0) return {}; + + // print two rows of pieces at a time + int piece = 0; + ++*height; + std::string ret; + ret.reserve(std::size_t((p.size() + width * 2 - 1) / width / 2 * 4)); + while (piece < p.size()) + { + if (piece > 0) + ret += "\n"; + for (int i = 0; i < width; ++i) + { + // each character has 4 pieces. store them in a byte to use for lookups + int const c = get_piece(p, piece) + | (get_piece(p, piece+1) << 1) + | (get_piece(p, width*2+piece) << 2) + | (get_piece(p, width*2+piece+1) << 3); + + // we have 4 bits, 16 different combinations + static char const* const chars[] = + { + " ", // no bit is set 0000 + "\u2598", // upper left 0001 + "\u259d", // upper right 0010 + "\u2580", // both top bits 0011 + "\u2596", // lower left 0100 + "\u258c", // both left bits 0101 + "\u259e", // upper right, lower left 0110 + "\u259b", // left and upper sides 0111 + "\u2597", // lower right 1000 + "\u259a", // lower right, upper left 1001 + "\u2590", // right side 1010 + "\u259c", // lower right, top side 1011 + "\u2584", // both lower bits 1100 + "\u2599", // both lower, top left 1101 + "\u259f", // both lower, top right 1110 + "\x1b[7m \x1b[27m" // all bits are set (full block) + }; + + ret += chars[c]; + piece += 2; + } + ret += "\x1b[K"; + ++*height; + piece += width * 2; // skip another row, as we've already printed it + } + return ret; +} +#else +// on MS-DOS terminals, we only have block characters for upper half and lower +// half. This lets us print two pieces per character. +std::string piece_matrix(lt::bitfield const& p, int width, int* height) +{ + // print two rows of pieces at a time + int piece = 0; + ++*height; + std::string ret; + ret.reserve((p.size() + width * 2 - 1) / width); + while (piece < p.size()) + { + if (piece > 0) + ret += '\n'; + for (int i = 0; i < width; ++i) + { + // each character has 8 pieces. store them in a byte to use for lookups + // the ordering of these bits + int const c = get_piece(p, piece) + | (get_piece(p, width*2+piece) << 1); + + static char const* const chars[] = + { + " ", // no piece 00 + "\xdf", // top piece 01 + "\xdc", // bottom piece 10 + "\xdb" // both pieces 11 + }; + + ret += chars[c]; + ++piece; + } + ++*height; + piece += width * 2; // skip another row, as we've already printed it + } + return ret; +} +#endif + +void set_cursor_pos(int x, int y) +{ +#ifdef _WIN32 + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + COORD c = {SHORT(x), SHORT(y)}; + SetConsoleCursorPosition(out, c); +#else + std::printf("\033[%d;%dH", y + 1, x + 1); +#endif +} + +void clear_screen() +{ +#ifdef _WIN32 + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + + COORD c = {0, 0}; + CONSOLE_SCREEN_BUFFER_INFO si; + GetConsoleScreenBufferInfo(out, &si); + DWORD n; + FillConsoleOutputCharacter(out, ' ', si.dwSize.X * si.dwSize.Y, c, &n); + FillConsoleOutputAttribute(out, 0x7, si.dwSize.X * si.dwSize.Y, c, &n); +#else + std::printf("\033[2J"); +#endif +} + +void clear_rows(int y1, int y2) +{ + if (y1 > y2) return; + +#ifdef _WIN32 + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + + COORD c = {0, SHORT(y1)}; + SetConsoleCursorPosition(out, c); + CONSOLE_SCREEN_BUFFER_INFO si; + GetConsoleScreenBufferInfo(out, &si); + DWORD n; + int num_chars = si.dwSize.X * (std::min)(si.dwSize.Y - y1, y2 - y1); + FillConsoleOutputCharacter(out, ' ', num_chars, c, &n); + FillConsoleOutputAttribute(out, 0x7, num_chars, c, &n); +#else + for (int i = y1; i < y2; ++i) + std::printf("\033[%d;1H\033[2K", i + 1); +#endif +} + +std::pair terminal_size() +{ + int width = 80; + int height = 50; +#ifdef _WIN32 + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO coninfo; + if (GetConsoleScreenBufferInfo(out, &coninfo)) + { + width = coninfo.dwSize.X; + height = coninfo.srWindow.Bottom - coninfo.srWindow.Top; +#else + int tty = open("/dev/tty", O_RDONLY); + if (tty < 0) + { + width = 190; + height = 100; + return {width, height}; + } + winsize size; + int ret = ioctl(tty, TIOCGWINSZ, reinterpret_cast(&size)); + close(tty); + if (ret == 0) + { + width = size.ws_col; + height = size.ws_row; +#endif + + if (width < 64) + width = 64; + if (height < 25) + height = 25; + } + else + { + width = 190; + height = 100; + } + return {width, height}; +} + +#ifdef _WIN32 +void apply_ansi_code(WORD* attributes, bool* reverse, bool* support_chaining, int code) +{ + static const WORD color_table[8] = + { + 0, // black + FOREGROUND_RED, // red + FOREGROUND_GREEN, // green + FOREGROUND_RED | FOREGROUND_GREEN, // yellow + FOREGROUND_BLUE, // blue + FOREGROUND_RED | FOREGROUND_BLUE, // magenta + FOREGROUND_BLUE | FOREGROUND_GREEN, // cyan + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE // white + }; + + enum + { + foreground_mask = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, + background_mask = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY + }; + + static const int fg_mask[2] = {foreground_mask, background_mask}; + static const int bg_mask[2] = {background_mask, foreground_mask}; + static const int fg_shift[2] = { 0, 4}; + static const int bg_shift[2] = { 4, 0}; + + // default foreground + if (code == 39) code = 37; + + // default background + if (code == 49) code = 40; + + if (code == 0) + { + // reset + *attributes = color_table[7]; + *reverse = false; + *support_chaining = true; + } + else if (code == 1) + { + // intensity + *attributes |= *reverse ? BACKGROUND_INTENSITY : FOREGROUND_INTENSITY; + *support_chaining = true; + } + else if (code == 7) + { + // reverse video + *support_chaining = true; + if (*reverse) return; + *reverse = true; + int fg_col = *attributes & foreground_mask; + int bg_col = (*attributes & background_mask) >> 4; + *attributes &= ~(foreground_mask + background_mask); + *attributes |= fg_col << 4; + *attributes |= bg_col; + } + else if (code >= 30 && code <= 37) + { + // foreground color + *attributes &= ~fg_mask[*reverse]; + *attributes |= color_table[code - 30] << fg_shift[*reverse]; + *support_chaining = true; + } + else if (code >= 40 && code <= 47) + { + // background color + *attributes &= ~bg_mask[*reverse]; + *attributes |= color_table[code - 40] << bg_shift[*reverse]; + *support_chaining = true; + } +} +#endif +void print(char const* buf) +{ +#ifdef _WIN32 + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + + WORD current_attributes = 7; + bool reverse = false; + SetConsoleTextAttribute(out, current_attributes); + + char const* start = buf; + DWORD written; + while (*buf != 0) + { + if (*buf == '\033' && buf[1] == '[') + { + WriteFile(out, start, DWORD(buf - start), &written, nullptr); + buf += 2; // skip escape and '[' + start = buf; + if (*buf == 0) break; + if (*start == 'K') + { + // this means clear the rest of the line. + CONSOLE_SCREEN_BUFFER_INFO sbi; + if (GetConsoleScreenBufferInfo(out, &sbi)) + { + COORD const pos = sbi.dwCursorPosition; + int const width = sbi.dwSize.X; + int const run = width - pos.X; + DWORD n; + FillConsoleOutputAttribute(out, 0x7, run, pos, &n); + FillConsoleOutputCharacter(out, ' ', run, pos, &n); + } + ++buf; + start = buf; + continue; + } + else if (*start == 'J') + { + // clear rest of screen + CONSOLE_SCREEN_BUFFER_INFO sbi; + if (GetConsoleScreenBufferInfo(out, &sbi)) + { + COORD pos = sbi.dwCursorPosition; + int width = sbi.dwSize.X; + int run = (width - pos.X) + width * (sbi.dwSize.Y - pos.Y - 1); + DWORD n; + FillConsoleOutputAttribute(out, 0x7, run, pos, &n); + FillConsoleOutputCharacter(out, ' ', run, pos, &n); + } + ++buf; + start = buf; + continue; + } +one_more: + while (*buf != 'm' && *buf != ';' && *buf != 0) ++buf; + + // this is where we handle reset, color and reverse codes + if (*buf == 0) break; + int code = atoi(start); + bool support_chaining = false; + apply_ansi_code(¤t_attributes, &reverse, &support_chaining, code); + if (support_chaining) + { + if (*buf == ';') + { + ++buf; + start = buf; + goto one_more; + } + } + else + { + // ignore codes with multiple fields for now + while (*buf != 'm' && *buf != 0) ++buf; + } + SetConsoleTextAttribute(out, current_attributes); + ++buf; // skip 'm' + start = buf; + } + else + { + ++buf; + } + } + WriteFile(out, start, DWORD(buf - start), &written, nullptr); + +#else + fputs(buf, stdout); +#endif +} diff --git a/examples/print.hpp b/examples/print.hpp new file mode 100644 index 0000000..70348c8 --- /dev/null +++ b/examples/print.hpp @@ -0,0 +1,56 @@ +#ifndef PRINT_HPP_ +#define PRINT_HPP_ + +#include +#include // for snprintf +#include // for PRId64 et.al. +#include "libtorrent/bitfield.hpp" +#include "libtorrent/span.hpp" + +enum color_code +{ + col_none = -1, + col_black = 0, + col_red = 1, + col_green = 2, + col_yellow = 3, + col_blue = 4, + col_magenta = 5, + col_cyan = 6, + col_white = 7 +}; + +char const* esc(char const* code); + +std::string to_string(int v, int width); + +std::string add_suffix_float(double val, char const* suffix); + +template std::string add_suffix(T val, char const* suffix = nullptr) { + return add_suffix_float(double(val), suffix); +} + +std::string color(std::string const& s, color_code c); + +enum { progress_invert = 1}; + +std::string const& progress_bar(int progress, int width, color_code c = col_green + , char fill = '#', char bg = '-', std::string caption = "", int flags = 0); + +std::string const& piece_bar(lt::bitfield const& p, int width); + +std::string avail_bar(lt::span avail, int const width, int& pos); + +void set_cursor_pos(int x, int y); + +void clear_screen(); + +void clear_rows(int y1, int y2); + +std::pair terminal_size(); +std::string piece_matrix(lt::bitfield const& p, int width, int* height); + +void print(char const* str); + +#endif // PRINT_HPP_ + diff --git a/examples/run_benchmarks.py b/examples/run_benchmarks.py new file mode 100755 index 0000000..3dcbe45 --- /dev/null +++ b/examples/run_benchmarks.py @@ -0,0 +1,438 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 +from __future__ import print_function + +import sys +import os +import resource +import shutil +import shlex +import time +import subprocess +import random +import signal +import hashlib + +# this is a disk I/O benchmark script. It runs benchmarks +# over different number of peers. + +# to set up the test, build the example directory in release +# and stage client_test and connection_tester to the examples directory: +# +# bjam link=static release debug-symbols=on stage +# +# make sure gnuplot is installed. + +# the following lists define the space tests will be run in + +peers = [50, 200, 500, 1000] +# builds = ['rtorrent', 'utorrent', 'libtorrent'] +builds = ['libtorrent'] + +# the number of peers for the filesystem test. The +# idea is to stress test the filesystem by using a lot +# of peers, since each peer essentially is a separate +# read location on the platter +default_peers = peers[1] + +# the amount of cache for the filesystem test +# 5.5 GiB of cache +default_cache = 400000 + +# the number of seconds to run each test. It's important that +# this is shorter than what it takes to finish downloading +# the test torrent, since then the average rate will not +# be representative of the peak anymore +# this has to be long enough to download a full copy +# of the test torrent. It's also important for the +# test to be long enough that the warming up of the +# disk cache is not a significant part of the test, +# since download rates will be extremely high while downloading +# into RAM +test_duration = 100 + +utorrent_version = 'utorrent-server-alpha-v3_3' + +# make sure the environment is properly set up +try: + if os.name == 'posix': + resource.setrlimit(resource.RLIMIT_NOFILE, (4000, 5000)) +except Exception: + if resource.getrlimit(resource.RLIMIT_NOFILE)[0] < 4000: + print('please set ulimit -n to at least 4000') + sys.exit(1) + + +def build_stage_dirs(): + ret = [] + for i in builds[2:3]: + ret.append('stage_%s' % i) + return ret + + +# make sure we have all the binaries available +binaries = ['client_test', 'connection_tester'] +for b in build_stage_dirs(): + for i in binaries: + p = os.path.join(b, i) + if not os.path.exists(p): + print('make sure "%s" is available in ./%s' % (i, b)) + sys.exit(1) + +# make sure we have a test torrent +if not os.path.exists('test.torrent'): + print('generating test torrent') + # generate a 100 GB torrent, to make sure it won't all fit in physical RAM + os.system('./connection_tester gen-torrent -s 100000 -t test.torrent') + +# use a new port for each test to make sure they keep working +# this port is incremented for each test run +port = 10000 + random.randint(0, 40000) + +try: + os.mkdir('benchmark-dir') +except Exception: + pass + + +def clear_caches(): + if 'linux' in sys.platform: + os.system('sync') + try: + open('/proc/sys/vm/drop_caches', 'w').write('3') + except Exception: + pass + elif 'darwin' in sys.platform: + os.system('purge') + + +def build_utorrent_commandline(config, port): + num_peers = config['num-peers'] + torrent_path = config['torrent'] + target_folder = build_target_folder(config) + + try: + os.mkdir('utorrent_session') + except Exception: + pass + with open('utorrent_session/settings.dat', 'w+') as cfg: + + cfg.write('d') + cfg.write('20:ul_slots_per_torrenti%de' % num_peers) + cfg.write('17:conns_per_torrenti%de' % num_peers) + cfg.write('14:conns_globallyi%de' % num_peers) + cfg.write('9:bind_porti%de' % port) + cfg.write('19:dir_active_download%d:%s' % (len(config['save-path']), + config['save-path'])) + cfg.write('19:diskio.sparse_filesi1e') + cfg.write('14:cache.overridei1e') + cfg.write('19:cache.override_sizei%de' % int(config['cache-size'] * + 16 / 1024)) + cfg.write('17:dir_autoload_flagi1e') + cfg.write('12:dir_autoload8:autoload') + cfg.write('11:logger_maski4294967295e') + cfg.write('1:vi0e') + cfg.write('12:webui.enablei1e') + cfg.write('19:webui.enable_listeni1e') + cfg.write('14:webui.hashword20:' + hashlib.sha1( + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaadmin').digest()) + cfg.write('10:webui.porti8080e') + cfg.write('10:webui.salt32:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + cfg.write('14:webui.username5:admin') + cfg.write('e') + + try: + os.mkdir('utorrent_session/autoload') + except Exception: + pass + try: + shutil.copy(torrent_path, 'utorrent_session/autoload/') + except Exception: + pass + return './%s/utserver -logfile %s/client.log -settingspath ' % \ + (utorrent_version, target_folder) + \ + 'utorrent_session' + + +def build_rtorrent_commandline(config, port): + num_peers = config['num-peers'] + torrent_path = config['torrent'] + target_folder = build_target_folder(config) + + if os.path.exists(target_folder): + add_command = '' + else: + try: + os.mkdir(target_folder) + except Exception: + pass + # it seems rtorrent may delete the original torrent when it's being added + try: + shutil.copy(torrent_path, target_folder) + except Exception: + pass + add_command = '-O load_start_verbose=%s/%s ' % (target_folder, torrent_path) + + return ('rtorrent -d %s -n -p %d-%d -O max_peers=%d -O max_uploads=%d %s -s ' + '%s -O max_memory_usage=128000000000') % ( + config['save-path'], port, port, num_peers, num_peers, add_command, target_folder) + + +def build_libtorrent_commandline(config, port): + num_peers = config['num-peers'] + torrent_path = config['torrent'] + target_folder = build_target_folder(config) + + return ('./client_test -k -O -F 500 --enable_upnp=0 --enable_natpmp=0 ' + '--enable_dht=0 --mixed_mode_algorithm=0 --peer_timeout=%d ' + '--listen_queue_size=%d --unchoke_slots_limit=%d -T %d ' + '--connections_limit=%d --cache_size=%d -s "%s" ' + '--listen_interfaces="0.0.0.0:%d" --aio_threads=%d ' + '-f %s/client.log %s') % ( + test_duration, num_peers, num_peers, num_peers, num_peers, config['cache-size'], + config['save-path'], port, config['disk-threads'], target_folder, torrent_path) + + +def build_commandline(config, port): + + if config['build'] == 'utorrent': + return build_utorrent_commandline(config, port) + + if config['build'] == 'rtorrent': + return build_rtorrent_commandline(config, port) + + if config['build'] == 'libtorrent': + return build_libtorrent_commandline(config, port) + + +def delete_files(files): + for i in files: + print('deleting %s' % i) + try: + os.remove(i) + except Exception: + try: + shutil.rmtree(i) + except Exception: + try: + if os.path.exists(i): + print('failed to delete %s' % i) + except Exception: + pass + + +def build_test_config(num_peers=default_peers, cache_size=default_cache, + test='download', build='libtorrent', profile='', disk_threads=16, + torrent='test.torrent', disable_disk=False): + config = {'test': test, 'save-path': os.path.join('.', 'benchmark-dir'), 'num-peers': num_peers, + 'cache-size': cache_size, 'build': build, 'profile': profile, + 'disk-threads': disk_threads, 'torrent': torrent, 'disable-disk': disable_disk} + return config + + +def build_target_folder(config): + + no_disk = '' + if config['disable-disk']: + no_disk = '_no-disk' + + return 'results_%s_%s_%d_%d_%d%s' % (config['build'], + config['test'], + config['num-peers'], + config['cache-size'], + config['disk-threads'], + no_disk) + + +def find_library(name): + paths = ['/usr/lib64/', '/usr/local/lib64/', '/usr/lib/', '/usr/local/lib/'] + + for p in paths: + try: + if os.path.exists(p + name): + return p + name + except Exception: + pass + return name + + +def find_binary(names): + paths = ['/usr/bin/', '/usr/local/bin/'] + for n in names: + for p in paths: + try: + if os.path.exists(p + n): + return p + n + except Exception: + pass + return names[0] + + +def run_test(config): + + j = os.path.join + + target_folder = build_target_folder(config) + if os.path.exists(target_folder): + print('results already exists, skipping test (%s)' % target_folder) + return + + print('\n\n*********************************') + print('* RUNNING TEST *') + print('*********************************\n\n') + print('%s %s' % (config['build'], config['test'])) + + # make sure any previous test file is removed + # don't clean up unless we're running a download-test, so that we leave the test file + # complete for a seed test. + delete_files(['utorrent_session/settings.dat', 'utorrent_session/settings.dat.old', 'asserts.log']) + if config['test'] == 'download' or config['test'] == 'dual': + delete_files([j(config['save-path'], 'test'), + '.ses_state', + j(config['save-path'], '.resume'), + 'utorrent_session', + '.dht_state', + 'rtorrent_session']) + + try: + os.mkdir(target_folder) + except Exception: + pass + + # save off the command line for reference + global port + cmdline = build_commandline(config, port) + binary = cmdline.split(' ')[0] + environment = None + if config['profile'] == 'tcmalloc': + environment = {'LD_PRELOAD': find_library('libprofiler.so.0'), + 'CPUPROFILE': j(target_folder, 'cpu_profile.prof')} + if config['profile'] == 'memory': + environment = {'LD_PRELOAD': find_library('libprofiler.so.0'), + 'HEAPPROFILE': j(target_folder, 'heap_profile.prof')} + if config['profile'] == 'perf': + cmdline = 'perf record -g --output=' + \ + j(target_folder, 'perf_profile.prof') + ' ' + cmdline + with open(j(target_folder, 'cmdline.txt'), 'w+') as f: + f.write(cmdline) + + with open(j(target_folder, 'config.txt'), 'w+') as f: + print(config, file=f) + + print('clearing disk cache') + clear_caches() + print('OK') + client_output = open(j(target_folder, 'client.output'), 'w+') + client_error = open(j(target_folder, 'client.error'), 'w+') + print('launching: %s' % cmdline) + client = subprocess.Popen( + shlex.split(cmdline), + stdout=client_output, + stdin=subprocess.PIPE, + stderr=client_error, + env=environment) + print('OK') + # enable disk stats printing + if config['build'] == 'libtorrent': + print('x', end=' ', file=client.stdin) + time.sleep(4) + test_dir = 'upload' if config['test'] == 'download' else 'download' if config['test'] == 'upload' else 'dual' + cmdline = './connection_tester %s -c %d -d 127.0.0.1 -p %d -t %s' % ( + test_dir, config['num-peers'], port, config['torrent']) + print('launching: %s' % cmdline) + tester_output = open(j(target_folder, 'tester.output'), 'w+') + tester = subprocess.Popen(shlex.split(cmdline), stdout=tester_output) + print('OK') + + time.sleep(2) + + print('\n') + i = 0 + while True: + time.sleep(1) + tester.poll() + if tester.returncode is not None: + print('tester terminated') + break + client.poll() + if client.returncode is not None: + print('client terminated') + break + print('\r%d / %d\x1b[K' % (i, test_duration), end=' ') + sys.stdout.flush() + i += 1 + # in download- and dual tests, connection_tester will exit once the + # client is done downloading. In upload tests, we'll upload for + # 'test_duration' number of seconds until we end the test + if config['test'] != 'download' and config['test'] != 'dual' and i >= test_duration: + break + print('\n') + + if client.returncode is None: + try: + print('killing client') + client.send_signal(signal.SIGINT) + except Exception: + pass + + time.sleep(10) + client.wait() + tester.wait() + tester_output.close() + client_output.close() + terminate = False + if tester.returncode != 0: + print('tester returned %d' % tester.returncode) + terminate = True + if client.returncode != 0: + print('client returned %d' % client.returncode) + terminate = True + + try: + shutil.copy('asserts.log', target_folder) + except Exception: + pass + + os.chdir(target_folder) + + if config['build'] == 'libtorrent': + # parse session stats + print('parsing session log') + os.system('python ../../tools/parse_session_stats.py client.log') + + os.chdir('..') + + if config['profile'] == 'tcmalloc': + print('analyzing CPU profile [%s]' % binary) + os.system('%s --pdf %s %s/cpu_profile.prof >%s/cpu_profile.pdf' % + (find_binary(['google-pprof', 'pprof']), binary, target_folder, target_folder)) + if config['profile'] == 'memory': + for i in range(1, 300): + profile = j(target_folder, 'heap_profile.prof.%04d.heap' % i) + try: + os.stat(profile) + except Exception: + break + print('analyzing heap profile [%s] %d' % (binary, i)) + os.system('%s --pdf %s %s >%s/heap_profile_%d.pdf' % + (find_binary(['google-pprof', 'pprof']), binary, profile, target_folder, i)) + if config['profile'] == 'perf': + print('analyzing CPU profile [%s]' % binary) + os.system(('perf report --input=%s/perf_profile.prof --threads --demangle --show-nr-samples ' + '>%s/profile.txt' % (target_folder, target_folder))) + + port += 1 + + if terminate: + sys.exit(1) + + +for b in builds: + for test in ['upload', 'download', 'dual']: + config = build_test_config(build=b, test=test, profile='perf') + run_test(config) + +for p in peers: + for test in ['upload', 'download', 'dual']: + config = build_test_config(num_peers=p, test=test, profile='perf') + run_test(config) diff --git a/examples/session_view.cpp b/examples/session_view.cpp new file mode 100644 index 0000000..cd8804f --- /dev/null +++ b/examples/session_view.cpp @@ -0,0 +1,162 @@ +/* + +Copyright (c) 2018, Alden Torres +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "session_view.hpp" +#include "print.hpp" +#include "libtorrent/torrent_handle.hpp" + +#include // for std::max + +using lt::span; + +session_view::session_view() +{ + std::vector metrics = lt::session_stats_metrics(); + m_cnt[0].resize(metrics.size(), 0); + m_cnt[1].resize(metrics.size(), 0); +} + +void session_view::set_pos(int pos) +{ + m_position = pos; +} + +void session_view::set_width(int width) +{ + m_width = width; +} + +int session_view::pos() const { return m_position; } + +int session_view::height() const +{ + return 3; +} + +std::int64_t session_view::value(int idx) const +{ + if (idx < 0) return 0; + return m_cnt[0][std::size_t(idx)]; +} + +std::int64_t session_view::prev_value(int idx) const +{ + if (idx < 0) return 0; + return m_cnt[1][std::size_t(idx)]; +} + +void session_view::render() +{ + char str[1024]; + + int y = m_position; + + using std::chrono::duration_cast; + double const seconds = duration_cast(m_timestamp[0] - m_timestamp[1]).count() / 1000.0; + + int const download_rate = int((value(m_recv_idx) - prev_value(m_recv_idx)) + / seconds); + int const upload_rate = int((value(m_sent_idx) - prev_value(m_sent_idx)) + / seconds); + + std::snprintf(str, sizeof(str), "%s%s fail: %s down: %s (%s) " + " bw queue: %s | %s conns: %3d unchoked: %2d / %2d queued-trackers: %02d%*s\x1b[K" + , esc("48;5;238") + , esc("1") + , add_suffix(value(m_failed_bytes_idx)).c_str() + , color(add_suffix(download_rate, "/s"), col_green).c_str() + , color(add_suffix(value(m_recv_idx)), col_green).c_str() + , color(to_string(int(value(m_limiter_up_queue_idx)), 3), col_red).c_str() + , color(to_string(int(value(m_limiter_down_queue_idx)), 3), col_green).c_str() + , int(value(m_num_peers_idx)) + , int(value(m_unchoked_idx)) + , int(value(m_unchoke_slots_idx)) + , int(value(m_queued_tracker_announces)) + , std::max(0, m_width - 86) + , esc("0")); + + set_cursor_pos(0, y++); + print(str); + + std::snprintf(str, sizeof(str), "%s%swaste: %s up: %s (%s) " + "disk queue: %s | %s cache w: %3d%% total: %s %*s\x1b[K" +#ifdef _WIN32 + , esc("40") +#else + , esc("48;5;238") +#endif + , esc("1") + , add_suffix(value(m_wasted_bytes_idx)).c_str() + , color(add_suffix(upload_rate, "/s"), col_red).c_str() + , color(add_suffix(value(m_sent_idx)), col_red).c_str() + , color(to_string(int(value(m_queued_reads_idx)), 3), col_red).c_str() + , color(to_string(int(value(m_queued_writes_idx)), 3), col_green).c_str() + , int((value(m_blocks_written_idx) - value(m_write_ops_idx)) * 100 + / std::max(std::int64_t(1), value(m_blocks_written_idx))) + , add_suffix(value(m_blocks_in_use_idx) * 16 * 1024).c_str() + , std::max(0, m_width - 85) + , esc("0")); + set_cursor_pos(0, y++); + print(str); + + std::snprintf(str, sizeof(str), "%s%suTP idle: %d syn: %d est: %d fin: %d wait: %d%*s\x1b[K" + , esc("48;5;238") + , esc("1") + , int(value(m_utp_idle)) + , int(value(m_utp_syn_sent)) + , int(value(m_utp_connected)) + , int(value(m_utp_fin_sent)) + , int(value(m_utp_close_wait)) + , int(m_width - 37) + , esc("0")); + set_cursor_pos(0, y++); + print(str); +} + +void session_view::update_counters(span stats_counters + , lt::clock_type::time_point const t) +{ + // only update the previous counters if there's been enough + // time since it was last updated + if (t - m_timestamp[1] > lt::seconds(2)) + { + m_cnt[1].swap(m_cnt[0]); + m_timestamp[1] = m_timestamp[0]; + } + + m_cnt[0].assign(stats_counters.begin(), stats_counters.end()); + m_timestamp[0] = t; + render(); +} + diff --git a/examples/session_view.hpp b/examples/session_view.hpp new file mode 100644 index 0000000..caa3d4f --- /dev/null +++ b/examples/session_view.hpp @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2014, 2016-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SESSION_VIEW_HPP_ +#define SESSION_VIEW_HPP_ + +#include +#include + +#include "libtorrent/session_stats.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/time.hpp" + +struct session_view +{ + session_view(); + + void set_pos(int pos); + void set_width(int width); + + int pos() const; + + int height() const; + + void render(); + + void update_counters(lt::span stats_counters, lt::clock_type::time_point t); + +private: + + int m_position = 0; + int m_width = 80; + + // there are two sets of counters. the current one and the last one. This + // is used to calculate rates + std::vector m_cnt[2]; + + std::int64_t value(int idx) const; + std::int64_t prev_value(int idx) const; + + // the timestamps of the counters in m_cnt[0] and m_cnt[1] + // respectively. + lt::clock_type::time_point m_timestamp[2]; + + int const m_queued_bytes_idx = lt::find_metric_idx("disk.queued_write_bytes"); + int const m_wasted_bytes_idx = lt::find_metric_idx("net.recv_redundant_bytes"); + int const m_failed_bytes_idx = lt::find_metric_idx("net.recv_failed_bytes"); + int const m_num_peers_idx = lt::find_metric_idx("peer.num_peers_connected"); + int const m_recv_idx = lt::find_metric_idx("net.recv_bytes"); + int const m_sent_idx = lt::find_metric_idx("net.sent_bytes"); + int const m_unchoked_idx = lt::find_metric_idx("peer.num_peers_up_unchoked"); + int const m_unchoke_slots_idx = lt::find_metric_idx("ses.num_unchoke_slots"); + int const m_limiter_up_queue_idx = lt::find_metric_idx("net.limiter_up_queue"); + int const m_limiter_down_queue_idx = lt::find_metric_idx("net.limiter_down_queue"); + int const m_queued_writes_idx = lt::find_metric_idx("disk.num_write_jobs"); + int const m_queued_reads_idx = lt::find_metric_idx("disk.num_read_jobs"); + + int const m_num_blocks_read_idx = lt::find_metric_idx("disk.num_blocks_read"); + int const m_blocks_in_use_idx = lt::find_metric_idx("disk.disk_blocks_in_use"); + int const m_blocks_written_idx = lt::find_metric_idx("disk.num_blocks_written"); + int const m_write_ops_idx = lt::find_metric_idx("disk.num_write_ops"); + + int const m_utp_idle = lt::find_metric_idx("utp.num_utp_idle"); + int const m_utp_syn_sent = lt::find_metric_idx("utp.num_utp_syn_sent"); + int const m_utp_connected = lt::find_metric_idx("utp.num_utp_connected"); + int const m_utp_fin_sent = lt::find_metric_idx("utp.num_utp_fin_sent"); + int const m_utp_close_wait = lt::find_metric_idx("utp.num_utp_close_wait"); + + int const m_queued_tracker_announces = lt::find_metric_idx("tracker.num_queued_tracker_announces"); +}; + +#endif + diff --git a/examples/simple_client.cpp b/examples/simple_client.cpp new file mode 100644 index 0000000..041711d --- /dev/null +++ b/examples/simple_client.cpp @@ -0,0 +1,63 @@ +/* + +Copyright (c) 2003, 2005, 2009, 2015-2017, 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/torrent_info.hpp" + +#include + +int main(int argc, char* argv[]) try +{ + if (argc != 2) { + std::cerr << "usage: ./simple_client torrent-file\n" + "to stop the client, press return.\n"; + return 1; + } + + lt::session s; + lt::add_torrent_params p; + p.save_path = "."; + p.ti = std::make_shared(argv[1]); + s.add_torrent(p); + + // wait for the user to end + char a; + int ret = std::scanf("%c\n", &a); + (void)ret; // ignore + return 0; +} +catch (std::exception const& e) { + std::cerr << "ERROR: " << e.what() << "\n"; +} diff --git a/examples/stats_counters.cpp b/examples/stats_counters.cpp new file mode 100644 index 0000000..9f21c42 --- /dev/null +++ b/examples/stats_counters.cpp @@ -0,0 +1,50 @@ +/* + +Copyright (c) 2010, 2014-2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session_stats.hpp" +#include // for snprintf +#include // for PRId64 et.al. + +using namespace lt; + +int main() +{ + std::vector m = session_stats_metrics(); + for (auto const& c : m) + { + std::printf("%s: %s (%d)\n" + , c.type == metric_type_t::counter ? "CNTR" : "GAUG" + , c.name, c.value_index); + } + return 0; +} + diff --git a/examples/torrent2magnet.cpp b/examples/torrent2magnet.cpp new file mode 100644 index 0000000..ff24ddf --- /dev/null +++ b/examples/torrent2magnet.cpp @@ -0,0 +1,95 @@ +/* + +Copyright (c) 2019-2020, 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include + +#include "libtorrent/load_torrent.hpp" +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/load_torrent.hpp" + +namespace { + +[[noreturn]] void print_usage() +{ + std::cerr << R"(usage: torrent2magnet torrent-file [options] + OPTIONS: + --no-trackers do not include trackers in the magnet link + --no-web-seeds do not include web seeds in the magnet link +)"; + std::exit(1); +} + +} // anonymous namespace + +int main(int argc, char const* argv[]) try +{ + lt::span args(argv, argc); + + // strip executable name + args = args.subspan(1); + + if (args.empty()) print_usage(); + + char const* filename = args[0]; + args = args.subspan(1); + + lt::add_torrent_params atp = lt::load_torrent_file(filename); + + using namespace lt::literals; + + while (!args.empty()) + { + if (args[0] == "--no-trackers"_sv) + { + atp.trackers.clear(); + } + else if (args[0] == "--no-web-seeds"_sv) + { + atp.url_seeds.clear(); + atp.http_seeds.clear(); + } + else + { + std::cerr << "unknown option: " << args[0] << "\n"; + print_usage(); + } + args = args.subspan(1); + } + + std::cout << lt::make_magnet_uri(atp) << '\n'; + return 0; +} +catch (std::exception const& e) +{ + std::cerr << "ERROR: " << e.what() << "\n"; +} diff --git a/examples/torrent_view.cpp b/examples/torrent_view.cpp new file mode 100644 index 0000000..38ace62 --- /dev/null +++ b/examples/torrent_view.cpp @@ -0,0 +1,570 @@ +/* + +Copyright (c) 2014-2021, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2017, AllSeeingEyeTolledEweSew +Copyright (c) 2018, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "torrent_view.hpp" +#include "print.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/torrent_status.hpp" +#include "libtorrent/torrent_info.hpp" + +#include + +namespace { + +const int header_size = 2; +using lt::queue_position_t; + +std::string torrent_state(lt::torrent_status const& s) +{ + static char const* state_str[] = + {"checking (q)", "checking", "dl metadata" + , "downloading", "finished", "seeding", "", "checking (r)"}; + + if (s.errc) return s.errc.message(); + std::string ret; + if ((s.flags & lt::torrent_flags::paused) && + (s.flags & lt::torrent_flags::auto_managed)) + { + ret += "queued "; + } + + if (s.state == lt::torrent_status::downloading + && (s.flags & lt::torrent_flags::upload_mode)) + ret += "upload mode"; + else + ret += state_str[s.state]; + + if (!(s.flags & lt::torrent_flags::auto_managed)) + { + if (s.flags & lt::torrent_flags::paused) + ret += " [P]"; + else + ret += " [F]"; + } + if (s.flags & lt::torrent_flags::i2p_torrent) + ret += " i2p"; + if (s.state == lt::torrent_status::seeding) + { + ret += " "; + ret += add_suffix(s.total_done); + } + else + { + char buf[20]; + std::snprintf(buf, sizeof(buf), " (%.1f%%)", s.progress_ppm / 10000.0); + ret += buf; + } + return ret; +} + +bool cmp_torrent_position(lt::torrent_status const* lhs, lt::torrent_status const* rhs) +{ + if (lhs->queue_position != queue_position_t{-1} && rhs->queue_position != queue_position_t{-1}) + { + // both are downloading, sort by queue pos + return lhs->queue_position < rhs->queue_position; + } + else if (lhs->queue_position == queue_position_t{-1} + && rhs->queue_position == queue_position_t{-1}) + { + // both are seeding, sort by seed-rank + if (lhs->seed_rank != rhs->seed_rank) + return lhs->seed_rank > rhs->seed_rank; + + return lhs->info_hashes < rhs->info_hashes; + } + + return (lhs->queue_position == queue_position_t{-1}) + < (rhs->queue_position == queue_position_t{-1}); +} + +bool cmp_torrent_name(lt::torrent_status const* lhs, lt::torrent_status const* rhs) +{ + return lhs->name < rhs->name; +} + +bool cmp_torrent_size(lt::torrent_status const* lhs, lt::torrent_status const* rhs) +{ + return lhs->total_done > rhs->total_done; +} + +} + +torrent_view::torrent_view() = default; + +void torrent_view::set_size(int width, int height) +{ + if (m_width == width && m_height == height) return; + + m_width = width; + m_height = height; + render(); +} + +int torrent_view::filter() const +{ + return m_torrent_filter; +} + +void torrent_view::set_filter(int filter) +{ + if (filter == m_torrent_filter) return; + m_torrent_filter = filter; + + update_filtered_torrents(); + render(); +} + +int torrent_view::sort_order() const +{ + return m_sort_order; +} + +void torrent_view::set_sort_order(int const o) +{ + if (o == m_sort_order) return; + m_sort_order = order(o); + + update_sort_order(); + render(); +} + +// returns the lt::torrent_status of the currently selected torrent. +lt::torrent_status const& torrent_view::get_active_torrent() const +{ + if (m_active_torrent >= int(m_filtered_handles.size())) + m_active_torrent = int(m_filtered_handles.size()) - 1; + if (m_active_torrent < 0) m_active_torrent = 0; + TORRENT_ASSERT(m_active_torrent >= 0); + + return *m_filtered_handles[std::size_t(m_active_torrent)]; +} + +lt::torrent_handle torrent_view::get_active_handle() const +{ + if (m_active_torrent >= int(m_filtered_handles.size())) + m_active_torrent = int(m_filtered_handles.size()) - 1; + if (m_active_torrent < 0) m_active_torrent = 0; + TORRENT_ASSERT(m_active_torrent >= 0); + + if (m_filtered_handles.empty()) return lt::torrent_handle(); + + return m_filtered_handles[std::size_t(m_active_torrent)]->handle; +} + +void torrent_view::remove_torrent(lt::torrent_handle h) +{ + auto i = m_all_handles.find(h); + if (i == m_all_handles.end()) return; + bool need_rerender = false; + if (show_torrent(i->second)) + { + auto j = std::find(m_filtered_handles.begin(), m_filtered_handles.end(), &i->second); + if (j != m_filtered_handles.end()) + { + m_filtered_handles.erase(j); + need_rerender = true; + } + } + m_all_handles.erase(i); + if (need_rerender) render(); +} + +void torrent_view::update_torrents(std::vector st) +{ + std::set updates; + bool need_filter_update = false; + for (lt::torrent_status& t : st) + { + auto j = m_all_handles.find(t.handle); + // add new entries here + if (j == m_all_handles.end()) + { + auto handle = t.handle; + j = m_all_handles.emplace(handle, std::move(t)).first; + if (show_torrent(j->second)) + { + m_filtered_handles.push_back(&j->second); + need_filter_update = true; + } + } + else + { + bool const prev_show = show_torrent(j->second); + j->second = std::move(t); + if (prev_show != show_torrent(j->second)) + need_filter_update = true; + else + updates.insert(j->second.handle); + } + } + if (need_filter_update) + { + update_filtered_torrents(); + render(); + } + else + { + int torrent_index = 0; + for (auto i = m_filtered_handles.begin(); + i != m_filtered_handles.end(); ++i) + { + if (torrent_index < m_scroll_position + || torrent_index >= m_scroll_position + m_height - header_size) + { + ++torrent_index; + continue; + } + + lt::torrent_status const& s = **i; + + if (!s.handle.is_valid()) + continue; + + if (updates.count(s.handle) == 0) + { + ++torrent_index; + continue; + } + + set_cursor_pos(0, header_size + torrent_index - m_scroll_position); + print_torrent(s, torrent_index == m_active_torrent); + ++torrent_index; + } + } +} + +int torrent_view::height() const +{ + return m_height; +} + +void torrent_view::arrow_up() +{ + if (m_filtered_handles.empty()) return; + if (m_active_torrent <= 0) return; + + if (m_active_torrent - 1 < m_scroll_position) + { + int const scroll_step = std::max(m_height / 3, 8); + --m_active_torrent; + m_scroll_position = std::max(0, m_active_torrent - scroll_step); + TORRENT_ASSERT(m_scroll_position >= 0); + render(); + return; + } + + set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position); + print_torrent(*m_filtered_handles[std::size_t(m_active_torrent)], false); + --m_active_torrent; + TORRENT_ASSERT(m_active_torrent >= 0); + + set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position); + print_torrent(*m_filtered_handles[std::size_t(m_active_torrent)], true); +} + +void torrent_view::arrow_down() +{ + if (m_filtered_handles.empty()) return; + int const max_pos = int(m_filtered_handles.size()) - 1; + if (m_active_torrent >= max_pos) return; + + int bottom_pos = m_height - header_size - 1; + if (m_active_torrent - m_scroll_position + 1 > bottom_pos) + { + int const scroll_step = std::max(m_height / 3, 8); + ++m_active_torrent; + m_scroll_position = std::min(max_pos, m_active_torrent + scroll_step) - bottom_pos; + TORRENT_ASSERT(m_scroll_position >= 0); + render(); + return; + } + + set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position); + print_torrent(*m_filtered_handles[std::size_t(m_active_torrent)], false); + + TORRENT_ASSERT(m_active_torrent >= 0); + ++m_active_torrent; + + set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position); + print_torrent(*m_filtered_handles[std::size_t(m_active_torrent)], true); +} + +void torrent_view::render() +{ + print_tabs(); + print_headers(); + + int lines_printed = header_size; + + int torrent_index = 0; + + for (std::vector::iterator i = m_filtered_handles.begin(); + i != m_filtered_handles.end();) + { + if (torrent_index < m_scroll_position) + { + ++i; + ++torrent_index; + continue; + } + if (lines_printed >= m_height) + break; + + lt::torrent_status const& s = **i; + if (!s.handle.is_valid()) + { + i = m_filtered_handles.erase(i); + continue; + } + ++i; + + set_cursor_pos(0, torrent_index + header_size - m_scroll_position); + print_torrent(s, torrent_index == m_active_torrent); + ++lines_printed; + ++torrent_index; + } + + clear_rows(torrent_index + header_size, m_height); +} + +void torrent_view::print_tabs() +{ + set_cursor_pos(0, 0); + + std::array str; + lt::span dest(str); + static std::array const filter_names{{ "all", "downloading", "non-paused" + , "seeding", "queued", "stopped", "checking"}}; + for (int i = 0; i < int(filter_names.size()); ++i) + { + int const ret = std::snprintf(dest.data(), std::size_t(dest.size()), "%s[%s]%s" + , m_torrent_filter == i?esc("7"):"" + , filter_names[std::size_t(i)], m_torrent_filter == i?esc("0"):""); + if (ret >= 0 && ret <= dest.size()) dest = dest.subspan(ret); + } + int const ret = std::snprintf(dest.data(), std::size_t(dest.size()), "\x1b[K"); + if (ret >= 0 && ret <= dest.size()) dest = dest.subspan(ret); + + if (m_width + 1 < int(str.size())) + str.back() = '\0'; + print(str.data()); +} + +namespace { + +struct column_info +{ + char const* name; + int width; + int sort_order; +}; + +std::array const torrent_columns = {{ + {"#", 3, 0}, + {"Name", 50, 1}, + {"Progress", 35, -1}, + {"Pieces", 14, 2}, + {"Download", 17, -1}, + {"Upload", 17, -1}, + {"Peers (D:S)", 11, -1}, + {"Down", 6, -1}, + {"Up", 6, -1}, + {"Flags", 4, -1}, +}}; + +} + +void torrent_view::print_headers() +{ + set_cursor_pos(0, 1); + + // print title bar for torrent list + std::array str; + int cursor = 0; + for (auto const& ci : torrent_columns) + { + if (ci.sort_order == m_sort_order) + { + cursor += std::snprintf(str.data() + cursor, str.size() - std::size_t(cursor), "\x1b[7m"); + if (std::size_t(cursor) > str.size()) break; + } + cursor += std::snprintf(str.data() + cursor, str.size() - std::size_t(cursor), "%-*s ", ci.width, ci.name); + if (std::size_t(cursor) > str.size()) break; + if (ci.sort_order == m_sort_order) + { + cursor += std::snprintf(str.data() + cursor, str.size() - std::size_t(cursor), "\x1b[0m"); + if (std::size_t(cursor) > str.size()) break; + } + } + cursor += std::snprintf(str.data() + cursor, str.size() - std::size_t(cursor), "\x1b[K"); + + str.back() = '\0'; + + print(str.data()); +} + +void torrent_view::print_torrent(lt::torrent_status const& s, bool selected) +{ + std::array str; + lt::span dest(str); + + // the active torrent is highlighted in the list + // this inverses the foreground and background colors + char const* selection = ""; + if (selected) + selection = "\x1b[1m\x1b[44m"; + + char queue_pos[16] = {0}; + if (s.queue_position == queue_position_t{-1}) + std::snprintf(queue_pos, sizeof(queue_pos), "-"); + else + std::snprintf(queue_pos, sizeof(queue_pos), "%d" + , static_cast(s.queue_position)); + + std::string name = s.name; + if (name.size() > 50) name.resize(50); + + color_code progress_bar_color = col_yellow; + if (s.errc) progress_bar_color = col_red; + else if (s.flags & lt::torrent_flags::paused) progress_bar_color = col_blue; + else if (s.state == lt::torrent_status::downloading_metadata) + progress_bar_color = col_magenta; + else if (s.current_tracker.empty()) + progress_bar_color = col_green; + + auto ti = s.torrent_file.lock(); + int const total_pieces = ti && ti->is_valid() ? ti->num_pieces() : 0; + color_code piece_color = total_pieces == s.num_pieces ? col_green : col_yellow; + + int const ret = std::snprintf(dest.data(), std::size_t(dest.size()), "%s%-3s %-50s %s%s %s/%s %s (%s) " + "%s (%s) %5d:%-5d %s %s %c" + , selection + , queue_pos + , name.c_str() + , progress_bar(s.progress_ppm / 1000, 35, progress_bar_color, '-', '#', torrent_state(s)).c_str() + , selection + , color(to_string(s.num_pieces, 6), piece_color).c_str() + , color(to_string(total_pieces, 6), piece_color).c_str() + , color(add_suffix(s.download_rate, "/s"), col_green).c_str() + , color(add_suffix(s.total_download), col_green).c_str() + , color(add_suffix(s.upload_rate, "/s"), col_red).c_str() + , color(add_suffix(s.total_upload), col_red).c_str() + , s.num_peers - s.num_seeds, s.num_seeds + , color(add_suffix(s.all_time_download), col_green).c_str() + , color(add_suffix(s.all_time_upload), col_red).c_str() + , s.need_save_resume?'S':' '); + if (ret >= 0 && ret <= dest.size()) dest = dest.subspan(ret); + + // if this is the selected torrent, restore the background color + if (selected) + { + int const ret2 = std::snprintf(dest.data(), std::size_t(dest.size()), "%s", esc("0")); + if (ret2 >= 0 && ret2 <= dest.size()) dest = dest.subspan(ret2); + } + + int const ret2 = std::snprintf(dest.data(), std::size_t(dest.size()), "\x1b[K"); + if (ret2 >= 0 && ret2 <= dest.size()) dest = dest.subspan(ret2); + + print(str.data()); +} + +bool torrent_view::show_torrent(lt::torrent_status const& st) +{ + switch (m_torrent_filter) + { + case torrents_all: return true; + case torrents_downloading: + return !(st.flags & lt::torrent_flags::paused) + && st.state != lt::torrent_status::seeding + && st.state != lt::torrent_status::finished; + case torrents_not_paused: + return !(st.flags & lt::torrent_flags::paused); + case torrents_seeding: + return !(st.flags & lt::torrent_flags::paused) + && (st.state == lt::torrent_status::seeding + || st.state == lt::torrent_status::finished); + case torrents_queued: + return (st.flags & lt::torrent_flags::paused) + && (st.flags & lt::torrent_flags::auto_managed); + case torrents_stopped: + return (st.flags & lt::torrent_flags::paused) + && !(st.flags & lt::torrent_flags::auto_managed); + case torrents_checking: return st.state == lt::torrent_status::checking_files; + } + return true; +} + +// refresh all pointers in m_filtered_handles. This must be done when +// inserting or removing elements from m_all_handles, since pointers may +// be invalidated or when a torrent changes status to either become +// visible or filtered +void torrent_view::update_filtered_torrents() +{ + m_filtered_handles.clear(); + for (auto const& h : m_all_handles) + { + if (!show_torrent(h.second)) continue; + m_filtered_handles.push_back(&h.second); + } + if (m_active_torrent >= int(m_filtered_handles.size())) m_active_torrent = int(m_filtered_handles.size()) - 1; + if (m_active_torrent < 0) m_active_torrent = 0; + TORRENT_ASSERT(m_active_torrent >= 0); + + update_sort_order(); + if (m_scroll_position + m_height - header_size > int(m_filtered_handles.size())) + { + m_scroll_position = std::max(0, int(m_filtered_handles.size()) - m_height + header_size); + } +} + + +void torrent_view::update_sort_order() +{ + switch (m_sort_order) + { + case order::queue: + std::sort(m_filtered_handles.begin(), m_filtered_handles.end(), &cmp_torrent_position); + break; + case order::name: + std::sort(m_filtered_handles.begin(), m_filtered_handles.end(), &cmp_torrent_name); + break; + case order::size: + std::sort(m_filtered_handles.begin(), m_filtered_handles.end(), &cmp_torrent_size); + break; + } +} diff --git a/examples/torrent_view.hpp b/examples/torrent_view.hpp new file mode 100644 index 0000000..12e1ba9 --- /dev/null +++ b/examples/torrent_view.hpp @@ -0,0 +1,126 @@ +/* + +Copyright (c) 2014-2015, 2017-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_VIEW_HPP_ +#define TORRENT_VIEW_HPP_ + +#include +#include +#include + +#include "libtorrent/fwd.hpp" +#include "libtorrent/torrent_status.hpp" + + +struct torrent_view +{ + torrent_view(); + + void set_size(int width, int height); + + // torrent filter + enum { + torrents_all, + torrents_downloading, + torrents_not_paused, + torrents_seeding, + torrents_queued, + torrents_stopped, + torrents_checking, + + torrents_max + }; + + // sort order + enum order: std::uint8_t { + queue, + name, + size, + }; + + int filter() const; + void set_filter(int filter); + + int sort_order() const; + void set_sort_order(int); + + // returns the lt::torrent_status of the currently selected torrent. + lt::torrent_status const& get_active_torrent() const; + lt::torrent_handle get_active_handle() const; + + void remove_torrent(lt::torrent_handle st); + void update_torrents(std::vector st); + int num_visible_torrents() const { return int(m_filtered_handles.size()); } + + int height() const; + + void arrow_up(); + void arrow_down(); + + void render(); + +private: + + void print_tabs(); + + void print_headers(); + + void print_torrent(lt::torrent_status const& s, bool selected); + + bool show_torrent(lt::torrent_status const& st); + + // refresh all pointers in m_filtered_handles. This must be done when + // inserting or removing elements from m_all_handles, since pointers may + // be invalidated or when a torrent changes status to either become + // visible or filtered + void update_filtered_torrents(); + + // re-sorts m_filtered_handles based on m_sort_order + void update_sort_order(); + + // all torrents + std::unordered_map m_all_handles; + + // pointers into m_all_handles of the remaining torrents after filtering + std::vector m_filtered_handles; + + mutable int m_active_torrent = 0; // index into m_filtered_handles + int m_scroll_position = 0; + int m_torrent_filter = 0; + order m_sort_order = order::queue; + int m_width = 80; + int m_height = 30; +}; + +#endif // TORRENT_VIEW_HPP_ + diff --git a/examples/upnp_test.cpp b/examples/upnp_test.cpp new file mode 100644 index 0000000..76ce783 --- /dev/null +++ b/examples/upnp_test.cpp @@ -0,0 +1,108 @@ +/* + +Copyright (c) 2010, 2014-2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include "libtorrent/session.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/alert_types.hpp" + +namespace { +void print_alert(lt::alert const* a) +{ + using namespace lt; + + if (alert_cast(a)) + { + std::printf("%s","\x1b[32m"); + } + else if (alert_cast(a)) + { + std::printf("%s","\x1b[33m"); + } + + std::printf("%s\n", a->message().c_str()); + std::printf("%s", "\x1b[0m"); +} +} // anonymous namespace + +int main(int argc, char*[]) +{ + using namespace lt; + + if (argc != 1) + { + fputs("usage: ./upnp_test\n", stderr); + return 1; + } + + settings_pack p; + p.set_int(settings_pack::alert_mask, alert_category::port_mapping); + lt::session s(p); + + for (;;) + { + alert const* a = s.wait_for_alert(seconds(5)); + if (a == nullptr) + { + p.set_bool(settings_pack::enable_upnp, false); + p.set_bool(settings_pack::enable_natpmp, false); + s.apply_settings(p); + break; + } + std::vector alerts; + s.pop_alerts(&alerts); + for (std::vector::iterator i = alerts.begin() + , end(alerts.end()); i != end; ++i) + { + print_alert(*i); + } + } + + std::printf("\x1b[1m\n\n===================== done mapping. Now deleting mappings ========================\n\n\n\x1b[0m"); + + for (;;) + { + alert const* a = s.wait_for_alert(seconds(5)); + if (a == nullptr) break; + std::vector alerts; + s.pop_alerts(&alerts); + for (std::vector::iterator i = alerts.begin() + , end(alerts.end()); i != end; ++i) + { + print_alert(*i); + } + } + + + return 0; +} + diff --git a/fuzzers/Jamfile b/fuzzers/Jamfile new file mode 100644 index 0000000..716ce55 --- /dev/null +++ b/fuzzers/Jamfile @@ -0,0 +1,102 @@ +# to fuzz libtorrent, you need a recent version of clang. + +# if you have a favourite component to fuzz, you can run that specific binary +# without specifying the "-runs=" argument, it's probably a good idea to seed +# the fuzzing with the included corpus though + +import feature : feature ; + +use-project /torrent : .. ; + +feature fuzz : off external on : composite propagated link-incompatible ; +feature.compose on : -fsanitize=fuzzer -fsanitize=fuzzer ; + +feature sanitize : off on : composite propagated link-incompatible ; +feature.compose on : norecover norecover ; + +# this is a build configuration that only does limited validation (i.e. no +# sanitizers, invariant-checks, asserts etc.). The purpose is to quickly iterate +# on inputs to build code coverage +variant build_coverage : release : off on off off ; + +project fuzzers + : requirements + on + TORRENT_USE_IPV6=1 + _SCL_SECURE=1 + _GLIBCXX_DEBUG + -fno-omit-frame-pointer + -fno-omit-frame-pointer + /torrent//torrent + : default-build + 14 + on + multi + on + static + release + on + on + on + on + on + ; + +local TARGETS ; + +rule fuzzer ( name ) +{ + exe $(name) : src/$(name).cpp : off:main.cpp ; + TARGETS += $(name) ; +} + +fuzzer torrent_info ; +fuzzer parse_magnet_uri ; +fuzzer bdecode_node ; +fuzzer parse_int ; +fuzzer sanitize_path ; +fuzzer escape_path ; +fuzzer file_storage_add_file ; +fuzzer base32decode ; +fuzzer base32encode ; +fuzzer base64encode ; +fuzzer escape_string ; +fuzzer gzip ; +fuzzer verify_encoding ; +fuzzer convert_to_native ; +fuzzer convert_from_native ; +fuzzer utf8_codepoint ; +fuzzer http_parser ; +fuzzer upnp ; +fuzzer dht_node ; +fuzzer utp ; +fuzzer resume_data ; +fuzzer peer_conn ; +fuzzer idna ; +fuzzer parse_url ; +fuzzer http_tracker ; +fuzzer session_params ; +fuzzer add_torrent ; + +local LARGE_TARGETS = + torrent_info + bdecode_node + http_parser + dht_node + utp + resume_data + file_storage_add_file + sanitize_path + upnp + peer_conn + http_tracker + session_params + add_torrent + ; + +install stage : $(TARGETS) : EXE fuzzers ; +install stage-large : $(LARGE_TARGETS) : EXE fuzzers ; + +explicit stage ; +explicit stage-large ; + diff --git a/fuzzers/LICENSE b/fuzzers/LICENSE new file mode 100644 index 0000000..8778d7c --- /dev/null +++ b/fuzzers/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/fuzzers/README.rst b/fuzzers/README.rst new file mode 100644 index 0000000..f973b1f --- /dev/null +++ b/fuzzers/README.rst @@ -0,0 +1,64 @@ +libtorrent fuzzing +================== + +Fuzzing of various libtorrent APIs (both internal and external), +inspired by Kostya Serebryany's `cppcon 2017 presentation`_ + +This project requires: + +.. _`cppcon 2017 presentation`: https://www.youtube.com/watch?v=k-Cv8Q3zWNQ&index=36&list=PLHTh1InhhwT6bwIpRk0ZbCA0N2p1taxd6 + +clang +..... + +A very recent version of clang that supports libFuzzer. +clang-5.0 may not be recent enough, you may have to build head from source. + +boost-build +........... + +Also known as ``b2``. To configure boost build with your fresh clang build, +create a ``~/user-config.jam`` with something like this in it (example for macOS):: + + using darwin : 6.0 : ~/Documents/dev/clang/build/bin/clang++ ; + +Or on Linux:: + + using clang ; + +corpus +...... + +The corpus is the set of inputs that has been built by libFuzzer. It's the seed +for testing more mutations. The corpus is not checked into the repository, +before running the fuzzer it is advised to download and unzip the corpus +associated with the latest release on github. + + https://github.com/arvidn/libtorrent/releases/download/libtorrent_1_2_0/corpus.zip + +Uzip the corpus in the fuzzers directory:: + + unzip corpus.zip + +building +........ + +To build the fuzzers:: + + b2 clang stage + +The fuzzers binaries are placed in a directory called `fuzzers`. + +running +....... + +To run the fuzzers, there's a convenience `run.sh` script that launches all +fuzzers in parallel. By default, each fuzzer runs for 48 hours. This can be +adjusted in the `run.sh` script. + +contribute +.......... + +Please consider contributing back any updated corpuses (amended by more seed +inputs) or fuzzers for more APIs in libtorrent. + diff --git a/fuzzers/main.cpp b/fuzzers/main.cpp new file mode 100644 index 0000000..1d5d334 --- /dev/null +++ b/fuzzers/main.cpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ +#include +#include +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const*, size_t); + +int main(int const argc, char const** argv) +{ + if (argc < 2) + { + std::cout << "usage: " << argv[0] << " test-case-file\n"; + return 1; + } + + std::fstream f(argv[1], std::ios_base::in | std::ios_base::binary); + f.seekg(0, std::ios_base::end); + auto const s = f.tellg(); + f.seekg(0, std::ios_base::beg); + std::vector v(static_cast(s)); + f.read(reinterpret_cast(v.data()), v.size()); + + return LLVMFuzzerTestOneInput(v.data(), v.size()); +} + diff --git a/fuzzers/minimize.sh b/fuzzers/minimize.sh new file mode 100755 index 0000000..4141dbe --- /dev/null +++ b/fuzzers/minimize.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -e + +function minimize +{ +mkdir corpus/${1} +./fuzzers/${1} -artifact_prefix=./${1}- -merge=1 corpus/${1} prev-corpus/${1} +} + +mv corpus prev-corpus +mkdir corpus + +for file in fuzzers/*; do + minimize $(basename $file) & +done + +wait + diff --git a/fuzzers/run.sh b/fuzzers/run.sh new file mode 100755 index 0000000..d4bf924 --- /dev/null +++ b/fuzzers/run.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +function run +{ +# run for 48 hours +nice ./fuzzers/${1} -max_total_time=172800 -timeout=10 -artifact_prefix=./${1}- corpus/${1} +} + +for file in fuzzers/*; do + run $(basename $file) & +done + +wait diff --git a/fuzzers/src/add_torrent.cpp b/fuzzers/src/add_torrent.cpp new file mode 100644 index 0000000..a1deb1d --- /dev/null +++ b/fuzzers/src/add_torrent.cpp @@ -0,0 +1,243 @@ +/* + +Copyright (c) 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include + +#include "libtorrent/session.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/disk_interface.hpp" // for default_block_size +#include "libtorrent/aux_/merkle.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/disabled_disk_io.hpp" +#include "read_bits.hpp" + +using namespace lt; + +lt::session_params g_params; +io_context g_ioc; +std::shared_ptr g_torrent; +std::vector g_tree; + +int const piece_size = 1024 * 1024; +int const blocks_per_piece = piece_size / lt::default_block_size; +int const num_pieces = 10; +int const num_leafs = merkle_num_leafs(num_pieces * blocks_per_piece); +int const num_nodes = merkle_num_nodes(num_leafs); +int const first_leaf = merkle_first_leaf(num_leafs); + +extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + lt::settings_pack& pack = g_params.settings; + // set up settings pack we'll be using + pack.set_int(settings_pack::tick_interval, 1); + pack.set_int(settings_pack::alert_mask, 0); + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_disabled); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_disabled); + pack.set_int(settings_pack::aio_threads, 0); + + // don't waste time making outbound connections + pack.set_bool(settings_pack::enable_outgoing_tcp, false); + pack.set_bool(settings_pack::enable_outgoing_utp, false); + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_natpmp, false); + pack.set_bool(settings_pack::enable_dht, false); + pack.set_bool(settings_pack::enable_lsd, false); + pack.set_bool(settings_pack::enable_ip_notifier, false); + pack.set_str(settings_pack::listen_interfaces, "127.0.0.1:0"); + + g_params.disk_io_constructor = lt::disabled_disk_io_constructor; + + // create a torrent + file_storage fs; + std::int64_t const total_size = std::int64_t(piece_size) * num_pieces; + fs.add_file("test_file", total_size); + + g_tree.resize(num_nodes); + + create_torrent t(fs, piece_size); + + std::vector piece(piece_size, 0); + lt::span piece_span(piece); + std::vector piece_tree(merkle_num_nodes(blocks_per_piece)); + for (piece_index_t i : fs.piece_range()) + { + std::memset(piece.data(), char(static_cast(i) & 0xff), piece.size()); + t.set_hash(piece_index_t(i), lt::hasher(piece).final()); + + for (int k = 0; k < blocks_per_piece; ++k) + { + auto const h = lt::hasher256(piece_span.subspan( + k * lt::default_block_size, lt::default_block_size)).final(); + piece_tree[std::size_t(k)] = h; + g_tree[std::size_t(first_leaf + static_cast(i) * blocks_per_piece + k)] = h; + } + + auto const r = merkle_root(piece_tree); + t.set_hash2(file_index_t{0}, piece_index_t::diff_type(i), r); + } + + merkle_fill_tree(g_tree, num_leafs); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + g_torrent = std::make_shared(buf, from_span); + + return 0; +} + +lt::add_torrent_params generate_atp(std::uint8_t const* data, size_t size) +{ + read_bits bits(data, size); + lt::add_torrent_params ret; + ret.ti = g_torrent; + ret.info_hashes = g_torrent->info_hashes(); + ret.save_path = "."; + ret.file_priorities.resize(bits.read(2)); + for (auto& p : ret.file_priorities) + p = lt::download_priority_t(bits.read(3)); + ret.flags = lt::torrent_flags_t(bits.read(24)); + int const num_unfinished = bits.read(4); + for (int i = 0; i < num_unfinished; ++i) + { + auto& mask = ret.unfinished_pieces[piece_index_t(bits.read(32))]; + mask.resize(bits.read(5)); + for (int i = 0; i < mask.size(); ++i) + if (bits.read(1)) mask.set_bit(i); + } + ret.have_pieces.resize(bits.read(6)); + for (int i = 0; i < ret.have_pieces.size(); ++i) + if (bits.read(1)) ret.have_pieces.set_bit(i); + + ret.verified_pieces.resize(bits.read(6)); + for (int i = 0; i < ret.verified_pieces.size(); ++i) + if (bits.read(1)) ret.verified_pieces.set_bit(i); + + ret.piece_priorities.resize(bits.read(6)); + for (auto& p : ret.piece_priorities) + p = lt::download_priority_t(bits.read(1)); + + // if we read a 1 here, initialize the merkle tree fields correctly + if (bits.read(1)) + { + ret.merkle_trees.resize(1); + ret.merkle_tree_mask.resize(1); + ret.verified_leaf_hashes.resize(1); + ret.verified_leaf_hashes[0].resize(num_leafs, true); + + auto& t = ret.merkle_trees[0]; + auto& mask = ret.merkle_tree_mask[0]; + mask.resize(num_nodes, false); + int idx = -1; + for (auto const& h : g_tree) + { + ++idx; + if (h.is_all_zeros()) continue; + mask[std::size_t(idx)] = true; + t.push_back(g_tree[std::size_t(idx)]); + } + } + else + { + ret.merkle_trees.resize(bits.read(2)); + for (auto& t : ret.merkle_trees) + { + std::size_t block = 0; + t.resize(bits.read(13)); + for (auto& h : t) + { + h = g_tree[block++]; + if (block >= g_tree.size()) block = 0; + } + } + ret.merkle_tree_mask.resize(bits.read(2)); + for (auto& m : ret.merkle_tree_mask) + { + m.resize(bits.read(13)); + for (std::size_t i = 0; i < m.size(); ++i) + m[i] = bits.read(1); + } + ret.verified_leaf_hashes.resize(bits.read(2)); + for (auto& m : ret.verified_leaf_hashes) + { + m.resize(bits.read(4)); + for (std::size_t i = 0; i < m.size(); ++i) + m[i] = bits.read(1); + } + } + + ret.max_uploads = bits.read(32); + ret.max_connections = bits.read(32); + ret.upload_limit = bits.read(32); + ret.download_limit = bits.read(32); + ret.active_time = bits.read(32); + ret.finished_time = bits.read(32); + ret.seeding_time = bits.read(32); + ret.last_seen_complete = bits.read(32); + ret.num_complete = bits.read(32); + ret.num_incomplete = bits.read(32); + ret.num_downloaded = bits.read(32); + + return ret; +} + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + g_ioc.restart(); + boost::optional ses(lt::session{g_params, g_ioc}); + + lt::add_torrent_params atp = generate_atp(data, size); + + ses->async_add_torrent(atp); + auto proxy = ses->abort(); + post(g_ioc, [&]{ ses.reset(); }); + + g_ioc.run_for(seconds(2)); + +#if defined TORRENT_ASIO_DEBUGGING + lt::log_async(); + lt::_async_ops.clear(); + lt::_async_ops_nthreads = 0; + lt::_wakeups.clear(); +#endif + + return 0; +} + + diff --git a/fuzzers/src/base32decode.cpp b/fuzzers/src/base32decode.cpp new file mode 100644 index 0000000..1004de8 --- /dev/null +++ b/fuzzers/src/base32decode.cpp @@ -0,0 +1,40 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/escape_string.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::base32decode({reinterpret_cast(data), size}); + return 0; +} + diff --git a/fuzzers/src/base32encode.cpp b/fuzzers/src/base32encode.cpp new file mode 100644 index 0000000..5bc1cd6 --- /dev/null +++ b/fuzzers/src/base32encode.cpp @@ -0,0 +1,40 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/escape_string.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::base32encode_i2p({reinterpret_cast(data), static_cast(size)}); + return 0; +} + diff --git a/fuzzers/src/base64encode.cpp b/fuzzers/src/base64encode.cpp new file mode 100644 index 0000000..2ce2c12 --- /dev/null +++ b/fuzzers/src/base64encode.cpp @@ -0,0 +1,40 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/escape_string.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::base64encode({reinterpret_cast(data), size}); + return 0; +} + diff --git a/fuzzers/src/bdecode_node.cpp b/fuzzers/src/bdecode_node.cpp new file mode 100644 index 0000000..2578dfd --- /dev/null +++ b/fuzzers/src/bdecode_node.cpp @@ -0,0 +1,41 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/bdecode.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::error_code ec; + lt::bdecode({reinterpret_cast(data), int(size)}, ec); + return 0; +} + diff --git a/fuzzers/src/convert_from_native.cpp b/fuzzers/src/convert_from_native.cpp new file mode 100644 index 0000000..0aa7eab --- /dev/null +++ b/fuzzers/src/convert_from_native.cpp @@ -0,0 +1,40 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/escape_string.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::convert_from_native({reinterpret_cast(data), size}); + return 0; +} + diff --git a/fuzzers/src/convert_to_native.cpp b/fuzzers/src/convert_to_native.cpp new file mode 100644 index 0000000..d03d1e8 --- /dev/null +++ b/fuzzers/src/convert_to_native.cpp @@ -0,0 +1,40 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/escape_string.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::convert_to_native({reinterpret_cast(data), size}); + return 0; +} + diff --git a/fuzzers/src/dht_node.cpp b/fuzzers/src/dht_node.cpp new file mode 100644 index 0000000..97d1edc --- /dev/null +++ b/fuzzers/src/dht_node.cpp @@ -0,0 +1,102 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/kademlia/dht_tracker.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/aux_/session_impl.hpp" + +#include + +using namespace lt; + +aux::session_settings sett; +dht::dht_state state; +std::unique_ptr dht_storage(dht::dht_default_storage_constructor(sett)); + +counters cnt; + +struct obs : dht::dht_observer +{ + void set_external_address(lt::aux::listen_socket_handle const&, lt::address const& /* addr */ + , lt::address const&) override + {} + int get_listen_port(aux::transport ssl, aux::listen_socket_handle const& s) override + { return 6881; } + + void get_peers(lt::sha1_hash const&) override {} + void outgoing_get_peers(sha1_hash const& + , sha1_hash const&, lt::udp::endpoint const&) override {} + void announce(sha1_hash const&, lt::address const&, int) override {} + bool on_dht_request(string_view + , dht::msg const&, entry&) override + { return false; } + +#ifndef TORRENT_DISABLE_LOGGING + + void log(dht_logger::module_t, char const*, ...) override {} + + bool should_log(module_t) const override { return true; } + void log_packet(message_direction_t + , span + , lt::udp::endpoint const&) override {} +#endif // TORRENT_DISABLE_LOGGING +}; + +obs o; +io_context ios; +dht::dht_tracker dht_node(&o + , ios + , [](aux::listen_socket_handle const&, udp::endpoint const& + , span, error_code&, udp_send_flags_t) {} + , sett + , cnt + , *dht_storage + , std::move(state)); +auto listen_socket = std::make_shared(); +aux::listen_socket_handle s(listen_socket); + +error_code ignore; +lt::address_v4 src = make_address_v4("2.2.2.2", ignore); +udp::endpoint ep(src, 6881); +std::once_flag once_flag; + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + ep.address(src); + src = lt::address_v4(aux::plus_one(src.to_bytes())); + std::call_once(once_flag, []{ dht_node.new_socket(s); }); + dht_node.incoming_packet(s, ep, {reinterpret_cast(data), int(size)}); + return 0; +} + diff --git a/fuzzers/src/escape_path.cpp b/fuzzers/src/escape_path.cpp new file mode 100644 index 0000000..87b4426 --- /dev/null +++ b/fuzzers/src/escape_path.cpp @@ -0,0 +1,40 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/escape_string.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::escape_path({reinterpret_cast(data), size}); + return 0; +} + diff --git a/fuzzers/src/escape_string.cpp b/fuzzers/src/escape_string.cpp new file mode 100644 index 0000000..d6bd218 --- /dev/null +++ b/fuzzers/src/escape_string.cpp @@ -0,0 +1,40 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/escape_string.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::escape_string({reinterpret_cast(data), size}); + return 0; +} + diff --git a/fuzzers/src/file_storage_add_file.cpp b/fuzzers/src/file_storage_add_file.cpp new file mode 100644 index 0000000..7151c9b --- /dev/null +++ b/fuzzers/src/file_storage_add_file.cpp @@ -0,0 +1,45 @@ +/* + +Copyright (c) 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/file_storage.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::file_storage fs; + // we expect this call to fail sometimes + try { + fs.add_file({reinterpret_cast(data), size}, 1); + } + catch (...) {} + return 0; +} + diff --git a/fuzzers/src/gzip.cpp b/fuzzers/src/gzip.cpp new file mode 100644 index 0000000..93324e4 --- /dev/null +++ b/fuzzers/src/gzip.cpp @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/gzip.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::error_code ec; + std::vector out; + lt::inflate_gzip({reinterpret_cast(data), int(size)}, out + , 100000, ec); + return 0; +} + diff --git a/fuzzers/src/http_parser.cpp b/fuzzers/src/http_parser.cpp new file mode 100644 index 0000000..35744f9 --- /dev/null +++ b/fuzzers/src/http_parser.cpp @@ -0,0 +1,60 @@ +/* + +Copyright (c) 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/http_parser.hpp" +#include "libtorrent/string_view.hpp" + +void feed_bytes(lt::http_parser& parser, lt::string_view str) +{ + for (int chunks = 1; chunks < 70; ++chunks) + { + parser.reset(); + lt::string_view recv_buf; + for (;;) + { + int const chunk_size = std::min(chunks, int(str.size() - recv_buf.size())); + if (chunk_size == 0) break; + recv_buf = str.substr(0, recv_buf.size() + std::size_t(chunk_size)); + bool error = false; + parser.incoming(recv_buf, error); + if (error) break; + } + } +} + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::http_parser p; + feed_bytes(p, {reinterpret_cast(data), size}); + return 0; +} + diff --git a/fuzzers/src/http_tracker.cpp b/fuzzers/src/http_tracker.cpp new file mode 100644 index 0000000..a74adee --- /dev/null +++ b/fuzzers/src/http_tracker.cpp @@ -0,0 +1,49 @@ +/* + +Copyright (c) 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/http_tracker_connection.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::error_code ec; + lt::sha1_hash const ih("abababababababababab"); + lt::span const input(reinterpret_cast(data), size); + + parse_tracker_response(input, ec, lt::tracker_request_flags_t{}, ih); + parse_tracker_response(input, ec, lt::tracker_request::scrape_request, ih); +#if TORRENT_USE_I2P + parse_tracker_response(input, ec, lt::tracker_request::i2p, ih); +#endif + + return 0; +} + diff --git a/fuzzers/src/idna.cpp b/fuzzers/src/idna.cpp new file mode 100644 index 0000000..83ca051 --- /dev/null +++ b/fuzzers/src/idna.cpp @@ -0,0 +1,42 @@ +/* + +Copyright (c) 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/parse_url.hpp" +#include "libtorrent/string_view.hpp" + +#include + +extern "C" int LLVMFuzzerTestOneInput(std::uint8_t const* data, size_t size) +{ + lt::is_idna(lt::string_view(reinterpret_cast(data), size)); + return 0; +} diff --git a/fuzzers/src/parse_int.cpp b/fuzzers/src/parse_int.cpp new file mode 100644 index 0000000..ac42d9f --- /dev/null +++ b/fuzzers/src/parse_int.cpp @@ -0,0 +1,41 @@ +/* + +Copyright (c) 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ +#include "libtorrent/bdecode.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::bdecode_errors::error_code_enum ec; + std::int64_t val = 0; + lt::parse_int(reinterpret_cast(data), reinterpret_cast(data) + size, ':', val, ec); + return 0; +} + diff --git a/fuzzers/src/parse_magnet_uri.cpp b/fuzzers/src/parse_magnet_uri.cpp new file mode 100644 index 0000000..86b96fc --- /dev/null +++ b/fuzzers/src/parse_magnet_uri.cpp @@ -0,0 +1,45 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/add_torrent_params.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::error_code ec; + lt::add_torrent_params params; + lt::parse_magnet_uri({reinterpret_cast(data), size} + , params, ec); + return 0; +} + + diff --git a/fuzzers/src/parse_url.cpp b/fuzzers/src/parse_url.cpp new file mode 100644 index 0000000..b376f69 --- /dev/null +++ b/fuzzers/src/parse_url.cpp @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/parse_url.hpp" + +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(std::uint8_t const* data, size_t size) +{ + lt::error_code ec; + lt::parse_url_components(std::string(reinterpret_cast(data), size), ec); + return 0; +} diff --git a/fuzzers/src/peer_conn.cpp b/fuzzers/src/peer_conn.cpp new file mode 100644 index 0000000..a7a9a9e --- /dev/null +++ b/fuzzers/src/peer_conn.cpp @@ -0,0 +1,222 @@ +/* + +Copyright (c) 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include "libtorrent/session.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/random.hpp" + +#include "libtorrent/io_context.hpp" + +using namespace lt; + +std::unique_ptr g_ses; +info_hash_t g_info_hash; +int g_listen_port = 0; +io_context g_ios; + +//#define DEBUG_LOGGING 1 + +extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + // set up a session + settings_pack pack; + pack.set_int(settings_pack::piece_timeout, 1); + pack.set_int(settings_pack::request_timeout, 1); + pack.set_int(settings_pack::peer_timeout, 1); + pack.set_int(settings_pack::peer_connect_timeout, 1); + pack.set_int(settings_pack::inactivity_timeout, 1); + pack.set_int(settings_pack::handshake_timeout, 1); + +#ifdef DEBUG_LOGGING + pack.set_int(settings_pack::alert_mask, 0xffffff); +#else + pack.set_int(settings_pack::alert_mask, alert_category::connect + | alert_category::error + | alert_category::status + | alert_category::peer); +#endif + + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_disabled); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_disabled); + + // don't waste time making outbound connections + pack.set_bool(settings_pack::enable_outgoing_tcp, false); + pack.set_bool(settings_pack::enable_outgoing_utp, false); + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_natpmp, false); + pack.set_bool(settings_pack::enable_dht, false); + pack.set_bool(settings_pack::enable_lsd, false); + pack.set_bool(settings_pack::enable_ip_notifier, false); + + // pick an available listen port and only listen on loopback + pack.set_str(settings_pack::listen_interfaces, "127.0.0.1:0"); + + g_ses = std::unique_ptr(new lt::session(pack)); + + // create a torrent + file_storage fs; + int const piece_size = 1024 * 1024; + std::int64_t const total_size = std::int64_t(piece_size) * 100; + fs.add_file("test_file", total_size); + + create_torrent t(fs, piece_size); + + for (piece_index_t i : fs.piece_range()) + t.set_hash(i, sha1_hash("abababababababababab")); + + for (file_index_t const f : fs.file_range()) + for (piece_index_t::diff_type i : fs.file_piece_range(f)) + t.set_hash2(f, i, sha256_hash("abababababababababababababababababababababababababababababababab")); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + auto ti = std::make_shared(buf, from_span); + + // remember the info-hash to give the fuzzer a chance to connect to it + g_info_hash = ti->info_hashes(); + + // add the torrent to the session + add_torrent_params atp; + atp.ti = std::move(ti); + atp.save_path = "."; + + g_ses->add_torrent(std::move(atp)); + + // pull the alerts for the listen socket we ended up using + time_point const end_time = clock_type::now() + seconds(5); + bool started = false; + while (g_listen_port == 0 || !started) + { + std::vector alerts; + auto const now = clock_type::now(); + if (now > end_time) return -1; + + g_ses->wait_for_alert(end_time - now); + g_ses->pop_alerts(&alerts); + + for (auto const a : alerts) + { + std::cout << a->message() << '\n'; + if (auto la = alert_cast(a)) + { + if (la->socket_type == socket_type_t::tcp) + { + g_listen_port = la->port; + std::cout << "listening on " << g_listen_port << '\n'; + } + } + if (alert_cast(a)) + { + started = true; + } + } + } + + // we have to destruct the session before global destructors, such as the + // system error code category. The session objects rely on error_code during + // its destruction + std::atexit([]{ g_ses.reset(); }); + + return 0; +} + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + if (size < 8) return 0; + +#ifdef DEBUG_LOGGING + time_point const start_time = clock_type::now(); +#endif + // connect + tcp::socket s(g_ios); + error_code ec; + do { + ec.clear(); + error_code ignore; + s.connect(tcp::endpoint(make_address("127.0.0.1", ignore), g_listen_port), ec); + } while (ec == boost::system::errc::interrupted); + + // bittorrent handshake + + std::vector handshake(1 + 19 + 8 + 20 + 20 + size - 8); + std::memcpy(handshake.data(), "\x13" "BitTorrent protocol", 20); + std::memcpy(handshake.data() + 20, data, 8); + std::memcpy(handshake.data() + 28, g_info_hash.get_best().data(), 20); + lt::aux::random_bytes({handshake.data() + 48, 20}); + data += 8; + size -= 8; + std::memcpy(handshake.data() + 68, data, size); + + // we're likely to fail to write entire (garbage) messages, as libtorrent may + // disconnect us half-way through. This may fail with broken_pipe for + // instance + error_code ignore; + boost::asio::write(s, boost::asio::buffer(handshake), ignore); + + s.close(); + + // wait for the alert saying the connection was closed + + time_point const end_time = clock_type::now() + seconds(3); + for (;;) + { + std::vector alerts; + auto const now = clock_type::now(); + if (now > end_time) return -1; + + g_ses->wait_for_alert(end_time - now); + g_ses->pop_alerts(&alerts); + + for (auto const a : alerts) + { +#ifdef DEBUG_LOGGING + std::cout << duration_cast(a->timestamp() - start_time).count() + << ": " << a->message() << '\n'; +#endif + if (alert_cast(a) + || alert_cast(a)) + { + goto done; + } + } + } +done: + + return 0; +} + diff --git a/fuzzers/src/read_bits.hpp b/fuzzers/src/read_bits.hpp new file mode 100644 index 0000000..9e08fd6 --- /dev/null +++ b/fuzzers/src/read_bits.hpp @@ -0,0 +1,65 @@ +/* + +Copyright (c) 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +struct read_bits +{ + read_bits(std::uint8_t const* d, std::size_t s) + : m_data(d), m_size(s) + {} + + int read(int bits) + { + if (m_size == 0) return 0; + int ret = 0; + while (bits > 0 && m_size > 0) + { + int const bits_to_copy = std::min(8 - m_bit, bits); + ret <<= bits_to_copy; + ret |= ((*m_data) >> m_bit) & ((1 << bits_to_copy) - 1); + m_bit += bits_to_copy; + bits -= bits_to_copy; + if (m_bit == 8) + { + --m_size; + ++m_data; + m_bit = 0; + } + } + return ret; + } +private: + std::uint8_t const* m_data; + std::size_t m_size; + int m_bit = 0; +}; + + diff --git a/fuzzers/src/resume_data.cpp b/fuzzers/src/resume_data.cpp new file mode 100644 index 0000000..dd02d4c --- /dev/null +++ b/fuzzers/src/resume_data.cpp @@ -0,0 +1,45 @@ +/* + +Copyright (c) 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include "libtorrent/read_resume_data.hpp" +#include "libtorrent/write_resume_data.hpp" +#include "libtorrent/add_torrent_params.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::error_code ec; + auto ret = lt::read_resume_data({reinterpret_cast(data), int(size)}, ec); + auto buf = write_resume_data_buf(ret); + return 0; +} + diff --git a/fuzzers/src/sanitize_path.cpp b/fuzzers/src/sanitize_path.cpp new file mode 100644 index 0000000..baaada9 --- /dev/null +++ b/fuzzers/src/sanitize_path.cpp @@ -0,0 +1,41 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/torrent_info.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + std::string out; + lt::aux::sanitize_append_path_element(out, {reinterpret_cast(data), size}); + return 0; +} + diff --git a/fuzzers/src/session_params.cpp b/fuzzers/src/session_params.cpp new file mode 100644 index 0000000..360eab7 --- /dev/null +++ b/fuzzers/src/session_params.cpp @@ -0,0 +1,44 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include "libtorrent/session_params.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + try { + auto ret = lt::read_session_params({reinterpret_cast(data), int(size)}); + } catch (...) {} + return 0; +} + + diff --git a/fuzzers/src/torrent_info.cpp b/fuzzers/src/torrent_info.cpp new file mode 100644 index 0000000..7ff3c13 --- /dev/null +++ b/fuzzers/src/torrent_info.cpp @@ -0,0 +1,41 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/torrent_info.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + lt::error_code ec; + lt::torrent_info ti({reinterpret_cast(data), int(size)}, ec, lt::from_span); + return 0; +} + diff --git a/fuzzers/src/upnp.cpp b/fuzzers/src/upnp.cpp new file mode 100644 index 0000000..ffebb61 --- /dev/null +++ b/fuzzers/src/upnp.cpp @@ -0,0 +1,45 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/upnp.hpp" +#include "libtorrent/xml_parse.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + using namespace std::placeholders; + + lt::parse_state s; + lt::xml_parse({reinterpret_cast(data), size} + , std::bind(<::find_control_url, _1, _2, std::ref(s))); + return 0; +} + diff --git a/fuzzers/src/utf8_codepoint.cpp b/fuzzers/src/utf8_codepoint.cpp new file mode 100644 index 0000000..72f5450 --- /dev/null +++ b/fuzzers/src/utf8_codepoint.cpp @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/utf8.hpp" + +#include + +extern "C" int LLVMFuzzerTestOneInput(std::uint8_t const* data, size_t size) +{ + if (size == 0) return 0; + lt::parse_utf8_codepoint({reinterpret_cast(data), size}); + return 0; +} + diff --git a/fuzzers/src/utp.cpp b/fuzzers/src/utp.cpp new file mode 100644 index 0000000..2c8c9a8 --- /dev/null +++ b/fuzzers/src/utp.cpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/utp_socket_manager.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/aux_/utp_stream.hpp" +#include "libtorrent/udp_socket.hpp" + +using namespace lt; + +io_context ios; +lt::aux::session_settings sett; +counters cnt; + +aux::utp_socket_manager man( + [](std::weak_ptr, udp::endpoint const&, span, error_code&, udp_send_flags_t){} + , [](aux::socket_type){} + , ios + , sett + , cnt + , nullptr); + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + std::unique_ptr sock; + { + aux::utp_stream str(ios); + sock = std::make_unique(1, 0, &str, man); + str.set_impl(sock.get()); + udp::endpoint ep; + time_point ts(seconds(100)); + span buf(reinterpret_cast(data), size); + sock->incoming_packet(buf, ep, ts); + + // clear any deferred acks + man.socket_drained(); + } + return 0; +} + diff --git a/fuzzers/src/verify_encoding.cpp b/fuzzers/src/verify_encoding.cpp new file mode 100644 index 0000000..5104531 --- /dev/null +++ b/fuzzers/src/verify_encoding.cpp @@ -0,0 +1,42 @@ +/* + +Copyright (c) 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/torrent_info.hpp" + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + if (size == 0) return 0; + std::string str{reinterpret_cast(data), size}; + lt::aux::verify_encoding(str); + return 0; +} + diff --git a/fuzzers/tools/generate_initial_corpus.py b/fuzzers/tools/generate_initial_corpus.py new file mode 100644 index 0000000..efc1dc1 --- /dev/null +++ b/fuzzers/tools/generate_initial_corpus.py @@ -0,0 +1,214 @@ +import os +import shutil +import hashlib +import struct +import random + +corpus_dirs = [ + 'torrent_info', 'upnp', 'gzip', 'base32decode', 'base32encode', + 'base64encode', 'bdecode_node', 'convert_from_native', 'convert_to_native', + 'dht_node', 'escape_path', 'escape_string', 'file_storage_add_file', + 'http_parser', 'lazy_bdecode', 'parse_int', 'parse_magnet_uri', 'resume_data', + 'sanitize_path', 'utf8_codepoint', 'utp', + 'verify_encoding', 'peer_conn', 'add_torrent', 'idna', 'parse_url', 'http_tracker'] + +for p in corpus_dirs: + try: + os.makedirs(os.path.join('corpus', p)) + except Exception as e: + print(e) + +torrent_dir = '../test/test_torrents' +for f in os.listdir(torrent_dir): + shutil.copy(os.path.join(torrent_dir, f), os.path.join('corpus', 'torrent_info')) + +xml_tests = [ + '', '', '', ' +''', + ''] + +for x in xml_tests: + name = hashlib.sha1(x.encode('ascii')).hexdigest() + with open(os.path.join('corpus', 'upnp', name), 'w+') as f: + f.write(x) + +gzip_dir = '../test' +for f in ['zeroes.gz', 'corrupt.gz', 'invalid1.gz']: + shutil.copy(os.path.join(gzip_dir, f), os.path.join('corpus', 'gzip')) + +idna = ['....', 'xn--foo-.bar', 'foo.xn--bar-.com', 'Xn--foobar-', 'XN--foobar-', '..xnxn--foobar-'] + +counter = 0 +for i in idna: + open(os.path.join('corpus', 'idna', '%d' % counter), 'w+').write(i) + counter += 1 + +urls = ['https://user:password@example.com:8080/path?query'] + +counter = 0 +for i in urls: + open(os.path.join('corpus', 'parse_url', '%d' % counter), 'w+').write(i) + counter += 1 + +counter = 0 +tracker_fields = ['interval', 'min interval', 'tracker id', 'failure reason', + 'warning message', 'complete', 'incomplete', 'downloaded', 'downloaders', 'external ip'] +tracker_values = ['i-1e', 'i0e', 'i1800e', '6:foobar', 'de', '0:', 'le'] +peer_fields = ['peer id', 'ip', 'port'] +peer_values = ['i-1e', 'i0e', 'i1800e', '6:foobar', 'de', '0:', 'le', '9:127.0.0.1'] + +for i in range(1000): + tracker_msg = 'd' + for f in tracker_fields: + tracker_msg += '%d:' % len(f) + f + tracker_msg += random.choice(tracker_values) + + tracker_msg += '5:filesd20:ababababababababababd' + for f in tracker_fields: + tracker_msg += '%d:' % len(f) + f + tracker_msg += random.choice(tracker_values) + tracker_msg += 'ee' + + tracker_msg += '5:peers' + if random.getrandbits(1) == 0: + tracker_msg += 'l' + for k in range(10): + tracker_msg += 'd' + for f in peer_fields: + tracker_msg += '%d:' % len(f) + f + tracker_msg += random.choice(peer_values) + tracker_msg += 'e' + tracker_msg += 'e' + else: + tracker_msg += '60:' + for k in range(6*10): + tracker_msg += chr(random.getrandbits(8)) + + tracker_msg += '6:peers6' + tracker_msg += '180:' + for k in range(18*10): + tracker_msg += chr(random.getrandbits(8)) + + tracker_msg += 'e' + open(os.path.join('corpus', 'http_tracker', '%d' % counter), 'w+').write(tracker_msg) + counter += 1 + +# generate peer protocol messages +messages = [] + + +def add_length(msg): + return struct.pack('>I', len(msg)) + msg + + +def add_reserved(msg): + return b'\0\0\0\0\0\x18\0\x05' + msg + + +# extended handshake +def add_extended_handshake(msg): + ext_handshake = b'd1:md11:ut_metadatai1e11:lt_donthavei2e12:ut_holepunch' + \ + b'i3e11:upload_onlyi4ee11:upload_onlyi1e10:share_modei1e4:reqqi1234e6:yourip4:0000e' + return add_length(struct.pack('BB', 20, 0) + ext_handshake) + msg + + +# request +for i in range(101): + for j in range(-1, 1): + messages.append(add_length(struct.pack('>Biii', 6, i, j, 0x4000))) + +# cancel +for i in range(101): + for j in range(-1, 1): + messages.append(add_length(struct.pack('>Biii', 8, i, j, 0x4000))) + +# piece +for i in range(101): + messages.append(add_length(struct.pack('>Bii', 7, i, 0) + (b'a' * 0x4000))) + +# single-byte +for i in range(256): + messages.append(add_length(struct.pack('B', i))) + +# reject +for i in range(101): + messages.append(add_length(struct.pack('>Biii', 16, i, 0, 0x4000))) + +# suggest +for i in range(101): + messages.append(add_length(struct.pack('>Bi', 13, i))) + +# allow-fast +for i in range(101): + messages.append(add_length(struct.pack('>Bi', 17, i))) + +# have +for i in range(101): + messages.append(add_length(struct.pack('>Bi', 4, i))) + +# DHT-port +for i in range(101): + messages.append(add_length(struct.pack('>BH', 9, i * 10))) + +# hash request +for i in range(-10, 200, 20): + for j in range(-1, 1): + for k in range(-1, 1): + for m in range(-1, 1): + for n in range(-1, 1): + messages.append(add_length(struct.pack('>Biiiii', 21, i, j, k, m, n))) + +# hash reject +for i in range(-10, 200, 20): + for j in range(-1, 1): + for k in range(-1, 1): + for m in range(-1, 1): + for n in range(-1, 1): + messages.append(add_length(struct.pack('>Biiiii', 23, i, j, k, m, n))) + +# hash +for i in range(-10, 200, 20): + for j in range(-1, 1): + messages.append(add_length(struct.pack('>Biiiii', 22, i, j, 0, 2, 0) + (b'0' * 32 * 5))) + +# lt_dont_have +messages.append(add_extended_handshake(add_length(struct.pack('>BBi', 20, 7, -1)))) +messages.append(add_extended_handshake(add_length(struct.pack('>BBi', 20, 7, 0)))) +messages.append(add_extended_handshake(add_length(struct.pack('>BBi', 20, 7, 0x7fffffff)))) + +# share mode +messages.append(add_extended_handshake(add_length(struct.pack('BBB', 20, 8, 255)))) +messages.append(add_extended_handshake(add_length(struct.pack('BBB', 20, 8, 0)))) +messages.append(add_extended_handshake(add_length(struct.pack('BBB', 20, 8, 1)))) + +# holepunch +for i in range(0, 2): + for j in range(0, 1): + messages.append(add_extended_handshake(add_length(struct.pack('>BBBBiH', 20, 4, i, j, 0, 0)))) + messages.append(add_extended_handshake(add_length(struct.pack('>BBBBiiH', 20, 4, i, j, 0, 0, 0)))) + +# upload only +for i in range(0, 1): + messages.append(add_extended_handshake(add_length(struct.pack('BBB', 20, 3, i)))) + +# bitfields +bitfield_len = (100 + 7) // 8 + +for i in range(256): + messages.append(add_length(struct.pack('B', 5) + (struct.pack('B', i) * bitfield_len))) + +mixes = [] + +for i in range(200): + random.shuffle(messages) + mixes.append(b''.join(messages[1:20])) + +messages += mixes + +for m in messages: + f = open('corpus/peer_conn/%s' % hashlib.sha1(m).hexdigest(), 'wb+') + f.write(add_reserved(m)) + f.close() diff --git a/fuzzers/tools/unify_corpus_names.py b/fuzzers/tools/unify_corpus_names.py new file mode 100644 index 0000000..8321d30 --- /dev/null +++ b/fuzzers/tools/unify_corpus_names.py @@ -0,0 +1,24 @@ +import sys +import os +import string +import hashlib + +if len(sys.argv) < 2: + print('usage: unify_corpus_names.py \n') + sys.exit(1) + +root = sys.argv[1] +for name in os.listdir(root): + f = os.path.join(root, name) + + # ignore directories + if not os.path.isfile(f): + continue + + # if the name already looks like a SHA-1 hash, ignore it + if len(name) == 40 and all(c in string.hexdigits for c in name): + continue + + new_name = hashlib.sha1(open(f, 'rb').read()).hexdigest() + print('%s -> %s' % (f, new_name)) + os.rename(f, os.path.join(root, new_name)) diff --git a/include/libtorrent/add_torrent_params.hpp b/include/libtorrent/add_torrent_params.hpp new file mode 100644 index 0000000..db5ffb8 --- /dev/null +++ b/include/libtorrent/add_torrent_params.hpp @@ -0,0 +1,414 @@ +/* + +Copyright (c) 2009-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2019, ghbplayer +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ADD_TORRENT_PARAMS_HPP_INCLUDED +#define TORRENT_ADD_TORRENT_PARAMS_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/version.hpp" +#include "libtorrent/socket.hpp" // for tcp::endpoint +#include "libtorrent/bitfield.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/torrent_flags.hpp" +#include "libtorrent/info_hash.hpp" +#include "libtorrent/download_priority.hpp" +#include "libtorrent/client_data.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" +#include "libtorrent/fwd.hpp" + +namespace libtorrent { + +TORRENT_VERSION_NAMESPACE_3 + + // The add_torrent_params contains all the information in a .torrent file + // along with all information necessary to add that torrent to a session. + // The key fields when adding a torrent are: + // + // * ti - the immutable info-dict part of the torrent + // * info_hash - when you don't have the metadata (.torrent file). This + // uniquely identifies the torrent and can validate the info-dict when + // received from the swarm. + // + // In order to add a torrent to a session, one of those fields must be set + // in addition to ``save_path``. The add_torrent_params object can then be + // passed into one of the ``session::add_torrent()`` overloads or + // ``session::async_add_torrent()``. + // + // If you only specify the info-hash, the torrent file will be downloaded + // from peers, which requires them to support the metadata extension. For + // the metadata extension to work, libtorrent must be built with extensions + // enabled (``TORRENT_DISABLE_EXTENSIONS`` must not be defined). It also + // takes an optional ``name`` argument. This may be left empty in case no + // name should be assigned to the torrent. In case it's not, the name is + // used for the torrent as long as it doesn't have metadata. See + // ``torrent_handle::name``. + // + // The ``add_torrent_params`` is also used when requesting resume data for a + // torrent. It can be saved to and restored from a file and added back to a + // new session. For serialization and de-serialization of + // ``add_torrent_params`` objects, see read_resume_data() and + // write_resume_data(). + // + // The ``add_torrent_params`` is also used to represent a parsed .torrent + // file. It can be loaded via load_torrent_file(), load_torrent_buffer() and + // load_torrent_parsed(). It can be saved via write_torrent_file(). +#include "libtorrent/aux_/disable_warnings_push.hpp" + struct TORRENT_EXPORT add_torrent_params + { + // hidden + add_torrent_params(); + ~add_torrent_params(); + add_torrent_params(add_torrent_params&&) noexcept; + add_torrent_params& operator=(add_torrent_params&&) &; + add_torrent_params(add_torrent_params const&); + add_torrent_params& operator=(add_torrent_params const&) &; + + // These are all deprecated. use torrent_flags_t instead (in + // libtorrent/torrent_flags.hpp) +#if TORRENT_ABI_VERSION == 1 + + using flags_t = torrent_flags_t; + +#define DECL_FLAG(name) \ + TORRENT_DEPRECATED static constexpr torrent_flags_t flag_##name = torrent_flags::name + + DECL_FLAG(seed_mode); + DECL_FLAG(upload_mode); + DECL_FLAG(share_mode); + DECL_FLAG(apply_ip_filter); + DECL_FLAG(paused); + DECL_FLAG(auto_managed); + DECL_FLAG(duplicate_is_error); + DECL_FLAG(update_subscribe); + DECL_FLAG(super_seeding); + DECL_FLAG(sequential_download); + DECL_FLAG(pinned); + DECL_FLAG(stop_when_ready); + DECL_FLAG(override_trackers); + DECL_FLAG(override_web_seeds); + DECL_FLAG(need_save_resume); + DECL_FLAG(override_resume_data); + DECL_FLAG(merge_resume_trackers); + DECL_FLAG(use_resume_save_path); + DECL_FLAG(merge_resume_http_seeds); + DECL_FLAG(default_flags); +#undef DECL_FLAG +#endif // TORRENT_ABI_VERSION + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // filled in by the constructor and should be left untouched. It is used + // for forward binary compatibility. + int version = LIBTORRENT_VERSION_NUM; + + // torrent_info object with the torrent to add. Unless the + // info_hash is set, this is required to be initialized. + std::shared_ptr ti; + + // If the torrent doesn't have a tracker, but relies on the DHT to find + // peers, the ``trackers`` can specify tracker URLs for the torrent. + aux::noexcept_movable> trackers; + + // the tiers the URLs in ``trackers`` belong to. Trackers belonging to + // different tiers may be treated differently, as defined by the multi + // tracker extension. This is optional, if not specified trackers are + // assumed to be part of tier 0, or whichever the last tier was as + // iterating over the trackers. + aux::noexcept_movable> tracker_tiers; + + // a list of hostname and port pairs, representing DHT nodes to be added + // to the session (if DHT is enabled). The hostname may be an IP address. + aux::noexcept_movable>> dht_nodes; + + // in case there's no other name in this torrent, this name will be used. + // The name out of the torrent_info object takes precedence if available. + std::string name; + + // the path where the torrent is or will be stored. + // + // .. note:: + // On windows this path (and other paths) are interpreted as UNC + // paths. This means they must use backslashes as directory separators + // and may not contain the special directories "." or "..". + // + // Setting this to an absolute path performs slightly better than a + // relative path. + std::string save_path; + + // One of the values from storage_mode_t. For more information, see + // storage-allocation_. + storage_mode_t storage_mode = storage_mode_sparse; + + // The ``userdata`` parameter is optional and will be passed on to the + // extension constructor functions, if any + // (see torrent_handle::add_extension()). It will also be stored in the + // torrent object and can be retrieved by calling userdata(). + client_data_t userdata; + + // can be set to control the initial file priorities when adding a + // torrent. The semantics are the same as for + // ``torrent_handle::prioritize_files()``. The file priorities specified + // in here take precedence over those specified in the resume data, if + // any. + // If this vector of file priorities is shorter than the number of files + // in the torrent, the remaining files (not covered by this) will still + // have the default download priority. This default can be changed by + // setting the default_dont_download torrent_flag. + aux::noexcept_movable> file_priorities; + + // torrent extension construction functions can be added to this vector + // to have them be added immediately when the torrent is constructed. + // This may be desired over the torrent_handle::add_extension() in order + // to avoid race conditions. For instance it may be important to have the + // plugin catch events that happen very early on after the torrent is + // created. + aux::noexcept_movable(torrent_handle const&, client_data_t)>>> + extensions; + + // the default tracker id to be used when announcing to trackers. By + // default this is empty, and no tracker ID is used, since this is an + // optional argument. If a tracker returns a tracker ID, that ID is used + // instead of this. + std::string trackerid; + + // flags controlling aspects of this torrent and how it's added. See + // torrent_flags_t for details. + // + // .. note:: + // The ``flags`` field is initialized with default flags by the + // constructor. In order to preserve default behavior when clearing or + // setting other flags, make sure to bitwise OR or in a flag or bitwise + // AND the inverse of a flag to clear it. + torrent_flags_t flags = torrent_flags::default_flags; + +#if TORRENT_ABI_VERSION < 3 + // backwards compatible v1 hash, or truncated v2 + TORRENT_DEPRECATED + sha1_hash info_hash; +#endif + + // set this to the info hash of the torrent to add in case the info-hash + // is the only known property of the torrent. i.e. you don't have a + // .torrent file nor a magnet link. + // To add a magnet link, use parse_magnet_uri() to populate fields in the + // add_torrent_params object. + info_hash_t info_hashes; + + // ``max_uploads``, ``max_connections``, ``upload_limit``, + // ``download_limit`` correspond to the ``set_max_uploads()``, + // ``set_max_connections()``, ``set_upload_limit()`` and + // ``set_download_limit()`` functions on torrent_handle. These values let + // you initialize these settings when the torrent is added, instead of + // calling these functions immediately following adding it. + // + // -1 means unlimited on these settings just like their counterpart + // functions on torrent_handle + // + // For fine grained control over rate limits, including making them apply + // to local peers, see peer-classes_. + int max_uploads = -1; + int max_connections = -1; + + // the upload and download rate limits for this torrent, specified in + // bytes per second. -1 means unlimited. + int upload_limit = -1; + int download_limit = -1; + + // the total number of bytes uploaded and downloaded by this torrent so + // far. + std::int64_t total_uploaded = 0; + std::int64_t total_downloaded = 0; + + // the number of seconds this torrent has spent in started, finished and + // seeding state so far, respectively. + int active_time = 0; + int finished_time = 0; + int seeding_time = 0; + + // if set to a non-zero value, this is the posix time of when this torrent + // was first added, including previous runs/sessions. If set to zero, the + // internal added_time will be set to the time of when add_torrent() is + // called. + std::time_t added_time = 0; + std::time_t completed_time = 0; + + // if set to non-zero, initializes the time (expressed in posix time) when + // we last saw a seed or peers that together formed a complete copy of the + // torrent. If left set to zero, the internal counterpart to this field + // will be updated when we see a seed or a distributed copies >= 1.0. + std::time_t last_seen_complete = 0; + + // these field can be used to initialize the torrent's cached scrape data. + // The scrape data is high level metadata about the current state of the + // swarm, as returned by the tracker (either when announcing to it or by + // sending a specific scrape request). ``num_complete`` is the number of + // peers in the swarm that are seeds, or have every piece in the torrent. + // ``num_incomplete`` is the number of peers in the swarm that do not have + // every piece. ``num_downloaded`` is the number of times the torrent has + // been downloaded (not initiated, but the number of times a download has + // completed). + // + // Leaving any of these values set to -1 indicates we don't know, or we + // have not received any scrape data. + int num_complete = -1; + int num_incomplete = -1; + int num_downloaded = -1; + + // URLs can be added to these two lists to specify additional web + // seeds to be used by the torrent. If the ``flag_override_web_seeds`` + // is set, these will be the _only_ ones to be used. i.e. any web seeds + // found in the .torrent file will be overridden. + // + // http_seeds expects URLs to web servers implementing the original HTTP + // seed specification `BEP 17`_. + // + // url_seeds expects URLs to regular web servers, aka "get right" style, + // specified in `BEP 19`_. + aux::noexcept_movable> http_seeds; + aux::noexcept_movable> url_seeds; + + // peers to add to the torrent, to be tried to be connected to as + // bittorrent peers. + aux::noexcept_movable> peers; + + // peers banned from this torrent. The will not be connected to + aux::noexcept_movable> banned_peers; + + // this is a map of partially downloaded piece. The key is the piece index + // and the value is a bitfield where each bit represents a 16 kiB block. + // A set bit means we have that block. + aux::noexcept_movable> unfinished_pieces; + + // this is a bitfield indicating which pieces we already have of this + // torrent. + typed_bitfield have_pieces; + + // when in seed_mode, pieces with a set bit in this bitfield have been + // verified to be valid. Other pieces will be verified the first time a + // peer requests it. + typed_bitfield verified_pieces; + + // this sets the priorities for each individual piece in the torrent. Each + // element in the vector represent the piece with the same index. If you + // set both file- and piece priorities, file priorities will take + // precedence. + aux::noexcept_movable> piece_priorities; + +#if TORRENT_ABI_VERSION <= 2 + // support for BEP 30 merkle torrents has been removed + + // if this is a merkle tree torrent, and you're seeding, this field must + // be set. It is all the hashes in the binary tree, with the root as the + // first entry. See torrent_info::set_merkle_tree() for more info. + TORRENT_DEPRECATED aux::noexcept_movable> merkle_tree; +#endif + + // v2 hashes, if known + aux::vector, file_index_t> merkle_trees; + + // if set, indicates which hashes are included in the corresponding + // vector of ``merkle_trees``. These bitmasks always cover the full + // tree, a cleared bit means the hash is all zeros (i.e. not set) and + // set bit means the next hash in the corresponding vector in + // ``merkle_trees`` is the hash for that node. This is an optimization + // to avoid storing a lot of zeros. + aux::vector, file_index_t> merkle_tree_mask; + + // bit-fields indicating which v2 leaf hashes have been verified + // against the root hash. If this vector is empty and merkle_trees is + // non-empty it implies that all hashes in merkle_trees are verified. + aux::vector, file_index_t> verified_leaf_hashes; + + // this is a map of file indices in the torrent and new filenames to be + // applied before the torrent is added. + aux::noexcept_movable> renamed_files; + + // the posix time of the last time payload was received or sent for this + // torrent, respectively. A value of 0 means we don't know when we last + // uploaded or downloaded, or we have never uploaded or downloaded any + // payload for this torrent. + std::time_t last_download = 0; + std::time_t last_upload = 0; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + + // ``url`` can be set to a magnet link, in order to download the .torrent + // file (also known as the metadata), specifically the info-dictionary, + // from the bittorrent swarm. This may require access to the DHT, in case + // the magnet link does not come with trackers. + // + // In earlier versions of libtorrent, the URL could be an HTTP or file:// + // url. These uses are deprecated and discouraged. When adding a torrent + // by magnet link, it will be set to the ``downloading_metadata`` state + // until the .torrent file has been downloaded. If there is any error + // while downloading, the torrent will be stopped and the torrent error + // state (``torrent_status::error``) will indicate what went wrong. + TORRENT_DEPRECATED std::string url; + + // The optional parameter, ``resume_data`` can be given if up to date + // fast-resume data is available. The fast-resume data can be acquired + // from a running torrent by calling save_resume_data() on + // torrent_handle. See fast-resume_. The ``vector`` that is passed in + // will be swapped into the running torrent instance with + // ``std::vector::swap()``. + TORRENT_DEPRECATED aux::noexcept_movable> resume_data; + + // to support the deprecated use case of reading the resume data into + // resume_data field and getting a reject alert, any parse failure is + // communicated forward into libtorrent via this field. If this is set, a + // fastresume_rejected_alert will be posted. + error_code internal_resume_data_error; +#endif // TORRENT_ABI_VERSION + + }; + +TORRENT_VERSION_NAMESPACE_3_END + +namespace aux { + TORRENT_EXTRA_EXPORT bool contains_resume_data(add_torrent_params const&); +} + +} + +#endif diff --git a/include/libtorrent/address.hpp b/include/libtorrent/address.hpp new file mode 100644 index 0000000..0639c7b --- /dev/null +++ b/include/libtorrent/address.hpp @@ -0,0 +1,79 @@ +/* + +Copyright (c) 2006, 2009, 2015, 2017-2020, Arvid Norberg +Copyright (c) 2016, 2018, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ADDRESS_HPP_INCLUDED +#define TORRENT_ADDRESS_HPP_INCLUDED + +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/string_view.hpp" + +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#else +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // SIMULATOR + +namespace libtorrent { + +#if defined TORRENT_BUILD_SIMULATOR + using sim::asio::ip::address; + using sim::asio::ip::address_v4; + using sim::asio::ip::address_v6; +#else + using boost::asio::ip::address; + using boost::asio::ip::address_v4; + using boost::asio::ip::address_v6; +#endif // SIMULATOR + + using boost::asio::ip::network_v4; + using boost::asio::ip::make_network_v4; + using boost::asio::ip::v4_mapped; + +#if defined TORRENT_BUILD_SIMULATOR + using sim::asio::ip::make_address; + using sim::asio::ip::make_address_v4; + using sim::asio::ip::make_address_v6; +#else + using boost::asio::ip::make_address; + using boost::asio::ip::make_address_v4; + using boost::asio::ip::make_address_v6; +#endif + +} + +#endif diff --git a/include/libtorrent/alert.hpp b/include/libtorrent/alert.hpp new file mode 100644 index 0000000..9afef63 --- /dev/null +++ b/include/libtorrent/alert.hpp @@ -0,0 +1,333 @@ +/* + +Copyright (c) 2003, Daniel Wallin +Copyright (c) 2004-2005, 2008-2009, 2013-2020, 2022, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALERT_HPP_INCLUDED +#define TORRENT_ALERT_HPP_INCLUDED + +#include + +// OVERVIEW +// +// The pop_alerts() function on session is the main interface for retrieving +// alerts (warnings, messages and errors from libtorrent). If no alerts have +// been posted by libtorrent pop_alerts() will return an empty list. +// +// By default, only errors are reported. settings_pack::alert_mask can be +// used to specify which kinds of events should be reported. The alert mask is +// a combination of the alert_category_t flags in the alert class. +// +// Every alert belongs to one or more category. There is a cost associated with +// posting alerts. Only alerts that belong to an enabled category are +// posted. Setting the alert bitmask to 0 will disable all alerts (except those +// that are non-discardable). Alerts that are responses to API calls such as +// save_resume_data() and post_session_stats() are non-discardable and will be +// posted even if their category is disabled. +// +// There are other alert base classes that some alerts derive from, all the +// alerts that are generated for a specific torrent are derived from +// torrent_alert, and tracker events derive from tracker_alert. +// +// Alerts returned by pop_alerts() are only valid until the next call to +// pop_alerts(). You may not copy an alert object to access it after the next +// call to pop_alerts(). Internal members of alerts also become invalid once +// pop_alerts() is called again. + +#include "libtorrent/time.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/flags.hpp" + +namespace libtorrent { + +// bitmask type used to define alert categories. Categories can be enabled +// and disabled by the settings_pack::alert_mask setting. Constants are defined +// in the lt::alert_category namespace +using alert_category_t = flags::bitfield_flag; + +namespace alert_category { + + // Enables alerts that report an error. This includes: + // + // * tracker errors + // * tracker warnings + // * file errors + // * resume data failures + // * web seed errors + // * .torrent files errors + // * listen socket errors + // * port mapping errors + constexpr alert_category_t error = 0_bit; + + // Enables alerts when peers send invalid requests, get banned or + // snubbed. + constexpr alert_category_t peer = 1_bit; + + // Enables alerts for port mapping events. For NAT-PMP and UPnP. + constexpr alert_category_t port_mapping = 2_bit; + + // Enables alerts for events related to the storage. File errors and + // synchronization events for moving the storage, renaming files etc. + constexpr alert_category_t storage = 3_bit; + + // Enables all tracker events. Includes announcing to trackers, + // receiving responses, warnings and errors. + constexpr alert_category_t tracker = 4_bit; + + // Low level alerts for when peers are connected and disconnected. + constexpr alert_category_t connect = 5_bit; + + // Enables alerts for when a torrent or the session changes state. + constexpr alert_category_t status = 6_bit; + + // Alerts when a peer is blocked by the ip blocker or port blocker. + constexpr alert_category_t ip_block = 8_bit; + + // Alerts when some limit is reached that might limit the download + // or upload rate. + constexpr alert_category_t performance_warning = 9_bit; + + // Alerts on events in the DHT node. For incoming searches or + // bootstrapping being done etc. + constexpr alert_category_t dht = 10_bit; + + // If you enable these alerts, you will receive a stats_alert + // approximately once every second, for every active torrent. + // These alerts contain all statistics counters for the interval since + // the lasts stats alert. + constexpr alert_category_t stats = 11_bit; + + // Enables debug logging alerts. These are available unless libtorrent + // was built with logging disabled (``TORRENT_DISABLE_LOGGING``). The + // alerts being posted are log_alert and are session wide. + constexpr alert_category_t session_log = 13_bit; + + // Enables debug logging alerts for torrents. These are available + // unless libtorrent was built with logging disabled + // (``TORRENT_DISABLE_LOGGING``). The alerts being posted are + // torrent_log_alert and are torrent wide debug events. + constexpr alert_category_t torrent_log = 14_bit; + + // Enables debug logging alerts for peers. These are available unless + // libtorrent was built with logging disabled + // (``TORRENT_DISABLE_LOGGING``). The alerts being posted are + // peer_log_alert and low-level peer events and messages. + constexpr alert_category_t peer_log = 15_bit; + + // enables the incoming_request_alert. + constexpr alert_category_t incoming_request = 16_bit; + + // enables dht_log_alert, debug logging for the DHT + constexpr alert_category_t dht_log = 17_bit; + + // enable events from pure dht operations not related to torrents + constexpr alert_category_t dht_operation = 18_bit; + + // enables port mapping log events. This log is useful + // for debugging the UPnP or NAT-PMP implementation + constexpr alert_category_t port_mapping_log = 19_bit; + + // enables verbose logging from the piece picker. + constexpr alert_category_t picker_log = 20_bit; + + // alerts when files complete downloading + constexpr alert_category_t file_progress = 21_bit; + + // alerts when pieces complete downloading or fail hash check + constexpr alert_category_t piece_progress = 22_bit; + + // alerts when we upload blocks to other peers + constexpr alert_category_t upload = 23_bit; + + // alerts on individual blocks being requested, downloading, finished, + // rejected, time-out and cancelled. This is likely to post alerts at a + // high rate. + constexpr alert_category_t block_progress = 24_bit; + + // The full bitmask, representing all available categories. + // + // since the enum is signed, make sure this isn't + // interpreted as -1. For instance, boost.python + // does that and fails when assigning it to an + // unsigned parameter. + constexpr alert_category_t all = alert_category_t::all(); + +} // namespace alert_category + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // The ``alert`` class is the base class that specific messages are derived from. + // alert types are not copyable, and cannot be constructed by the client. The + // pointers returned by libtorrent are short lived (the details are described + // under session_handle::pop_alerts()) + struct TORRENT_EXPORT alert + { +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // hidden + TORRENT_UNEXPORT alert(alert const& rhs) = delete; + alert& operator=(alert const&) = delete; + alert(alert&& rhs) noexcept = default; + +#if TORRENT_ABI_VERSION == 1 + using category_t = alert_category_t; +#endif + + static constexpr alert_category_t error_notification = 0_bit; + static constexpr alert_category_t peer_notification = 1_bit; + static constexpr alert_category_t port_mapping_notification = 2_bit; + static constexpr alert_category_t storage_notification = 3_bit; + static constexpr alert_category_t tracker_notification = 4_bit; + static constexpr alert_category_t connect_notification = 5_bit; +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + static constexpr alert_category_t debug_notification = connect_notification; +#endif + static constexpr alert_category_t status_notification = 6_bit; +#if TORRENT_ABI_VERSION == 1 + // Alerts for when blocks are requested and completed. Also when + // pieces are completed. + TORRENT_DEPRECATED + static constexpr alert_category_t progress_notification = 7_bit; +#endif + static constexpr alert_category_t ip_block_notification = 8_bit; + static constexpr alert_category_t performance_warning = 9_bit; + static constexpr alert_category_t dht_notification = 10_bit; + +#if TORRENT_ABI_VERSION <= 2 + // If you enable these alerts, you will receive a stats_alert + // approximately once every second, for every active torrent. + // These alerts contain all statistics counters for the interval since + // the lasts stats alert. + TORRENT_DEPRECATED + static constexpr alert_category_t stats_notification = 11_bit; +#endif + static constexpr alert_category_t session_log_notification = 13_bit; + static constexpr alert_category_t torrent_log_notification = 14_bit; + static constexpr alert_category_t peer_log_notification = 15_bit; + static constexpr alert_category_t incoming_request_notification = 16_bit; + static constexpr alert_category_t dht_log_notification = 17_bit; + static constexpr alert_category_t dht_operation_notification = 18_bit; + static constexpr alert_category_t port_mapping_log_notification = 19_bit; + static constexpr alert_category_t picker_log_notification = 20_bit; + static constexpr alert_category_t file_progress_notification = 21_bit; + static constexpr alert_category_t piece_progress_notification = 22_bit; + static constexpr alert_category_t upload_notification = 23_bit; + static constexpr alert_category_t block_progress_notification = 24_bit; + static constexpr alert_category_t all_categories = alert_category_t::all(); + + // hidden + TORRENT_UNEXPORT alert(); + // hidden + virtual ~alert(); + + // a timestamp is automatically created in the constructor + time_point timestamp() const; + + // returns an integer that is unique to this alert type. It can be + // compared against a specific alert by querying a static constant called ``alert_type`` + // in the alert. It can be used to determine the run-time type of an alert* in + // order to cast to that alert type and access specific members. + // + // e.g: + // + // .. code:: c++ + // + // std::vector alerts; + // ses.pop_alerts(&alerts); + // for (alert* a : alerts) { + // switch (a->type()) { + // + // case read_piece_alert::alert_type: + // { + // auto* p = static_cast(a); + // if (p->ec) { + // // read_piece failed + // break; + // } + // // use p + // break; + // } + // case file_renamed_alert::alert_type: + // { + // // etc... + // } + // } + // } + virtual int type() const noexcept = 0; + + // returns a string literal describing the type of the alert. It does + // not include any information that might be bundled with the alert. + virtual char const* what() const noexcept = 0; + + // generate a string describing the alert and the information bundled + // with it. This is mainly intended for debug and development use. It is not suitable + // to use this for applications that may be localized. Instead, handle each alert + // type individually and extract and render the information from the alert depending + // on the locale. + virtual std::string message() const = 0; + + // returns a bitmask specifying which categories this alert belong to. + virtual alert_category_t category() const noexcept = 0; + + private: + time_point const m_timestamp; + }; + +// When you get an alert, you can use ``alert_cast<>`` to attempt to cast the +// pointer to a specific alert type, in order to query it for more +// information. +// +// .. note:: +// ``alert_cast<>`` can only cast to an exact alert type, not a base class +template T* alert_cast(alert* a) +{ + static_assert(std::is_base_of::value + , "alert_cast<> can only be used with alert types (deriving from lt::alert)"); + + if (a == nullptr) return nullptr; + if (a->type() == T::alert_type) return static_cast(a); + return nullptr; +} +template T const* alert_cast(alert const* a) +{ + static_assert(std::is_base_of::value + , "alert_cast<> can only be used with alert types (deriving from lt::alert)"); + if (a == nullptr) return nullptr; + if (a->type() == T::alert_type) return static_cast(a); + return nullptr; +} + +} // namespace libtorrent + +#endif // TORRENT_ALERT_HPP_INCLUDED diff --git a/include/libtorrent/alert_types.hpp b/include/libtorrent/alert_types.hpp new file mode 100644 index 0000000..baa133f --- /dev/null +++ b/include/libtorrent/alert_types.hpp @@ -0,0 +1,3126 @@ +/* + +Copyright (c) 2017, toinetoine +Copyright (c) 2015, Thomas +Copyright (c) 2004-2022, Arvid Norberg +Copyright (c) 2008, Andrew Resch +Copyright (c) 2014-2018, Steven Siloti +Copyright (c) 2015-2018, Alden Torres +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2017, Antoine Dahan +Copyright (c) 2018, d-komarov +Copyright (c) 2019, ghbplayer +Copyright (c) 2020, AllSeeingEyeTolledEweSew +Copyright (c) 2020, Fonic +Copyright (c) 2020, Viktor Elofsson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALERT_TYPES_HPP_INCLUDED +#define TORRENT_ALERT_TYPES_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/torrent_status.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/operations.hpp" // for operation_t enum +#include "libtorrent/close_reason.hpp" +#include "libtorrent/piece_block.hpp" +#include "libtorrent/aux_/escape_string.hpp" // for convert_from_native +#include "libtorrent/string_view.hpp" +#include "libtorrent/stack_allocator.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" +#include "libtorrent/portmap.hpp" // for portmap_transport +#include "libtorrent/tracker_manager.hpp" // for event_t +#include "libtorrent/socket_type.hpp" +#include "libtorrent/client_data.hpp" +#include "libtorrent/peer_info.hpp" // for peer_info +#include "libtorrent/aux_/deprecated.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include // for va_list + +#if TORRENT_ABI_VERSION == 1 +#define PROGRESS_NOTIFICATION | alert::progress_notification +#else +#define PROGRESS_NOTIFICATION +#endif + + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED_EXPORT char const* operation_name(int op); +#endif + + // internal + TORRENT_EXPORT char const* alert_name(int alert_type); + + // user defined alerts should use IDs greater than this + constexpr int user_alert_id = 10000; + + // this constant represents "max_alert_index" + 1 + constexpr int num_alert_types = 105; + + // internal + constexpr int abi_alert_count = 128; + + // internal + enum class alert_priority : std::uint8_t + { + // the order matters here. Lower value means lower priority, and will + // start getting dropped earlier when the alert queue is filling up + normal = 0, + high, + critical, + meta + }; + + // struct to hold information about a single DHT routing table bucket + struct TORRENT_EXPORT dht_routing_bucket + { + // the total number of nodes and replacement nodes + // in the routing table + int num_nodes; + int num_replacements; + + // number of seconds since last activity + int last_active; + }; + +TORRENT_VERSION_NAMESPACE_3 + + // This is a base class for alerts that are associated with a + // specific torrent. It contains a handle to the torrent. + // + // Note that by the time the client receives a torrent_alert, its + // ``handle`` member may be invalid. + struct TORRENT_EXPORT torrent_alert : alert + { + // internal + TORRENT_UNEXPORT torrent_alert(aux::stack_allocator& alloc, torrent_handle const& h); + TORRENT_UNEXPORT torrent_alert(torrent_alert&&) noexcept = default; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED static int const alert_type = 0; +#endif + + // returns the message associated with this alert + std::string message() const override; + + // The torrent_handle pointing to the torrent this + // alert is associated with. + torrent_handle handle; + + char const* torrent_name() const; + + protected: + std::reference_wrapper m_alloc; + private: + aux::allocation_slot m_name_idx; +#if TORRENT_ABI_VERSION == 1 + public: + TORRENT_DEPRECATED std::string name; +#endif + }; + + // The peer alert is a base class for alerts that refer to a specific peer. It includes all + // the information to identify the peer. i.e. ``ip`` and ``peer-id``. + struct TORRENT_EXPORT peer_alert : torrent_alert + { + // internal + TORRENT_UNEXPORT peer_alert(aux::stack_allocator& alloc, torrent_handle const& h, + tcp::endpoint const& i, peer_id const& pi); + TORRENT_UNEXPORT peer_alert(peer_alert&& rhs) noexcept = default; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED static int const alert_type = 1; +#endif + + std::string message() const override; + + // The peer's IP address and port. + aux::noexcept_movable endpoint; + + // the peer ID, if known. + peer_id pid; + +#if TORRENT_ABI_VERSION == 1 + // The peer's IP address and port. + TORRENT_DEPRECATED aux::noexcept_movable ip; +#endif + }; + + // This is a base class used for alerts that are associated with a + // specific tracker. It derives from torrent_alert since a tracker + // is also associated with a specific torrent. + struct TORRENT_EXPORT tracker_alert : torrent_alert + { + // internal + TORRENT_UNEXPORT tracker_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep, string_view u); + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED static int const alert_type = 2; +#endif + + std::string message() const override; + + // endpoint of the listen interface being announced + aux::noexcept_movable local_endpoint; + + // returns a 0-terminated string of the tracker's URL + char const* tracker_url() const; + + private: + aux::allocation_slot m_url_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // The tracker URL + TORRENT_DEPRECATED std::string url; +#endif + }; + +#define TORRENT_DEFINE_ALERT_IMPL(name, seq, prio) \ + name(name&&) noexcept = default; \ + static alert_priority const priority = prio; \ + static int const alert_type = seq; \ + virtual int type() const noexcept override { return alert_type; } \ + virtual alert_category_t category() const noexcept override { return static_category; } \ + virtual char const* what() const noexcept override { return alert_name(alert_type); } + +#define TORRENT_DEFINE_ALERT(name, seq) \ + TORRENT_DEFINE_ALERT_IMPL(name, seq, alert_priority::normal) + +#define TORRENT_DEFINE_ALERT_PRIO(name, seq, prio) \ + TORRENT_DEFINE_ALERT_IMPL(name, seq, prio) + +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // The ``torrent_added_alert`` is posted once every time a torrent is successfully + // added. It doesn't contain any members of its own, but inherits the torrent handle + // from its base class. + // It's posted when the ``alert_category::status`` bit is set in the alert_mask. + // deprecated in 1.1.3 + // use add_torrent_alert instead + struct TORRENT_DEPRECATED_EXPORT torrent_added_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_added_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + TORRENT_DEFINE_ALERT(torrent_added_alert, 3) + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + }; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif + + // The ``torrent_removed_alert`` is posted whenever a torrent is removed. Since + // the torrent handle in its base class will usually be invalid (since the torrent + // is already removed) it has the info hash as a member, to identify it. + // It's posted when the ``alert_category::status`` bit is set in the alert_mask. + // + // Note that the ``handle`` remains valid for some time after + // torrent_removed_alert is posted, as long as some internal libtorrent + // task (such as an I/O task) refers to it. Additionally, other alerts like + // save_resume_data_alert may be posted after torrent_removed_alert. + // To synchronize on whether the torrent has been removed or not, call + // torrent_handle::in_session(). This will return true before + // torrent_removed_alert is posted, and false afterward. + // + // Even though the ``handle`` member doesn't point to an existing torrent anymore, + // it is still useful for comparing to other handles, which may also no + // longer point to existing torrents, but to the same non-existing torrents. + // + // The ``torrent_handle`` acts as a ``weak_ptr``, even though its object no + // longer exists, it can still compare equal to another weak pointer which + // points to the same non-existent object. + struct TORRENT_EXPORT torrent_removed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_removed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, info_hash_t const& ih, client_data_t userdata); + + TORRENT_DEFINE_ALERT_PRIO(torrent_removed_alert, 4, alert_priority::critical) + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; +#if TORRENT_ABI_VERSION < 3 + TORRENT_DEPRECATED sha1_hash info_hash; +#endif + info_hash_t info_hashes; + + // '`userdata`` as set in add_torrent_params at torrent creation. + // This can be used to associate this torrent with related data + // in the client application more efficiently than info_hashes. + client_data_t userdata; + }; + + // This alert is posted when the asynchronous read operation initiated by + // a call to torrent_handle::read_piece() is completed. If the read failed, the torrent + // is paused and an error state is set and the buffer member of the alert + // is 0. If successful, ``buffer`` points to a buffer containing all the data + // of the piece. ``piece`` is the piece index that was read. ``size`` is the + // number of bytes that was read. + // + // If the operation fails, ``error`` will indicate what went wrong. + struct TORRENT_EXPORT read_piece_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT read_piece_alert(aux::stack_allocator& alloc, torrent_handle const& h + , piece_index_t p, boost::shared_array d, int s); + TORRENT_UNEXPORT read_piece_alert(aux::stack_allocator& alloc, torrent_handle h + , piece_index_t p, error_code e); + + TORRENT_DEFINE_ALERT_PRIO(read_piece_alert, 5, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + error_code const error; + boost::shared_array const buffer; + piece_index_t const piece; + int const size; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED error_code ec; +#endif + }; + + // This is posted whenever an individual file completes its download. i.e. + // All pieces overlapping this file have passed their hash check. + struct TORRENT_EXPORT file_completed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT file_completed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , file_index_t idx); + + TORRENT_DEFINE_ALERT_PRIO(file_completed_alert, 6, alert_priority::normal) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + static constexpr alert_category_t static_category = + alert_category::file_progress + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + std::string message() const override; + + // refers to the index of the file that completed. + file_index_t const index; + }; + + // This is posted as a response to a torrent_handle::rename_file() call, if the rename + // operation succeeds. + struct TORRENT_EXPORT file_renamed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT file_renamed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , string_view n, string_view old, file_index_t idx); + + TORRENT_DEFINE_ALERT_PRIO(file_renamed_alert, 7, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + // returns the new and previous file name, respectively. + char const* new_name() const; + char const* old_name() const; + + // refers to the index of the file that was renamed, + file_index_t const index; + private: + aux::allocation_slot m_name_idx; + aux::allocation_slot m_old_name_idx; +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/aux_/disable_warnings_push.hpp" + + public: + TORRENT_DEPRECATED std::string name; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif + }; + + // This is posted as a response to a torrent_handle::rename_file() call, if the rename + // operation failed. + struct TORRENT_EXPORT file_rename_failed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT file_rename_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, file_index_t idx + , error_code ec); + + TORRENT_DEFINE_ALERT_PRIO(file_rename_failed_alert, 8, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::storage; + + std::string message() const override; + + // refers to the index of the file that was supposed to be renamed, + // ``error`` is the error code returned from the filesystem. + file_index_t const index; + error_code const error; + }; + + // This alert is generated when a limit is reached that might have a negative impact on + // upload or download rate performance. + struct TORRENT_EXPORT performance_alert final : torrent_alert + { + enum performance_warning_t + { + + // This warning means that the number of bytes queued to be written to disk + // exceeds the max disk byte queue setting (``settings_pack::max_queued_disk_bytes``). + // This might restrict the download rate, by not queuing up enough write jobs + // to the disk I/O thread. When this alert is posted, peer connections are + // temporarily stopped from downloading, until the queued disk bytes have fallen + // below the limit again. Unless your ``max_queued_disk_bytes`` setting is already + // high, you might want to increase it to get better performance. + outstanding_disk_buffer_limit_reached, + + // This is posted when libtorrent would like to send more requests to a peer, + // but it's limited by ``settings_pack::max_out_request_queue``. The queue length + // libtorrent is trying to achieve is determined by the download rate and the + // assumed round-trip-time (``settings_pack::request_queue_time``). The assumed + // round-trip-time is not limited to just the network RTT, but also the remote disk + // access time and message handling time. It defaults to 3 seconds. The target number + // of outstanding requests is set to fill the bandwidth-delay product (assumed RTT + // times download rate divided by number of bytes per request). When this alert + // is posted, there is a risk that the number of outstanding requests is too low + // and limits the download rate. You might want to increase the ``max_out_request_queue`` + // setting. + outstanding_request_limit_reached, + + // This warning is posted when the amount of TCP/IP overhead is greater than the + // upload rate limit. When this happens, the TCP/IP overhead is caused by a much + // faster download rate, triggering TCP ACK packets. These packets eat into the + // rate limit specified to libtorrent. When the overhead traffic is greater than + // the rate limit, libtorrent will not be able to send any actual payload, such + // as piece requests. This means the download rate will suffer, and new requests + // can be sent again. There will be an equilibrium where the download rate, on + // average, is about 20 times the upload rate limit. If you want to maximize the + // download rate, increase the upload rate limit above 5% of your download capacity. + upload_limit_too_low, + + // This is the same warning as ``upload_limit_too_low`` but referring to the download + // limit instead of upload. This suggests that your download rate limit is much lower + // than your upload capacity. Your upload rate will suffer. To maximize upload rate, + // make sure your download rate limit is above 5% of your upload capacity. + download_limit_too_low, + + // We're stalled on the disk. We want to write to the socket, and we can write + // but our send buffer is empty, waiting to be refilled from the disk. + // This either means the disk is slower than the network connection + // or that our send buffer watermark is too small, because we can + // send it all before the disk gets back to us. + // The number of bytes that we keep outstanding, requested from the disk, is calculated + // as follows: + // + // .. code:: C++ + // + // min(512, max(upload_rate * send_buffer_watermark_factor / 100, send_buffer_watermark)) + // + // If you receive this alert, you might want to either increase your ``send_buffer_watermark`` + // or ``send_buffer_watermark_factor``. + send_buffer_watermark_too_low, + + // If the half (or more) of all upload slots are set as optimistic unchoke slots, this + // warning is issued. You probably want more regular (rate based) unchoke slots. + too_many_optimistic_unchoke_slots, + + // If the disk write queue ever grows larger than half of the cache size, this warning + // is posted. The disk write queue eats into the total disk cache and leaves very little + // left for the actual cache. This causes the disk cache to oscillate in evicting large + // portions of the cache before allowing peers to download any more, onto the disk write + // queue. Either lower ``max_queued_disk_bytes`` or increase ``cache_size``. + too_high_disk_queue_limit, + + aio_limit_reached, +#if TORRENT_ABI_VERSION == 1 + bittyrant_with_no_uplimit TORRENT_DEPRECATED_ENUM, +#else + deprecated_bittyrant_with_no_uplimit, +#endif + + // This is generated if outgoing peer connections are failing because of *address in use* + // errors, indicating that ``settings_pack::outgoing_ports`` is set and is too small of + // a range. Consider not using the ``outgoing_ports`` setting at all, or widen the range to + // include more ports. + too_few_outgoing_ports, + + too_few_file_descriptors, + + num_warnings + }; + + // internal + TORRENT_UNEXPORT performance_alert(aux::stack_allocator& alloc, torrent_handle const& h + , performance_warning_t w); + + TORRENT_DEFINE_ALERT(performance_alert, 9) + + static constexpr alert_category_t static_category = alert_category::performance_warning; + + std::string message() const override; + + performance_warning_t const warning_code; + }; + + // Generated whenever a torrent changes its state. + struct TORRENT_EXPORT state_changed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT state_changed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , torrent_status::state_t st + , torrent_status::state_t prev_st); + + TORRENT_DEFINE_ALERT_PRIO(state_changed_alert, 10, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::status; + + std::string message() const override; + + // the new state of the torrent. + torrent_status::state_t const state; + + // the previous state. + torrent_status::state_t const prev_state; + }; + + // This alert is generated on tracker time outs, premature disconnects, + // invalid response or a HTTP response other than "200 OK". From the alert + // you can get the handle to the torrent the tracker belongs to. + struct TORRENT_EXPORT tracker_error_alert final : tracker_alert + { + // internal + TORRENT_UNEXPORT tracker_error_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , int times, protocol_version v, string_view u + , operation_t op, error_code const& e, string_view m); + + TORRENT_DEFINE_ALERT_PRIO(tracker_error_alert, 11, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::tracker | alert_category::error; + std::string message() const override; + + // This member says how many times in a row this tracker has failed. + int const times_in_row; + + // the error code indicating why the tracker announce failed. If it is + // is ``lt::errors::tracker_failure`` the failure_reason() might contain + // a more detailed description of why the tracker rejected the request. + // HTTP status codes indicating errors are also set in this field. + error_code const error; + + operation_t op; + + // if the tracker sent a "failure reason" string, it will be returned + // here. + char const* failure_reason() const; + + // hidden + char const* error_message() const { return failure_reason(); } + + private: + aux::allocation_slot m_msg_idx; +#if TORRENT_ABI_VERSION == 1 + public: + TORRENT_DEPRECATED int const status_code; + TORRENT_DEPRECATED std::string msg; +#endif + public: + // the bittorrent protocol version that was announced + protocol_version version; + }; + + // This alert is triggered if the tracker reply contains a warning field. + // Usually this means that the tracker announce was successful, but the + // tracker has a message to the client. + struct TORRENT_EXPORT tracker_warning_alert final : tracker_alert + { + // internal + TORRENT_UNEXPORT tracker_warning_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , string_view u, protocol_version v, string_view m); + + TORRENT_DEFINE_ALERT(tracker_warning_alert, 12) + + static constexpr alert_category_t static_category = alert_category::tracker | alert_category::error; + std::string message() const override; + + // the message associated with this warning + char const* warning_message() const; + + private: + aux::allocation_slot m_msg_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // contains the warning message from the tracker. + TORRENT_DEPRECATED std::string msg; +#endif + public: + // the bittorrent protocol version that was announced + protocol_version version; + }; + + // This alert is generated when a scrape request succeeds. + struct TORRENT_EXPORT scrape_reply_alert final : tracker_alert + { + // internal + TORRENT_UNEXPORT scrape_reply_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , int incomp, int comp, string_view u, protocol_version v); + + TORRENT_DEFINE_ALERT_PRIO(scrape_reply_alert, 13, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::tracker; + std::string message() const override; + + // the data returned in the scrape response. These numbers + // may be -1 if the response was malformed. + int const incomplete; + int const complete; + + // the bittorrent protocol version that was scraped + protocol_version version; + }; + + // If a scrape request fails, this alert is generated. This might be due + // to the tracker timing out, refusing connection or returning an http response + // code indicating an error. + struct TORRENT_EXPORT scrape_failed_alert final : tracker_alert + { + // internal + TORRENT_UNEXPORT scrape_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , string_view u, protocol_version v, error_code const& e); + TORRENT_UNEXPORT scrape_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , string_view u, string_view m); + + TORRENT_DEFINE_ALERT_PRIO(scrape_failed_alert, 14, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::tracker | alert_category::error; + std::string message() const override; + + // the error itself. This may indicate that the tracker sent an error + // message (``error::tracker_failure``), in which case it can be + // retrieved by calling ``error_message()``. + error_code const error; + + // if the error indicates there is an associated message, this returns + // that message. Otherwise and empty string. + char const* error_message() const; + + private: + aux::allocation_slot m_msg_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // contains a message describing the error. + TORRENT_DEPRECATED std::string msg; +#endif + public: + // the bittorrent protocol version that was scraped + protocol_version version; + }; + + // This alert is only for informational purpose. It is generated when a tracker announce + // succeeds. It is generated regardless what kind of tracker was used, be it UDP, HTTP or + // the DHT. + struct TORRENT_EXPORT tracker_reply_alert final : tracker_alert + { + // internal + TORRENT_UNEXPORT tracker_reply_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , int np, protocol_version v, string_view u); + + TORRENT_DEFINE_ALERT(tracker_reply_alert, 15) + + static constexpr alert_category_t static_category = alert_category::tracker; + std::string message() const override; + + // tells how many peers the tracker returned in this response. This is + // not expected to be greater than the ``num_want`` settings. These are not necessarily + // all new peers, some of them may already be connected. + int const num_peers; + + // the bittorrent protocol version that was announced + protocol_version version; + }; + + // This alert is generated each time the DHT receives peers from a node. ``num_peers`` + // is the number of peers we received in this packet. Typically these packets are + // received from multiple DHT nodes, and so the alerts are typically generated + // a few at a time. + struct TORRENT_EXPORT dht_reply_alert final : tracker_alert + { + // internal + TORRENT_UNEXPORT dht_reply_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , int np); + + TORRENT_DEFINE_ALERT(dht_reply_alert, 16) + + static constexpr alert_category_t static_category = alert_category::dht | alert_category::tracker; + std::string message() const override; + + int const num_peers; + }; + + // This alert is generated each time a tracker announce is sent (or attempted to be sent). + // There are no extra data members in this alert. The url can be found in the base class + // however. + struct TORRENT_EXPORT tracker_announce_alert final : tracker_alert + { + // internal + TORRENT_UNEXPORT tracker_announce_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , string_view u, protocol_version v, event_t e); + + TORRENT_DEFINE_ALERT(tracker_announce_alert, 17) + + static constexpr alert_category_t static_category = alert_category::tracker; + std::string message() const override; + + // specifies what event was sent to the tracker. See event_t. + event_t const event; + + // the bittorrent protocol version that is announced + protocol_version version; + }; + + // This alert is generated when a finished piece fails its hash check. You can get the handle + // to the torrent which got the failed piece and the index of the piece itself from the alert. + struct TORRENT_EXPORT hash_failed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT hash_failed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , piece_index_t index); + + TORRENT_DEFINE_ALERT(hash_failed_alert, 18) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + piece_index_t const piece_index; + }; + + // This alert is generated when a peer is banned because it has sent too many corrupt pieces + // to us. ``ip`` is the endpoint to the peer that was banned. + struct TORRENT_EXPORT peer_ban_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT peer_ban_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id); + + TORRENT_DEFINE_ALERT(peer_ban_alert, 19) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + }; + + // This alert is generated when a peer is un-snubbed. Essentially when it was snubbed for stalling + // sending data, and now it started sending data again. + struct TORRENT_EXPORT peer_unsnubbed_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT peer_unsnubbed_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id); + + TORRENT_DEFINE_ALERT(peer_unsnubbed_alert, 20) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + }; + + // This alert is generated when a peer is snubbed, when it stops sending data when we request + // it. + struct TORRENT_EXPORT peer_snubbed_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT peer_snubbed_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id); + + TORRENT_DEFINE_ALERT(peer_snubbed_alert, 21) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + }; + + // This alert is generated when a peer sends invalid data over the peer-peer protocol. The peer + // will be disconnected, but you get its ip address from the alert, to identify it. + struct TORRENT_EXPORT peer_error_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT peer_error_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep, peer_id const& peer_id, operation_t op + , error_code const& e); + + TORRENT_DEFINE_ALERT(peer_error_alert, 22) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + + // a 0-terminated string of the low-level operation that failed, or nullptr if + // there was no low level disk operation. + operation_t op; + + // tells you what error caused this alert. + error_code const error; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED int const operation; + TORRENT_DEPRECATED std::string msg; +#endif + }; + + // This alert is posted every time an incoming peer connection both + // successfully passes the protocol handshake and is associated with a + // torrent, or an outgoing peer connection attempt succeeds. For arbitrary + // incoming connections, see incoming_connection_alert. + struct TORRENT_EXPORT peer_connect_alert final : peer_alert + { + enum class direction_t { in, out }; + + // internal + TORRENT_UNEXPORT peer_connect_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, socket_type_t type, direction_t direction); + + TORRENT_DEFINE_ALERT(peer_connect_alert, 23) + + static constexpr alert_category_t static_category = alert_category::connect; + std::string message() const override; + + // Tells you if the peer was incoming or outgoing + direction_t direction; + + socket_type_t socket_type; + }; + + // This alert is generated when a peer is disconnected for any reason (other than the ones + // covered by peer_error_alert ). + struct TORRENT_EXPORT peer_disconnected_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT peer_disconnected_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , peer_id const& peer_id, operation_t op, socket_type_t type, error_code const& e + , close_reason_t r); + + TORRENT_DEFINE_ALERT(peer_disconnected_alert, 24) + + static constexpr alert_category_t static_category = alert_category::connect; + std::string message() const override; + + // the kind of socket this peer was connected over + socket_type_t const socket_type; + + // the operation or level where the error occurred. Specified as an + // value from the operation_t enum. Defined in operations.hpp. + operation_t const op; + + // tells you what error caused peer to disconnect. + error_code const error; + + // the reason the peer disconnected (if specified) + close_reason_t const reason; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED int const operation; + TORRENT_DEPRECATED std::string msg; +#endif + }; + + // This is a debug alert that is generated by an incoming invalid piece request. + // ``ip`` is the address of the peer and the ``request`` is the actual incoming + // request from the peer. See peer_request for more info. + struct TORRENT_EXPORT invalid_request_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT invalid_request_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , peer_id const& peer_id, peer_request const& r + , bool we_have, bool peer_interested, bool withheld); + + TORRENT_DEFINE_ALERT(invalid_request_alert, 25) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + + // the request we received from the peer + peer_request const request; + + // true if we have this piece + bool const we_have; + + // true if the peer indicated that it was interested to download before + // sending the request + bool const peer_interested; + + // if this is true, the peer is not allowed to download this piece because + // of super-seeding rules. + bool const withheld; + }; + + // This alert is generated when a torrent switches from being a downloader to a seed. + // It will only be generated once per torrent. It contains a torrent_handle to the + // torrent in question. + struct TORRENT_EXPORT torrent_finished_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_finished_alert(aux::stack_allocator& alloc, + torrent_handle h); + + TORRENT_DEFINE_ALERT_PRIO(torrent_finished_alert, 26, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + }; + + // this alert is posted every time a piece completes downloading + // and passes the hash check. This alert derives from torrent_alert + // which contains the torrent_handle to the torrent the piece belongs to. + // Note that being downloaded and passing the hash check may happen before + // the piece is also fully flushed to disk. So torrent_handle::have_piece() + // may still return false + struct TORRENT_EXPORT piece_finished_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT piece_finished_alert(aux::stack_allocator& alloc, + torrent_handle const& h, piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(piece_finished_alert, 27) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + static constexpr alert_category_t static_category = + alert_category::piece_progress + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + std::string message() const override; + + // the index of the piece that finished + piece_index_t const piece_index; + }; + + // This alert is generated when a peer rejects or ignores a piece request. + struct TORRENT_EXPORT request_dropped_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT request_dropped_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(request_dropped_alert, 28) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + static constexpr alert_category_t static_category = + alert_category::block_progress + | alert_category::peer + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + std::string message() const override; + + int const block_index; + piece_index_t const piece_index; + }; + + // This alert is generated when a block request times out. + struct TORRENT_EXPORT block_timeout_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT block_timeout_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(block_timeout_alert, 29) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + static constexpr alert_category_t static_category = + alert_category::block_progress + | alert_category::peer + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + std::string message() const override; + + int const block_index; + piece_index_t const piece_index; + }; + + // This alert is generated when a block request receives a response. + struct TORRENT_EXPORT block_finished_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT block_finished_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(block_finished_alert, 30) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + static constexpr alert_category_t static_category = + alert_category::block_progress + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + std::string message() const override; + + int const block_index; + piece_index_t const piece_index; + }; + + // This alert is generated when a block request is sent to a peer. + struct TORRENT_EXPORT block_downloading_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT block_downloading_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep + , peer_id const& peer_id, int block_num, piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(block_downloading_alert, 31) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + static constexpr alert_category_t static_category = + alert_category::block_progress + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + std::string message() const override; + + int const block_index; + piece_index_t const piece_index; +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED char const* peer_speedmsg; +#endif + }; + + // This alert is generated when a block is received that was not requested or + // whose request timed out. + struct TORRENT_EXPORT unwanted_block_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT unwanted_block_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep + , peer_id const& peer_id, int block_num, piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(unwanted_block_alert, 32) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + + int const block_index; + piece_index_t const piece_index; + }; + + // The ``storage_moved_alert`` is generated when all the disk IO has + // completed and the files have been moved, as an effect of a call to + // ``torrent_handle::move_storage``. This is useful to synchronize with the + // actual disk. The ``storage_path()`` member return the new path of the + // storage. + struct TORRENT_EXPORT storage_moved_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT storage_moved_alert(aux::stack_allocator& alloc + , torrent_handle const& h, string_view p, string_view old); + + TORRENT_DEFINE_ALERT_PRIO(storage_moved_alert, 33, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + // the path the torrent was moved to and from, respectively. + char const* storage_path() const; + char const* old_path() const; + + private: + aux::allocation_slot m_path_idx; + aux::allocation_slot m_old_path_idx; +#if TORRENT_ABI_VERSION == 1 + public: + TORRENT_DEPRECATED std::string path; +#endif + }; + + // The ``storage_moved_failed_alert`` is generated when an attempt to move the storage, + // via torrent_handle::move_storage(), fails. + struct TORRENT_EXPORT storage_moved_failed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT storage_moved_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& e, string_view file + , operation_t op); + + TORRENT_DEFINE_ALERT_PRIO(storage_moved_failed_alert, 34, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + error_code const error; + + // If the error happened for a specific file, this returns its path. + char const* file_path() const; + + // this indicates what underlying operation caused the error + operation_t op; + private: + aux::allocation_slot m_file_idx; +#if TORRENT_ABI_VERSION == 1 + public: + TORRENT_DEPRECATED char const* operation; + // If the error happened for a specific file, ``file`` is its path. + TORRENT_DEPRECATED std::string file; +#endif + }; + + // This alert is generated when a request to delete the files of a torrent complete. + // + // This alert is posted in the ``alert_category::storage`` category, and that bit + // needs to be set in the alert_mask. + struct TORRENT_EXPORT torrent_deleted_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_deleted_alert(aux::stack_allocator& alloc + , torrent_handle const& h, info_hash_t const& ih); + + TORRENT_DEFINE_ALERT_PRIO(torrent_deleted_alert, 35, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + +#if TORRENT_ABI_VERSION < 3 + TORRENT_DEPRECATED sha1_hash info_hash; +#endif + + // The info-hash of the torrent that was just deleted. Most of + // the time the torrent_handle in the ``torrent_alert`` will be invalid by the time + // this alert arrives, since the torrent is being deleted. The ``info_hashes`` member + // is hence the main way of identifying which torrent just completed the delete. + info_hash_t info_hashes; + }; + + // This alert is generated when a request to delete the files of a torrent fails. + // Just removing a torrent from the session cannot fail + struct TORRENT_EXPORT torrent_delete_failed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_delete_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& e, info_hash_t const& ih); + + TORRENT_DEFINE_ALERT_PRIO(torrent_delete_failed_alert, 36, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::storage + | alert_category::error; + std::string message() const override; + + // tells you why it failed. + error_code const error; + +#if TORRENT_ABI_VERSION < 3 + TORRENT_DEPRECATED sha1_hash info_hash; +#endif + // the info hash of the torrent whose files failed to be deleted + info_hash_t info_hashes; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED std::string msg; +#endif + }; + + // This alert is generated as a response to a ``torrent_handle::save_resume_data`` request. + // It is generated once the disk IO thread is done writing the state for this torrent. + struct TORRENT_EXPORT save_resume_data_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT save_resume_data_alert(aux::stack_allocator& alloc + , add_torrent_params&& params + , torrent_handle const& h); + TORRENT_UNEXPORT save_resume_data_alert(aux::stack_allocator& alloc + , add_torrent_params const& params + , torrent_handle const& h) = delete; + + TORRENT_DEFINE_ALERT_PRIO(save_resume_data_alert, 37, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + // the ``params`` object is populated with the torrent file whose resume + // data was saved. It is suitable to be: + // + // * added to a session with add_torrent() or async_add_torrent() + // * saved to disk with write_resume_data() + // * turned into a magnet link with make_magnet_uri() + // * saved as a .torrent file with write_torrent_file() + add_torrent_params params; + +#if TORRENT_ABI_VERSION == 1 + // points to the resume data. + TORRENT_DEPRECATED std::shared_ptr resume_data; +#endif + }; + + // This alert is generated instead of ``save_resume_data_alert`` if there was an error + // generating the resume data. ``error`` describes what went wrong. + struct TORRENT_EXPORT save_resume_data_failed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT save_resume_data_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& e); + + TORRENT_DEFINE_ALERT_PRIO(save_resume_data_failed_alert, 38, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::storage + | alert_category::error; + std::string message() const override; + + // the error code from the resume_data failure + error_code const error; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED std::string msg; +#endif + }; + + // This alert is generated as a response to a ``torrent_handle::pause`` request. It is + // generated once all disk IO is complete and the files in the torrent have been closed. + // This is useful for synchronizing with the disk. + struct TORRENT_EXPORT torrent_paused_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_paused_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + TORRENT_DEFINE_ALERT_PRIO(torrent_paused_alert, 39, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + }; + + // This alert is generated as a response to a torrent_handle::resume() request. It is + // generated when a torrent goes from a paused state to an active state. + struct TORRENT_EXPORT torrent_resumed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_resumed_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + TORRENT_DEFINE_ALERT_PRIO(torrent_resumed_alert, 40, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + }; + + // This alert is posted when a torrent completes checking. i.e. when it transitions + // out of the ``checking files`` state into a state where it is ready to start downloading + struct TORRENT_EXPORT torrent_checked_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_checked_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + TORRENT_DEFINE_ALERT_PRIO(torrent_checked_alert, 41, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + }; + + // This alert is generated when a HTTP seed name lookup fails. + struct TORRENT_EXPORT url_seed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT url_seed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , string_view u, error_code const& e); + TORRENT_UNEXPORT url_seed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , string_view u, string_view m); + + TORRENT_DEFINE_ALERT(url_seed_alert, 42) + + static constexpr alert_category_t static_category = alert_category::peer | alert_category::error; + std::string message() const override; + + // the error the web seed encountered. If this is not set, the server + // sent an error message, call ``error_message()``. + error_code const error; + + // the URL the error is associated with + char const* server_url() const; + + // in case the web server sent an error message, this function returns + // it. + char const* error_message() const; + + private: + aux::allocation_slot m_url_idx; + aux::allocation_slot m_msg_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // the HTTP seed that failed + TORRENT_DEPRECATED std::string url; + + // the error message, potentially from the server + TORRENT_DEPRECATED std::string msg; +#endif + + }; + + // If the storage fails to read or write files that it needs access to, this alert is + // generated and the torrent is paused. + struct TORRENT_EXPORT file_error_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT file_error_alert(aux::stack_allocator& alloc, error_code const& ec + , string_view file, operation_t op, torrent_handle const& h); + + TORRENT_DEFINE_ALERT_PRIO(file_error_alert, 43, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::status + | alert_category::error + | alert_category::storage; + std::string message() const override; + + // the error code describing the error. + error_code const error; + + // indicates which underlying operation caused the error + operation_t op; + + // the file that experienced the error + char const* filename() const; + + private: + aux::allocation_slot m_file_idx; +#if TORRENT_ABI_VERSION == 1 + public: + TORRENT_DEPRECATED char const* operation; + // the path to the file that was accessed when the error occurred. + TORRENT_DEPRECATED std::string file; + TORRENT_DEPRECATED std::string msg; +#endif + }; + + // This alert is generated when the metadata has been completely received and the info-hash + // failed to match it. i.e. the metadata that was received was corrupt. libtorrent will + // automatically retry to fetch it in this case. This is only relevant when running a + // torrent-less download, with the metadata extension provided by libtorrent. + struct TORRENT_EXPORT metadata_failed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT metadata_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& ec); + + TORRENT_DEFINE_ALERT(metadata_failed_alert, 44) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // indicates what failed when parsing the metadata. This error is + // what's returned from lazy_bdecode(). + error_code const error; + }; + + // This alert is generated when the metadata has been completely received and the torrent + // can start downloading. It is not generated on torrents that are started with metadata, but + // only those that needs to download it from peers (when utilizing the libtorrent extension). + // + // There are no additional data members in this alert. + // + // Typically, when receiving this alert, you would want to save the torrent file in order + // to load it back up again when the session is restarted. Here's an example snippet of + // code to do that: + // + // .. code:: c++ + // + // torrent_handle h = alert->handle; + // std::shared_ptr ti = h.torrent_file(); + // create_torrent ct(*ti); + // entry te = ct.generate(); + // std::vector buffer; + // bencode(std::back_inserter(buffer), te); + // FILE* f = fopen((to_hex(ti->info_hashes().get_best().to_string()) + ".torrent").c_str(), "wb+"); + // if (f) { + // fwrite(&buffer[0], 1, buffer.size(), f); + // fclose(f); + // } + // + struct TORRENT_EXPORT metadata_received_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT metadata_received_alert(aux::stack_allocator& alloc + , torrent_handle const& h); + + TORRENT_DEFINE_ALERT(metadata_received_alert, 45) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + }; + + // This alert is posted when there is an error on a UDP socket. The + // UDP sockets are used for all uTP, DHT and UDP tracker traffic. They are + // global to the session. + struct TORRENT_EXPORT udp_error_alert final : alert + { + // internal + TORRENT_UNEXPORT udp_error_alert( + aux::stack_allocator& alloc + , udp::endpoint const& ep + , operation_t op + , error_code const& ec); + + TORRENT_DEFINE_ALERT(udp_error_alert, 46) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // the source address associated with the error (if any) + aux::noexcept_movable endpoint; + + // the operation that failed + operation_t operation; + + // the error code describing the error + error_code const error; + }; + + // Whenever libtorrent learns about the machines external IP, this alert is + // generated. The external IP address can be acquired from the tracker (if it + // supports that) or from peers that supports the extension protocol. + // The address can be accessed through the ``external_address`` member. + struct TORRENT_EXPORT external_ip_alert final : alert + { + // internal + TORRENT_UNEXPORT external_ip_alert(aux::stack_allocator& alloc, address const& ip); + + TORRENT_DEFINE_ALERT(external_ip_alert, 47) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // the IP address that is believed to be our external IP + aux::noexcept_movable
    external_address; + }; + + // This alert is generated when none of the ports, given in the port range, to + // session can be opened for listening. The ``listen_interface`` member is the + // interface that failed, ``error`` is the error code describing the failure. + // + // In the case an endpoint was created before generating the alert, it is + // represented by ``address`` and ``port``. The combinations of socket type + // and operation in which such address and port are not valid are: + // accept - i2p + // accept - socks5 + // enum_if - tcp + // + // libtorrent may sometimes try to listen on port 0, if all other ports failed. + // Port 0 asks the operating system to pick a port that's free). If that fails + // you may see a listen_failed_alert with port 0 even if you didn't ask to + // listen on it. + struct TORRENT_EXPORT listen_failed_alert final : alert + { +#if TORRENT_ABI_VERSION == 1 + enum socket_type_t : std::uint8_t + { + tcp TORRENT_DEPRECATED_ENUM, + tcp_ssl TORRENT_DEPRECATED_ENUM, + udp TORRENT_DEPRECATED_ENUM, + i2p TORRENT_DEPRECATED_ENUM, + socks5 TORRENT_DEPRECATED_ENUM, + utp_ssl TORRENT_DEPRECATED_ENUM + }; +#endif + + // internal + TORRENT_UNEXPORT listen_failed_alert(aux::stack_allocator& alloc, string_view iface + , lt::address const& listen_addr, int listen_port + , operation_t op, error_code const& ec, lt::socket_type_t t); + TORRENT_UNEXPORT listen_failed_alert(aux::stack_allocator& alloc, string_view iface + , tcp::endpoint const& ep, operation_t op, error_code const& ec + , lt::socket_type_t t); + TORRENT_UNEXPORT listen_failed_alert(aux::stack_allocator& alloc, string_view iface + , udp::endpoint const& ep, operation_t op, error_code const& ec + , lt::socket_type_t t); + TORRENT_UNEXPORT listen_failed_alert(aux::stack_allocator& alloc, string_view iface + , operation_t op, error_code const& ec, lt::socket_type_t t); + + TORRENT_DEFINE_ALERT_PRIO(listen_failed_alert, 48, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::status | alert_category::error; + std::string message() const override; + + // the network device libtorrent attempted to listen on, or the IP address + char const* listen_interface() const; + + // the error the system returned + error_code const error; + + // the underlying operation that failed + operation_t op; + + // the type of listen socket this alert refers to. + lt::socket_type_t const socket_type; + + // the address libtorrent attempted to listen on + // see alert documentation for validity of this value + aux::noexcept_movable address; + + // the port libtorrent attempted to listen on + // see alert documentation for validity of this value + int const port; + + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_interface_idx; +#if TORRENT_ABI_VERSION == 1 + public: + enum TORRENT_DEPRECATED_ENUM op_t + { + parse_addr TORRENT_DEPRECATED_ENUM, + open TORRENT_DEPRECATED_ENUM, + bind TORRENT_DEPRECATED_ENUM, + listen TORRENT_DEPRECATED_ENUM, + get_socket_name TORRENT_DEPRECATED_ENUM, + accept TORRENT_DEPRECATED_ENUM, + enum_if TORRENT_DEPRECATED_ENUM, + bind_to_device TORRENT_DEPRECATED_ENUM + }; + + // the specific low level operation that failed. See op_t. + TORRENT_DEPRECATED int const operation; + + // the address and port libtorrent attempted to listen on + TORRENT_DEPRECATED aux::noexcept_movable endpoint; + + // the type of listen socket this alert refers to. + TORRENT_DEPRECATED socket_type_t sock_type; +#endif + }; + + // This alert is posted when the listen port succeeds to be opened on a + // particular interface. ``address`` and ``port`` is the endpoint that + // successfully was opened for listening. + struct TORRENT_EXPORT listen_succeeded_alert final : alert + { +#if TORRENT_ABI_VERSION == 1 + enum socket_type_t : std::uint8_t + { + tcp TORRENT_DEPRECATED_ENUM, + tcp_ssl TORRENT_DEPRECATED_ENUM, + udp TORRENT_DEPRECATED_ENUM, + i2p TORRENT_DEPRECATED_ENUM, + socks5 TORRENT_DEPRECATED_ENUM, + utp_ssl TORRENT_DEPRECATED_ENUM + }; +#endif + + // internal + TORRENT_UNEXPORT listen_succeeded_alert(aux::stack_allocator& alloc + , lt::address const& listen_addr + , int listen_port + , lt::socket_type_t t); + TORRENT_UNEXPORT listen_succeeded_alert(aux::stack_allocator& alloc + , tcp::endpoint const& ep + , lt::socket_type_t t); + TORRENT_UNEXPORT listen_succeeded_alert(aux::stack_allocator& alloc + , udp::endpoint const& ep + , lt::socket_type_t t); + + TORRENT_DEFINE_ALERT_PRIO(listen_succeeded_alert, 49, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // the address libtorrent ended up listening on. This address + // refers to the local interface. + aux::noexcept_movable address; + + // the port libtorrent ended up listening on. + int const port; + + // the type of listen socket this alert refers to. + lt::socket_type_t const socket_type; + +#if TORRENT_ABI_VERSION == 1 + // the endpoint libtorrent ended up listening on. The address + // refers to the local interface and the port is the listen port. + TORRENT_DEPRECATED aux::noexcept_movable endpoint; + + // the type of listen socket this alert refers to. + TORRENT_DEPRECATED socket_type_t sock_type; +#endif + }; + + // This alert is generated when a NAT router was successfully found but some + // part of the port mapping request failed. It contains a text message that + // may help the user figure out what is wrong. This alert is not generated in + // case it appears the client is not running on a NAT:ed network or if it + // appears there is no NAT router that can be remote controlled to add port + // mappings. + struct TORRENT_EXPORT portmap_error_alert final : alert + { + // internal + TORRENT_UNEXPORT portmap_error_alert(aux::stack_allocator& alloc, port_mapping_t i + , portmap_transport t, error_code const& e, address const& local); + + TORRENT_DEFINE_ALERT(portmap_error_alert, 50) + + static constexpr alert_category_t static_category = alert_category::port_mapping + | alert_category::error; + std::string message() const override; + + // refers to the mapping index of the port map that failed, i.e. + // the index returned from add_mapping(). + port_mapping_t const mapping; + + // UPnP or NAT-PMP + portmap_transport map_transport; + + // the local network the port mapper is running on + aux::noexcept_movable
    local_address; + + // tells you what failed. + error_code const error; +#if TORRENT_ABI_VERSION == 1 + // is 0 for NAT-PMP and 1 for UPnP. + TORRENT_DEPRECATED int const map_type; + + TORRENT_DEPRECATED std::string msg; +#endif + }; + + // This alert is generated when a NAT router was successfully found and + // a port was successfully mapped on it. On a NAT:ed network with a NAT-PMP + // capable router, this is typically generated once when mapping the TCP + // port and, if DHT is enabled, when the UDP port is mapped. + struct TORRENT_EXPORT portmap_alert final : alert + { + // internal + TORRENT_UNEXPORT portmap_alert(aux::stack_allocator& alloc, port_mapping_t i, int port + , portmap_transport t, portmap_protocol protocol, address const& local); + + TORRENT_DEFINE_ALERT(portmap_alert, 51) + + static constexpr alert_category_t static_category = alert_category::port_mapping; + std::string message() const override; + + // refers to the mapping index of the port map that failed, i.e. + // the index returned from add_mapping(). + port_mapping_t const mapping; + + // the external port allocated for the mapping. + int const external_port; + + portmap_protocol const map_protocol; + + portmap_transport const map_transport; + + // the local network the port mapper is running on + aux::noexcept_movable
    local_address; + +#if TORRENT_ABI_VERSION == 1 + enum TORRENT_DEPRECATED_ENUM protocol_t + { + tcp, + udp + }; + + // the protocol this mapping was for. one of protocol_t enums + TORRENT_DEPRECATED int const protocol; + + // 0 for NAT-PMP and 1 for UPnP. + TORRENT_DEPRECATED int const map_type; +#endif + }; + + // This alert is generated to log informational events related to either + // UPnP or NAT-PMP. They contain a log line and the type (0 = NAT-PMP + // and 1 = UPnP). Displaying these messages to an end user is only useful + // for debugging the UPnP or NAT-PMP implementation. This alert is only + // posted if the alert_category::port_mapping_log flag is enabled in + // the alert mask. + struct TORRENT_EXPORT portmap_log_alert final : alert + { + // internal + TORRENT_UNEXPORT portmap_log_alert(aux::stack_allocator& alloc, portmap_transport t + , const char* m, address const& local); + + TORRENT_DEFINE_ALERT(portmap_log_alert, 52) + + static constexpr alert_category_t static_category = alert_category::port_mapping_log; + std::string message() const override; + + portmap_transport const map_transport; + + // the local network the port mapper is running on + aux::noexcept_movable
    local_address; + + // the message associated with this log line + char const* log_message() const; + + private: + + std::reference_wrapper m_alloc; + + aux::allocation_slot m_log_idx; +#if TORRENT_ABI_VERSION == 1 + public: + TORRENT_DEPRECATED int const map_type; + TORRENT_DEPRECATED std::string msg; +#endif + + }; + + // This alert is generated when a fast resume file has been passed to + // add_torrent() but the files on disk did not match the fast resume file. + // The error_code explains the reason why the resume file was rejected. + struct TORRENT_EXPORT fastresume_rejected_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT fastresume_rejected_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& ec, string_view file + , operation_t op); + + TORRENT_DEFINE_ALERT_PRIO(fastresume_rejected_alert, 53, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::status + | alert_category::error; + std::string message() const override; + + error_code error; + + // If the error happened to a specific file, this returns the path to it. + char const* file_path() const; + + // the underlying operation that failed + operation_t op; + + private: + aux::allocation_slot m_path_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // If the error happened in a disk operation. a 0-terminated string of + // the name of that operation. ``operation`` is nullptr otherwise. + TORRENT_DEPRECATED char const* operation; + + // If the error happened to a specific file, ``file`` is the path to it. + TORRENT_DEPRECATED std::string file; + TORRENT_DEPRECATED std::string msg; +#endif + }; + + // This alert is posted when an incoming peer connection, or a peer that's about to be added + // to our peer list, is blocked for some reason. This could be any of: + // + // * the IP filter + // * i2p mixed mode restrictions (a normal peer is not allowed on an i2p swarm) + // * the port filter + // * the peer has a low port and ``no_connect_privileged_ports`` is enabled + // * the protocol of the peer is blocked (uTP/TCP blocking) + struct TORRENT_EXPORT peer_blocked_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT peer_blocked_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep, int r); + + TORRENT_DEFINE_ALERT(peer_blocked_alert, 54) + + static constexpr alert_category_t static_category = alert_category::ip_block; + std::string message() const override; + + enum reason_t + { + ip_filter, + port_filter, + i2p_mixed, + privileged_ports, + utp_disabled, + tcp_disabled, + invalid_local_interface, + ssrf_mitigation + }; + + // the reason for the peer being blocked. Is one of the values from the + // reason_t enum. + int const reason; + }; + + // This alert is generated when a DHT node announces to an info-hash on our + // DHT node. It belongs to the ``alert_category::dht`` category. + struct TORRENT_EXPORT dht_announce_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_announce_alert(aux::stack_allocator& alloc, address const& i, int p + , sha1_hash const& ih); + + TORRENT_DEFINE_ALERT(dht_announce_alert, 55) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + aux::noexcept_movable
    ip; + int port; + sha1_hash info_hash; + }; + + // This alert is generated when a DHT node sends a ``get_peers`` message to + // our DHT node. It belongs to the ``alert_category::dht`` category. + struct TORRENT_EXPORT dht_get_peers_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_get_peers_alert(aux::stack_allocator& alloc, sha1_hash const& ih); + + TORRENT_DEFINE_ALERT(dht_get_peers_alert, 56) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + sha1_hash info_hash; + }; + +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // This alert is posted approximately once every second, and it contains + // byte counters of most statistics that's tracked for torrents. Each active + // torrent posts these alerts regularly. + // This alert has been superseded by calling ``post_torrent_updates()`` + // regularly on the session object. This alert will be removed + struct TORRENT_DEPRECATED_EXPORT stats_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT stats_alert(aux::stack_allocator& alloc, torrent_handle const& h, int interval + , stat const& s); + + TORRENT_DEFINE_ALERT(stats_alert, 57) + + static constexpr alert_category_t static_category = alert_category::stats; + std::string message() const override; + + enum stats_channel + { + upload_payload, + upload_protocol, + download_payload, + download_protocol, + upload_ip_protocol, +#if TORRENT_ABI_VERSION == 1 + upload_dht_protocol TORRENT_DEPRECATED_ENUM, + upload_tracker_protocol TORRENT_DEPRECATED_ENUM, +#else + deprecated1, + deprecated2, +#endif + download_ip_protocol, +#if TORRENT_ABI_VERSION == 1 + download_dht_protocol TORRENT_DEPRECATED_ENUM, + download_tracker_protocol TORRENT_DEPRECATED_ENUM, +#else + deprecated3, + deprecated4, +#endif + num_channels + }; + + // an array of samples. The enum describes what each sample is a + // measurement of. All of these are raw, and not smoothing is performed. + std::array const transferred; + + // the number of milliseconds during which these stats were collected. + // This is typically just above 1000, but if CPU is limited, it may be + // higher than that. + int const interval; + }; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif // TORRENT_ABI_VERSION + + // This alert is posted when the disk cache has been flushed for a specific + // torrent as a result of a call to torrent_handle::flush_cache(). This + // alert belongs to the ``alert_category::storage`` category, which must be + // enabled to let this alert through. The alert is also posted when removing + // a torrent from the session, once the outstanding cache flush is complete + // and the torrent does no longer have any files open. + struct TORRENT_EXPORT cache_flushed_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT cache_flushed_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + TORRENT_DEFINE_ALERT_PRIO(cache_flushed_alert, 58, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::storage; + }; + +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // This alert is posted when a bittorrent feature is blocked because of the + // anonymous mode. For instance, if the tracker proxy is not set up, no + // trackers will be used, because trackers can only be used through proxies + // when in anonymous mode. + struct TORRENT_DEPRECATED_EXPORT anonymous_mode_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT anonymous_mode_alert(aux::stack_allocator& alloc, torrent_handle const& h + , int k, string_view s); + + TORRENT_DEFINE_ALERT(anonymous_mode_alert, 59) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + enum kind_t + { + // means that there's no proxy set up for tracker + // communication and the tracker will not be contacted. + // The tracker which this failed for is specified in the ``str`` member. + tracker_not_anonymous = 0 + }; + + // specifies what error this is, see kind_t. + int kind; + std::string str; + }; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif // TORRENT_ABI_VERSION + + // This alert is generated when we receive a local service discovery message + // from a peer for a torrent we're currently participating in. + struct TORRENT_EXPORT lsd_peer_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT lsd_peer_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& i); + + TORRENT_DEFINE_ALERT(lsd_peer_alert, 60) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + }; + + // This alert is posted whenever a tracker responds with a ``trackerid``. + // The tracker ID is like a cookie. libtorrent will store the tracker ID + // for this tracker and repeat it in subsequent announces. + struct TORRENT_EXPORT trackerid_alert final : tracker_alert + { + // internal + TORRENT_UNEXPORT trackerid_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep , string_view u, const std::string& id); + + TORRENT_DEFINE_ALERT(trackerid_alert, 61) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // The tracker ID returned by the tracker + char const* tracker_id() const; + + private: + aux::allocation_slot m_tracker_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // The tracker ID returned by the tracker + TORRENT_DEPRECATED std::string trackerid; +#endif + }; + + // This alert is posted when the initial DHT bootstrap is done. + struct TORRENT_EXPORT dht_bootstrap_alert final : alert + { + // internal + explicit TORRENT_UNEXPORT dht_bootstrap_alert(aux::stack_allocator& alloc); + + TORRENT_DEFINE_ALERT(dht_bootstrap_alert, 62) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + }; + + // This is posted whenever a torrent is transitioned into the error state. + // If the error code is duplicate_torrent (error_code_enum) error, it suggests two magnet + // links ended up resolving to the same hybrid torrent. For more details, + // see BitTorrent-v2-torrents_. + struct TORRENT_EXPORT torrent_error_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_error_alert(aux::stack_allocator& alloc, torrent_handle const& h + , error_code const& e, string_view f); + + TORRENT_DEFINE_ALERT_PRIO(torrent_error_alert, 64, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::error | alert_category::status; + std::string message() const override; + + // specifies which error the torrent encountered. + error_code const error; + + // the filename (or object) the error occurred on. + char const* filename() const; + + private: + aux::allocation_slot m_file_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // the filename (or object) the error occurred on. + TORRENT_DEPRECATED std::string error_file; +#endif + + }; + + // This is always posted for SSL torrents. This is a reminder to the client that + // the torrent won't work unless torrent_handle::set_ssl_certificate() is called with + // a valid certificate. Valid certificates MUST be signed by the SSL certificate + // in the .torrent file. + struct TORRENT_EXPORT torrent_need_cert_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_need_cert_alert(aux::stack_allocator& alloc + , torrent_handle const& h); + + TORRENT_DEFINE_ALERT_PRIO(torrent_need_cert_alert, 65, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED error_code const error; +#endif + }; + + // The incoming connection alert is posted every time we successfully accept + // an incoming connection, through any mean. The most straight-forward ways + // of accepting incoming connections are through the TCP listen socket and + // the UDP listen socket for uTP sockets. However, connections may also be + // accepted through a Socks5 or i2p listen socket, or via an SSL listen + // socket. + struct TORRENT_EXPORT incoming_connection_alert final : alert + { + // internal + TORRENT_UNEXPORT incoming_connection_alert(aux::stack_allocator& alloc + , socket_type_t t, tcp::endpoint const& i); + + TORRENT_DEFINE_ALERT(incoming_connection_alert, 66) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + + // tells you what kind of socket the connection was accepted + socket_type_t socket_type; + + // is the IP address and port the connection came from. + aux::noexcept_movable endpoint; + +#if TORRENT_ABI_VERSION == 1 + // is the IP address and port the connection came from. + TORRENT_DEPRECATED aux::noexcept_movable ip; +#endif + }; + + // This alert is always posted when a torrent was attempted to be added + // and contains the return status of the add operation. The torrent handle of the new + // torrent can be found as the ``handle`` member in the base class. If adding + // the torrent failed, ``error`` contains the error code. + struct TORRENT_EXPORT add_torrent_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT add_torrent_alert(aux::stack_allocator& alloc, torrent_handle const& h + , add_torrent_params p, error_code const& ec); + + TORRENT_DEFINE_ALERT_PRIO(add_torrent_alert, 67, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // This contains copies of the most important fields from the original + // add_torrent_params object, passed to add_torrent() or + // async_add_torrent(). Specifically, these fields are copied: + // + // * version + // * ti + // * name + // * save_path + // * userdata + // * tracker_id + // * flags + // * info_hash + // + // the info_hash field will be updated with the info-hash of the torrent + // specified by ``ti``. + add_torrent_params params; + + // set to the error, if one occurred while adding the torrent. + error_code error; + }; + + // This alert is only posted when requested by the user, by calling + // session::post_torrent_updates() on the session. It contains the torrent + // status of all torrents that changed since last time this message was + // posted. Its category is ``alert_category::status``, but it's not subject to + // filtering, since it's only manually posted anyway. + struct TORRENT_EXPORT state_update_alert final : alert + { + // internal + TORRENT_UNEXPORT state_update_alert(aux::stack_allocator& alloc + , std::vector st); + + TORRENT_DEFINE_ALERT_PRIO(state_update_alert, 68, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // contains the torrent status of all torrents that changed since last + // time this message was posted. Note that you can map a torrent status + // to a specific torrent via its ``handle`` member. The receiving end is + // suggested to have all torrents sorted by the torrent_handle or hashed + // by it, for efficient updates. + std::vector status; + }; + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + struct TORRENT_DEPRECATED_EXPORT mmap_cache_alert final : alert + { + mmap_cache_alert(aux::stack_allocator& alloc + , error_code const& ec); + TORRENT_DEFINE_ALERT(mmap_cache_alert, 69) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + error_code const error; + }; +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // TORRENT_ABI_VERSION + + // The session_stats_alert is posted when the user requests session statistics by + // calling post_session_stats() on the session object. This alert does not + // have a category, since it's only posted in response to an API call. It + // is not subject to the alert_mask filter. + // + // the ``message()`` member function returns a string representation of the values that + // properly match the line returned in ``session_stats_header_alert::message()``. + // + // this specific output is parsed by tools/parse_session_stats.py + // if this is changed, that parser should also be changed + struct TORRENT_EXPORT session_stats_alert final : alert + { + // internal + TORRENT_UNEXPORT session_stats_alert(aux::stack_allocator& alloc, counters const& cnt); + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" +#endif + + TORRENT_DEFINE_ALERT_PRIO(session_stats_alert, 70, alert_priority::critical) + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + + static constexpr alert_category_t static_category = {}; + std::string message() const override; + + // An array are a mix of *counters* and *gauges*, which meanings can be + // queries via the session_stats_metrics() function on the session. The + // mapping from a specific metric to an index into this array is constant + // for a specific version of libtorrent, but may differ for other + // versions. The intended usage is to request the mapping, i.e. call + // session_stats_metrics(), once on startup, and then use that mapping to + // interpret these values throughout the process' runtime. + // + // For more information, see the session-statistics_ section. + span counters() const; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED std::array const values; +#else + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_counters_idx; +#endif + }; + + // posted when something fails in the DHT. This is not necessarily a fatal + // error, but it could prevent proper operation + struct TORRENT_EXPORT dht_error_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_error_alert(aux::stack_allocator& alloc, operation_t op + , error_code const& ec); + + TORRENT_DEFINE_ALERT(dht_error_alert, 73) + + static constexpr alert_category_t static_category = alert_category::error | alert_category::dht; + std::string message() const override; + + // the error code + error_code error; + + // the operation that failed + operation_t op; + +#if TORRENT_ABI_VERSION == 1 + enum op_t + { + unknown TORRENT_DEPRECATED_ENUM, + hostname_lookup TORRENT_DEPRECATED_ENUM + }; + + // the operation that failed + TORRENT_DEPRECATED op_t const operation; +#endif + }; + + // this alert is posted as a response to a call to session::get_item(), + // specifically the overload for looking up immutable items in the DHT. + struct TORRENT_EXPORT dht_immutable_item_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_immutable_item_alert(aux::stack_allocator& alloc, sha1_hash const& t + , entry i); + + TORRENT_DEFINE_ALERT_PRIO(dht_immutable_item_alert, 74, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::dht; + + std::string message() const override; + + // the target hash of the immutable item. This must + // match the SHA-1 hash of the bencoded form of ``item``. + sha1_hash target; + + // the data for this item + entry item; + }; + + // this alert is posted as a response to a call to session::get_item(), + // specifically the overload for looking up mutable items in the DHT. + struct TORRENT_EXPORT dht_mutable_item_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_mutable_item_alert(aux::stack_allocator& alloc + , std::array const& k, std::array const& sig + , std::int64_t sequence, string_view s, entry i, bool a); + + TORRENT_DEFINE_ALERT_PRIO(dht_mutable_item_alert, 75, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + // the public key that was looked up + std::array key; + + // the signature of the data. This is not the signature of the + // plain encoded form of the item, but it includes the sequence number + // and possibly the hash as well. See the dht_store document for more + // information. This is primarily useful for echoing back in a store + // request. + std::array signature; + + // the sequence number of this item + std::int64_t seq; + + // the salt, if any, used to lookup and store this item. If no + // salt was used, this is an empty string + std::string salt; + + // the data for this item + entry item; + + // the last response for mutable data is authoritative. + bool authoritative; + }; + + // this is posted when a DHT put operation completes. This is useful if the + // client is waiting for a put to complete before shutting down for instance. + struct TORRENT_EXPORT dht_put_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_put_alert(aux::stack_allocator& alloc, sha1_hash const& t, int n); + TORRENT_UNEXPORT dht_put_alert(aux::stack_allocator& alloc, std::array const& key + , std::array const& sig + , std::string s + , std::int64_t sequence_number + , int n); + + TORRENT_DEFINE_ALERT(dht_put_alert, 76) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + // the target hash the item was stored under if this was an *immutable* + // item. + sha1_hash target; + + // if a mutable item was stored, these are the public key, signature, + // salt and sequence number the item was stored under. + std::array public_key; + std::array signature; + std::string salt; + std::int64_t seq; + + // DHT put operation usually writes item to k nodes, maybe the node + // is stale so no response, or the node doesn't support 'put', or the + // token for write is out of date, etc. num_success is the number of + // successful responses we got from the puts. + int num_success; + }; + + // this alert is used to report errors in the i2p SAM connection + struct TORRENT_EXPORT i2p_alert final : alert + { + // internal + TORRENT_UNEXPORT i2p_alert(aux::stack_allocator& alloc, error_code const& ec); + + TORRENT_DEFINE_ALERT(i2p_alert, 77) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // the error that occurred in the i2p SAM connection + error_code error; + }; + + // This alert is generated when we send a get_peers request + // It belongs to the ``alert_category::dht`` category. + struct TORRENT_EXPORT dht_outgoing_get_peers_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_outgoing_get_peers_alert(aux::stack_allocator& alloc + , sha1_hash const& ih, sha1_hash const& obfih + , udp::endpoint ep); + + TORRENT_DEFINE_ALERT(dht_outgoing_get_peers_alert, 78) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + // the info_hash of the torrent we're looking for peers for. + sha1_hash info_hash; + + // if this was an obfuscated lookup, this is the info-hash target + // actually sent to the node. + sha1_hash obfuscated_info_hash; + + // the endpoint we're sending this query to + aux::noexcept_movable endpoint; + +#if TORRENT_ABI_VERSION == 1 + // the endpoint we're sending this query to + TORRENT_DEPRECATED aux::noexcept_movable ip; +#endif + }; + + // This alert is posted by some session wide event. Its main purpose is + // trouble shooting and debugging. It's not enabled by the default alert + // mask and is enabled by the ``alert_category::session_log`` bit. + // Furthermore, it's by default disabled as a build configuration. + struct TORRENT_EXPORT log_alert final : alert + { + // internal + TORRENT_UNEXPORT log_alert(aux::stack_allocator& alloc, char const* log); + TORRENT_UNEXPORT log_alert(aux::stack_allocator& alloc, char const* fmt, va_list v); + + TORRENT_DEFINE_ALERT(log_alert, 79) + + static constexpr alert_category_t static_category = alert_category::session_log; + std::string message() const override; + + // returns the log message + char const* log_message() const; + +#if TORRENT_ABI_VERSION == 1 + // returns the log message + TORRENT_DEPRECATED + char const* msg() const; +#endif + + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_str_idx; + }; + + // This alert is posted by torrent wide events. It's meant to be used for + // trouble shooting and debugging. It's not enabled by the default alert + // mask and is enabled by the ``alert_category::torrent_log`` bit. By + // default it is disabled as a build configuration. + struct TORRENT_EXPORT torrent_log_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT torrent_log_alert(aux::stack_allocator& alloc, torrent_handle const& h + , char const* fmt, va_list v); + + TORRENT_DEFINE_ALERT(torrent_log_alert, 80) + + static constexpr alert_category_t static_category = alert_category::torrent_log; + std::string message() const override; + + // returns the log message + char const* log_message() const; + +#if TORRENT_ABI_VERSION == 1 + // returns the log message + TORRENT_DEPRECATED + char const* msg() const; +#endif + + private: + aux::allocation_slot m_str_idx; + }; + + // This alert is posted by events specific to a peer. It's meant to be used + // for trouble shooting and debugging. It's not enabled by the default alert + // mask and is enabled by the ``alert_category::peer_log`` bit. By + // default it is disabled as a build configuration. + struct TORRENT_EXPORT peer_log_alert final : peer_alert + { + // describes whether this log refers to in-flow or out-flow of the + // peer. The exception is ``info`` which is neither incoming or outgoing. + enum direction_t + { + incoming_message, + outgoing_message, + incoming, + outgoing, + info + }; + + // internal + TORRENT_UNEXPORT peer_log_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& i, peer_id const& pi + , peer_log_alert::direction_t dir + , char const* event, char const* fmt, va_list v); + + TORRENT_DEFINE_ALERT(peer_log_alert, 81) + + static constexpr alert_category_t static_category = alert_category::peer_log; + std::string message() const override; + + // string literal indicating the kind of event. For messages, this is the + // message name. + char const* event_type; + + direction_t direction; + + // returns the log message + char const* log_message() const; + +#if TORRENT_ABI_VERSION == 1 + // returns the log message + TORRENT_DEPRECATED + char const* msg() const; +#endif + + private: + aux::allocation_slot m_str_idx; + }; + + // posted if the local service discovery socket fails to start properly. + // it's categorized as ``alert_category::error``. + struct TORRENT_EXPORT lsd_error_alert final : alert + { + // internal + TORRENT_UNEXPORT lsd_error_alert(aux::stack_allocator& alloc, error_code const& ec + , address const& local); + + TORRENT_DEFINE_ALERT(lsd_error_alert, 82) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // the local network the corresponding local service discovery is running + // on + aux::noexcept_movable
    local_address; + + // The error code + error_code error; + }; + + // holds statistics about a current dht_lookup operation. + // a DHT lookup is the traversal of nodes, looking up a + // set of target nodes in the DHT for retrieving and possibly + // storing information in the DHT + struct TORRENT_EXPORT dht_lookup + { + // string literal indicating which kind of lookup this is + char const* type; + + // the number of outstanding request to individual nodes + // this lookup has right now + int outstanding_requests; + + // the total number of requests that have timed out so far + // for this lookup + int timeouts; + + // the total number of responses we have received for this + // lookup so far for this lookup + int responses; + + // the branch factor for this lookup. This is the number of + // nodes we keep outstanding requests to in parallel by default. + // when nodes time out we may increase this. + int branch_factor; + + // the number of nodes left that could be queries for this + // lookup. Many of these are likely to be part of the trail + // while performing the lookup and would never end up actually + // being queried. + int nodes_left; + + // the number of seconds ago the + // last message was sent that's still + // outstanding + int last_sent; + + // the number of outstanding requests + // that have exceeded the short timeout + // and are considered timed out in the + // sense that they increased the branch + // factor + int first_timeout; + + // the node-id or info-hash target for this lookup + sha1_hash target; + }; + + // contains current DHT state. Posted in response to session::post_dht_stats(). + struct TORRENT_EXPORT dht_stats_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_stats_alert(aux::stack_allocator& alloc + , std::vector table + , std::vector requests + , sha1_hash id, udp::endpoint ep); + + TORRENT_DEFINE_ALERT(dht_stats_alert, 83) + + static constexpr alert_category_t static_category = {}; + std::string message() const override; + + // a vector of the currently running DHT lookups. + std::vector active_requests; + + // contains information about every bucket in the DHT routing + // table. + std::vector routing_table; + + // the node ID of the DHT node instance + sha1_hash nid; + + // the local socket this DHT node is running on + aux::noexcept_movable local_endpoint; + }; + + // posted every time an incoming request from a peer is accepted and queued + // up for being serviced. This alert is only posted if + // the alert_category::incoming_request flag is enabled in the alert + // mask. + struct TORRENT_EXPORT incoming_request_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT incoming_request_alert(aux::stack_allocator& alloc + , peer_request r, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id); + + static constexpr alert_category_t static_category = alert_category::incoming_request; + TORRENT_DEFINE_ALERT(incoming_request_alert, 84) + + std::string message() const override; + + // the request this peer sent to us + peer_request req; + }; + + // debug logging of the DHT when alert_category::dht_log is set in the alert + // mask. + struct TORRENT_EXPORT dht_log_alert final : alert + { + enum dht_module_t + { + tracker, + node, + routing_table, + rpc_manager, + traversal + }; + + // internal + TORRENT_UNEXPORT dht_log_alert(aux::stack_allocator& alloc + , dht_module_t m, char const* fmt, va_list v); + + static constexpr alert_category_t static_category = alert_category::dht_log; + TORRENT_DEFINE_ALERT(dht_log_alert, 85) + + std::string message() const override; + + // the log message + char const* log_message() const; + + // the module, or part, of the DHT that produced this log message. + dht_module_t module; + + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_msg_idx; + }; + + // This alert is posted every time a DHT message is sent or received. It is + // only posted if the ``alert_category::dht_log`` alert category is + // enabled. It contains a verbatim copy of the message. + struct TORRENT_EXPORT dht_pkt_alert final : alert + { + enum direction_t + { incoming, outgoing }; + + // internal + TORRENT_UNEXPORT dht_pkt_alert(aux::stack_allocator& alloc, span buf + , dht_pkt_alert::direction_t d, udp::endpoint const& ep); + + static constexpr alert_category_t static_category = alert_category::dht_log; + TORRENT_DEFINE_ALERT(dht_pkt_alert, 86) + + std::string message() const override; + + // returns a pointer to the packet buffer and size of the packet, + // respectively. This buffer is only valid for as long as the alert itself + // is valid, which is owned by libtorrent and reclaimed whenever + // pop_alerts() is called on the session. + span pkt_buf() const; + + // whether this is an incoming or outgoing packet. + direction_t direction; + + // the DHT node we received this packet from, or sent this packet to + // (depending on ``direction``). + aux::noexcept_movable node; + + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_msg_idx; + int const m_size; +#if TORRENT_ABI_VERSION == 1 + public: + TORRENT_DEPRECATED direction_t dir; +#endif + + }; + + // Posted when we receive a response to a DHT get_peers request. + struct TORRENT_EXPORT dht_get_peers_reply_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_get_peers_reply_alert(aux::stack_allocator& alloc + , sha1_hash const& ih + , std::vector const& peers); + + static constexpr alert_category_t static_category = alert_category::dht_operation; + TORRENT_DEFINE_ALERT(dht_get_peers_reply_alert, 87) + + std::string message() const override; + + sha1_hash info_hash; + + int num_peers() const; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + void peers(std::vector& v) const; +#endif + std::vector peers() const; + + private: + std::reference_wrapper m_alloc; + int m_v4_num_peers = 0; + int m_v6_num_peers = 0; + aux::allocation_slot m_v4_peers_idx; + aux::allocation_slot m_v6_peers_idx; + }; + + // This is posted exactly once for every call to session_handle::dht_direct_request. + // If the request failed, response() will return a default constructed bdecode_node. + struct TORRENT_EXPORT dht_direct_response_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_direct_response_alert(aux::stack_allocator& alloc, client_data_t userdata + , udp::endpoint const& addr, bdecode_node const& response); + + // internal + // for when there was a timeout so we don't have a response + TORRENT_UNEXPORT dht_direct_response_alert(aux::stack_allocator& alloc, client_data_t userdata + , udp::endpoint const& addr); + + TORRENT_DEFINE_ALERT_PRIO(dht_direct_response_alert, 88, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + client_data_t userdata; + aux::noexcept_movable endpoint; + + bdecode_node response() const; + + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_response_idx; + int const m_response_size; +#if TORRENT_ABI_VERSION == 1 + public: + TORRENT_DEPRECATED aux::noexcept_movable addr; +#endif + }; + + // hidden + using picker_flags_t = flags::bitfield_flag; + + // this is posted when one or more blocks are picked by the piece picker, + // assuming the verbose piece picker logging is enabled (see + // alert_category::picker_log). + struct TORRENT_EXPORT picker_log_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT picker_log_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep, peer_id const& peer_id, picker_flags_t flags + , span blocks); + + TORRENT_DEFINE_ALERT(picker_log_alert, 89) + + static constexpr alert_category_t static_category = alert_category::picker_log; + std::string message() const override; + + static constexpr picker_flags_t partial_ratio = 0_bit; + static constexpr picker_flags_t prioritize_partials = 1_bit; + static constexpr picker_flags_t rarest_first_partials = 2_bit; + static constexpr picker_flags_t rarest_first = 3_bit; + static constexpr picker_flags_t reverse_rarest_first = 4_bit; + static constexpr picker_flags_t suggested_pieces = 5_bit; + static constexpr picker_flags_t prio_sequential_pieces = 6_bit; + static constexpr picker_flags_t sequential_pieces = 7_bit; + static constexpr picker_flags_t reverse_pieces = 8_bit; + static constexpr picker_flags_t time_critical = 9_bit; + static constexpr picker_flags_t random_pieces = 10_bit; + static constexpr picker_flags_t prefer_contiguous = 11_bit; + static constexpr picker_flags_t reverse_sequential = 12_bit; + static constexpr picker_flags_t backup1 = 13_bit; + static constexpr picker_flags_t backup2 = 14_bit; + static constexpr picker_flags_t end_game = 15_bit; + static constexpr picker_flags_t extent_affinity = 16_bit; + + // this is a bitmask of which features were enabled for this particular + // pick. The bits are defined in the picker_flags_t enum. + picker_flags_t const picker_flags; + + std::vector blocks() const; + + private: + aux::allocation_slot m_array_idx; + int const m_num_blocks; + }; + + // this alert is posted when the session encounters a serious error, + // potentially fatal + struct TORRENT_EXPORT session_error_alert final : alert + { + // internal + TORRENT_UNEXPORT session_error_alert(aux::stack_allocator& alloc, error_code err + , string_view error_str); + + TORRENT_DEFINE_ALERT(session_error_alert, 90) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // The error code, if one is associated with this error + error_code const error; + + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_msg_idx; + }; + + // posted in response to a call to session::dht_live_nodes(). It contains the + // live nodes from the DHT routing table of one of the DHT nodes running + // locally. + struct TORRENT_EXPORT dht_live_nodes_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_live_nodes_alert(aux::stack_allocator& alloc + , sha1_hash const& nid + , std::vector> const& nodes); + + TORRENT_DEFINE_ALERT(dht_live_nodes_alert, 91) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + // the local DHT node's node-ID this routing table belongs to + sha1_hash node_id; + + // the number of nodes in the routing table and the actual nodes. + int num_nodes() const; + std::vector> nodes() const; + + private: + std::reference_wrapper m_alloc; + int m_v4_num_nodes = 0; + int m_v6_num_nodes = 0; + aux::allocation_slot m_v4_nodes_idx; + aux::allocation_slot m_v6_nodes_idx; + }; + + // The session_stats_header alert is posted the first time + // post_session_stats() is called + // + // the ``message()`` member function returns a string representation of the + // header that properly match the stats values string returned in + // ``session_stats_alert::message()``. + // + // this specific output is parsed by tools/parse_session_stats.py + // if this is changed, that parser should also be changed + struct TORRENT_EXPORT session_stats_header_alert final : alert + { + // internal + explicit TORRENT_UNEXPORT session_stats_header_alert(aux::stack_allocator& alloc); + TORRENT_DEFINE_ALERT(session_stats_header_alert, 92) + + static constexpr alert_category_t static_category = {}; + std::string message() const override; + }; + + // posted as a response to a call to session::dht_sample_infohashes() with + // the information from the DHT response message. + struct TORRENT_EXPORT dht_sample_infohashes_alert final : alert + { + // internal + TORRENT_UNEXPORT dht_sample_infohashes_alert(aux::stack_allocator& alloc + , sha1_hash const& nid + , udp::endpoint const& endp + , time_duration interval + , int num + , std::vector const& samples + , std::vector> const& nodes); + + static constexpr alert_category_t static_category = alert_category::dht_operation; + TORRENT_DEFINE_ALERT(dht_sample_infohashes_alert, 93) + + std::string message() const override; + + // id of the node the request was sent to (and this response was received from) + sha1_hash node_id; + + // the node the request was sent to (and this response was received from) + aux::noexcept_movable endpoint; + + // the interval to wait before making another request to this node + time_duration const interval; + + // This field indicates how many info-hash keys are currently in the node's storage. + // If the value is larger than the number of returned samples it indicates that the + // indexer may obtain additional samples after waiting out the interval. + int const num_infohashes; + + // returns the number of info-hashes returned by the node, as well as the + // actual info-hashes. ``num_samples()`` is more efficient than + // ``samples().size()``. + int num_samples() const; + std::vector samples() const; + + // The total number of nodes returned by ``nodes()``. + int num_nodes() const; + + // This is the set of more DHT nodes returned by the request. + // + // The information is included so that indexing nodes can perform a key + // space traversal with a single RPC per node by adjusting the target + // value for each RPC. + std::vector> nodes() const; + + private: + std::reference_wrapper m_alloc; + int const m_num_samples; + aux::allocation_slot m_samples_idx; + int m_v4_num_nodes = 0; + int m_v6_num_nodes = 0; + aux::allocation_slot m_v4_nodes_idx; + aux::allocation_slot m_v6_nodes_idx; + }; + + // This alert is posted when a block intended to be sent to a peer is placed in the + // send buffer. Note that if the connection is closed before the send buffer is sent, + // the alert may be posted without the bytes having been sent to the peer. + // It belongs to the ``alert_category::upload`` category. + struct TORRENT_EXPORT block_uploaded_alert final : peer_alert + { + // internal + TORRENT_UNEXPORT block_uploaded_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(block_uploaded_alert, 94) + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + static constexpr alert_category_t static_category = + alert_category::upload + PROGRESS_NOTIFICATION + ; +#include "libtorrent/aux_/disable_warnings_pop.hpp" + std::string message() const override; + + int const block_index; + piece_index_t const piece_index; + }; + + // this alert is posted to indicate to the client that some alerts were + // dropped. Dropped meaning that the alert failed to be delivered to the + // client. The most common cause of such failure is that the internal alert + // queue grew too big (controlled by alert_queue_size). + struct TORRENT_EXPORT alerts_dropped_alert final : alert + { + // internal + explicit TORRENT_UNEXPORT alerts_dropped_alert(aux::stack_allocator& alloc + , std::bitset const&); + TORRENT_DEFINE_ALERT_PRIO(alerts_dropped_alert, 95, alert_priority::meta) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // a bitmask indicating which alerts were dropped. Each bit represents the + // alert type ID, where bit 0 represents whether any alert of type 0 has + // been dropped, and so on. + std::bitset dropped_alerts; + static_assert(num_alert_types <= abi_alert_count, "need to increase bitset. This is an ABI break"); + }; + + // this alert is posted with SOCKS5 related errors, when a SOCKS5 proxy is + // configured. It's enabled with the alert_category::error alert category. + struct TORRENT_EXPORT socks5_alert final : alert + { + // internal + explicit socks5_alert(aux::stack_allocator& alloc + , tcp::endpoint const& ep, operation_t operation, error_code const& ec); + TORRENT_DEFINE_ALERT(socks5_alert, 96) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // the error + error_code error; + + // the operation that failed + operation_t op; + + // the endpoint configured as the proxy + aux::noexcept_movable ip; + }; + + // posted when a prioritize_files() or file_priority() update of the file + // priorities complete, which requires a round-trip to the disk thread. + // + // If the disk operation fails this alert won't be posted, but a + // file_error_alert is posted instead, and the torrent is stopped. + struct TORRENT_EXPORT file_prio_alert final : torrent_alert + { + // internal + explicit file_prio_alert(aux::stack_allocator& alloc, torrent_handle h); + TORRENT_DEFINE_ALERT(file_prio_alert, 97) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + // the error + error_code error; + + // the operation that failed + operation_t op; + }; + +TORRENT_VERSION_NAMESPACE_3_END + + // this alert may be posted when the initial checking of resume data and files + // on disk (just existence, not piece hashes) completes. If a file belonging + // to the torrent is found on disk, but is larger than the file in the + // torrent, that's when this alert is posted. + // the client may want to call truncate_files() in that case, or perhaps + // interpret it as a sign that some other file is in the way, that shouldn't + // be overwritten. + struct TORRENT_EXPORT oversized_file_alert final : torrent_alert + { + // internal + explicit oversized_file_alert(aux::stack_allocator& alloc, torrent_handle h); + TORRENT_DEFINE_ALERT(oversized_file_alert, 98) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + // hidden + file_index_t reserved; + }; + + // this alert is posted when two separate torrents (magnet links) resolve to + // the same torrent, thus causing the same torrent being added twice. In + // that case, both torrents enter an error state with ``duplicate_torrent`` + // as the error code. This alert is posted containing the metadata. For more + // information, see BitTorrent-v2-torrents_. + // The torrent this alert originated from was the one that downloaded the + // + // metadata (i.e. the `handle` member from the torrent_alert base class). + struct TORRENT_EXPORT torrent_conflict_alert final : torrent_alert + { + // internal + explicit torrent_conflict_alert(aux::stack_allocator& alloc, torrent_handle h1 + , torrent_handle h2, std::shared_ptr ti); + TORRENT_DEFINE_ALERT_PRIO(torrent_conflict_alert, 99, alert_priority::high) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // the handle to the torrent in conflict. The swarm associated with this + // torrent handle did not download the metadata, but the downloaded + // metadata collided with this swarm's info-hash. + torrent_handle conflicting_torrent; + + // the metadata that was received by one of the torrents in conflict. + // One way to resolve the conflict is to remove both failing torrents + // and re-add it using this metadata + std::shared_ptr metadata; + }; + + // posted when torrent_handle::post_peer_info() is called + struct TORRENT_EXPORT peer_info_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT peer_info_alert(aux::stack_allocator& alloc, torrent_handle h + , std::vector p); + TORRENT_DEFINE_ALERT_PRIO(peer_info_alert, 100, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // the list of the currently connected peers + std::vector peer_info; + }; + + // posted when torrent_handle::post_file_progress() is called + struct TORRENT_EXPORT file_progress_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT file_progress_alert(aux::stack_allocator& alloc, torrent_handle h + , aux::vector fp); + TORRENT_DEFINE_ALERT_PRIO(file_progress_alert, 101, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::file_progress; + std::string message() const override; + + // the list of the files in the torrent + aux::vector files; + }; + + // posted when torrent_handle::post_download_queue() is called + struct TORRENT_EXPORT piece_info_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT piece_info_alert(aux::stack_allocator& alloc, torrent_handle h + , std::vector pi, std::vector&& bd); + TORRENT_DEFINE_ALERT_PRIO(piece_info_alert, 102, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::piece_progress; + std::string message() const override; + + // info about pieces being downloaded for the torrent + std::vector piece_info; + + // storage for block_info pointers in partial_piece_info objects + std::vector block_data; + }; + + // posted when torrent_handle::post_piece_availability() is called + struct TORRENT_EXPORT piece_availability_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT piece_availability_alert(aux::stack_allocator& alloc, torrent_handle h + , std::vector pa); + TORRENT_DEFINE_ALERT_PRIO(piece_availability_alert, 103, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // info about pieces being downloaded for the torrent + std::vector piece_availability; + }; + + // posted when torrent_handle::post_trackers() is called + struct TORRENT_EXPORT tracker_list_alert final : torrent_alert + { + // internal + TORRENT_UNEXPORT tracker_list_alert(aux::stack_allocator& alloc, torrent_handle h + , std::vector t); + TORRENT_DEFINE_ALERT_PRIO(tracker_list_alert, 104, alert_priority::critical) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // list of trackers and their status for the torrent + std::vector trackers; + }; + + // internal + TORRENT_EXTRA_EXPORT char const* performance_warning_str(performance_alert::performance_warning_t i); + + +#undef TORRENT_DEFINE_ALERT_IMPL +#undef TORRENT_DEFINE_ALERT +#undef TORRENT_DEFINE_ALERT_PRIO +#undef PROGRESS_NOTIFICATION + +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/announce_entry.hpp b/include/libtorrent/announce_entry.hpp new file mode 100644 index 0000000..1f3ce25 --- /dev/null +++ b/include/libtorrent/announce_entry.hpp @@ -0,0 +1,291 @@ +/* + +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2016, 2018, 2020, Alden Torres +Copyright (c) 2017-2018, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ANNOUNCE_ENTRY_HPP_INCLUDED +#define TORRENT_ANNOUNCE_ENTRY_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/array.hpp" +#include "libtorrent/info_hash.hpp" + +#include +#include +#include + +namespace libtorrent { + + struct torrent; + +TORRENT_VERSION_NAMESPACE_2 + + struct TORRENT_EXPORT announce_infohash + { + // internal + TORRENT_UNEXPORT announce_infohash(); + + // if this tracker has returned an error or warning message + // that message is stored here + std::string message; + + // if this tracker failed the last time it was contacted + // this error code specifies what error occurred + error_code last_error; + + // the time of next tracker announce + time_point32 next_announce = (time_point32::min)(); + + // no announces before this time + time_point32 min_announce = (time_point32::min)(); + + // TODO: include the number of peers received from this tracker, at last + // announce + + // these are either -1 or the scrape information this tracker last + // responded with. *incomplete* is the current number of downloaders in + // the swarm, *complete* is the current number of seeds in the swarm and + // *downloaded* is the cumulative number of completed downloads of this + // torrent, since the beginning of time (from this tracker's point of + // view). + + // if this tracker has returned scrape data, these fields are filled in + // with valid numbers. Otherwise they are set to -1. ``incomplete`` counts + // the number of current downloaders. ``complete`` counts the number of + // current peers completed the download, or "seeds". ``downloaded`` is the + // cumulative number of completed downloads. + int scrape_incomplete = -1; + int scrape_complete = -1; + int scrape_downloaded = -1; + + // the number of times in a row we have failed to announce to this + // tracker. + std::uint8_t fails : 7; + + // true while we're waiting for a response from the tracker. + bool updating : 1; + + // set to true when we get a valid response from an announce + // with event=started. If it is set, we won't send start in the subsequent + // announces. + bool start_sent : 1; + + // set to true when we send a event=completed. + bool complete_sent : 1; + + // internal + bool triggered_manually : 1; + +#if TORRENT_ABI_VERSION <= 2 + // reset announce counters and clears the started sent flag. + // The announce_endpoint will look like we've never talked to + // the tracker. + TORRENT_DEPRECATED void reset(); + + // updates the failure counter and time-outs for re-trying. + // This is called when the tracker announce fails. + TORRENT_DEPRECATED void failed(int backoff_ratio, seconds32 retry_interval = seconds32(0)); + + // returns true if we can announce to this tracker now. + // The current time is passed in as ``now``. The ``is_seed`` + // argument is necessary because once we become a seed, we + // need to announce right away, even if the re-announce timer + // hasn't expired yet. + TORRENT_DEPRECATED bool can_announce(time_point now, bool is_seed, std::uint8_t fail_limit) const; + + // returns true if the last time we tried to announce to this + // tracker succeeded, or if we haven't tried yet. + TORRENT_DEPRECATED bool is_working() const { return fails == 0; } +#endif + }; + + // announces are sent to each tracker using every listen socket + // this class holds information about one listen socket for one tracker +#if TORRENT_ABI_VERSION <= 2 + // this is to suppress deprecation warnings from implicit move constructor +#include "libtorrent/aux_/disable_warnings_push.hpp" +#endif + struct TORRENT_EXPORT announce_endpoint + { +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + + announce_endpoint(); + + // the local endpoint of the listen interface associated with this endpoint + tcp::endpoint local_endpoint; + + // torrents can be announced using multiple info hashes + // for different protocol versions + + // info_hashes[0] is the v1 info hash (SHA1) + // info_hashes[1] is the v2 info hash (truncated SHA-256) + aux::array info_hashes; + +#if TORRENT_ABI_VERSION <= 2 + // reset announce counters and clears the started sent flag. + // The announce_endpoint will look like we've never talked to + // the tracker. + TORRENT_DEPRECATED void reset(); + + // deprecated in 2.0, use announce_infohash::can_announce + // returns true if we can announce to this tracker now. + // The current time is passed in as ``now``. The ``is_seed`` + // argument is necessary because once we become a seed, we + // need to announce right away, even if the re-announce timer + // hasn't expired yet. + TORRENT_DEPRECATED bool can_announce(time_point now, bool is_seed, std::uint8_t fail_limit) const; + + // deprecated in 2.0, use announce_infohash::is_working + // returns true if the last time we tried to announce to this + // tracker succeeded, or if we haven't tried yet. + TORRENT_DEPRECATED bool is_working() const; + + // for backwards compatibility + TORRENT_DEPRECATED time_point32 next_announce = (time_point32::min)(); + TORRENT_DEPRECATED time_point32 min_announce = (time_point32::min)(); + TORRENT_DEPRECATED std::string message; + TORRENT_DEPRECATED error_code last_error; + TORRENT_DEPRECATED int scrape_incomplete = -1; + TORRENT_DEPRECATED int scrape_complete = -1; + TORRENT_DEPRECATED int scrape_downloaded = -1; + TORRENT_DEPRECATED std::uint8_t fails : 7; + TORRENT_DEPRECATED bool updating : 1; + TORRENT_DEPRECATED bool start_sent : 1; + TORRENT_DEPRECATED bool complete_sent : 1; +#endif + + // set to false to not announce from this endpoint + bool enabled = true; + }; + + // this class holds information about one bittorrent tracker, as it + // relates to a specific torrent. + struct TORRENT_EXPORT announce_entry + { + // constructs a tracker announce entry with ``u`` as the URL. + explicit announce_entry(string_view u); + announce_entry(); + ~announce_entry(); + announce_entry(announce_entry const&); + announce_entry& operator=(announce_entry const&) &; + + // tracker URL as it appeared in the torrent file + std::string url; + + // the current ``&trackerid=`` argument passed to the tracker. + // this is optional and is normally empty (in which case no + // trackerid is sent). + std::string trackerid; + + // each local listen socket (endpoint) will announce to the tracker. This + // list contains state per endpoint. + std::vector endpoints; + + // the tier this tracker belongs to + std::uint8_t tier = 0; + + // the max number of failures to announce to this tracker in + // a row, before this tracker is not used anymore. 0 means unlimited + std::uint8_t fail_limit = 0; + + // flags for the source bitmask, each indicating where + // we heard about this tracker + enum tracker_source + { + // the tracker was part of the .torrent file + source_torrent = 1, + // the tracker was added programmatically via the add_tracker() function + source_client = 2, + // the tracker was part of a magnet link + source_magnet_link = 4, + // the tracker was received from the swarm via tracker exchange + source_tex = 8 + }; + + // a bitmask specifying which sources we got this tracker from. + std::uint8_t source:4; + + // set to true the first time we receive a valid response + // from this tracker. + bool verified:1; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + // all of these will be set to false or 0 + // use the corresponding members in announce_endpoint + TORRENT_DEPRECATED std::uint8_t fails:7; + TORRENT_DEPRECATED bool send_stats:1; + TORRENT_DEPRECATED bool start_sent:1; + TORRENT_DEPRECATED bool complete_sent:1; + // internal + TORRENT_DEPRECATED bool triggered_manually:1; + TORRENT_DEPRECATED bool updating:1; +#endif + +#if TORRENT_ABI_VERSION <= 2 + // reset announce counters and clears the started sent flag. + // The announce_entry will look like we've never talked to + // the tracker. + TORRENT_DEPRECATED void reset(); + + // trims whitespace characters from the beginning of the URL. + TORRENT_DEPRECATED void trim(); +#endif + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2, use announce_endpoint::can_announce + // returns true if we can announce to this tracker now. + // The current time is passed in as ``now``. The ``is_seed`` + // argument is necessary because once we become a seed, we + // need to announce right away, even if the re-announce timer + // hasn't expired yet. + TORRENT_DEPRECATED bool can_announce(time_point now, bool is_seed) const; + + // deprecated in 1.2, use announce_endpoint::is_working + // returns true if the last time we tried to announce to this + // tracker succeeded, or if we haven't tried yet. + TORRENT_DEPRECATED bool is_working() const; +#endif + }; + +TORRENT_VERSION_NAMESPACE_2_END + +} + +#endif diff --git a/include/libtorrent/assert.hpp b/include/libtorrent/assert.hpp new file mode 100644 index 0000000..8e09de3 --- /dev/null +++ b/include/libtorrent/assert.hpp @@ -0,0 +1,135 @@ +/* + +Copyright (c) 2007-2008, 2010-2011, 2013-2019, 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ASSERT_HPP_INCLUDED +#define TORRENT_ASSERT_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/export.hpp" + +#if TORRENT_USE_ASSERTS \ + || defined TORRENT_ASIO_DEBUGGING \ + || defined TORRENT_PROFILE_CALLS \ + || defined TORRENT_DEBUG_BUFFERS + +#include +namespace libtorrent { +std::string demangle(char const* name); +TORRENT_EXPORT void print_backtrace(char* out, int len, int max_depth = 0, void* ctx = nullptr); +} +#endif + +// this is to disable the warning of conditional expressions +// being constant in msvc +#ifdef _MSC_VER +#define TORRENT_WHILE_0 \ + __pragma( warning(push) ) \ + __pragma( warning(disable:4127) ) \ + while (false) \ + __pragma( warning(pop) ) +#else +#define TORRENT_WHILE_0 while (false) +#endif + + +namespace libtorrent { +// declarations of the two functions + +// internal +TORRENT_EXPORT void assert_print(char const* fmt, ...) TORRENT_FORMAT(1,2); + +// internal +TORRENT_EXPORT void assert_fail(const char* expr, int line + , char const* file, char const* function, char const* val, int kind = 0); + +} + +#if TORRENT_USE_ASSERTS + +#ifdef TORRENT_PRODUCTION_ASSERTS +extern TORRENT_EXPORT char const* libtorrent_assert_log; +#endif + +#if TORRENT_USE_IOSTREAM +#include +#endif + +#ifndef TORRENT_USE_SYSTEM_ASSERTS + +#define TORRENT_ASSERT_PRECOND_MSG(x, msg) \ + do { if (x) {} else libtorrent::assert_fail(#x, __LINE__, __FILE__, __func__, msg, 1); } TORRENT_WHILE_0 + +#define TORRENT_ASSERT_PRECOND(x) \ + do { if (x) {} else libtorrent::assert_fail(#x, __LINE__, __FILE__, __func__, nullptr, 1); } TORRENT_WHILE_0 + +#define TORRENT_ASSERT(x) \ + do { if (x) {} else libtorrent::assert_fail(#x, __LINE__, __FILE__, __func__, nullptr, 0); } TORRENT_WHILE_0 + +#if TORRENT_USE_IOSTREAM +#define TORRENT_ASSERT_VAL(x, y) \ + do { if (x) {} else { std::stringstream _s; _s << #y ": " << y; \ + libtorrent::assert_fail(#x, __LINE__, __FILE__, __func__, _s.str().c_str(), 0); } } TORRENT_WHILE_0 + +#define TORRENT_ASSERT_FAIL_VAL(y) \ + do { std::stringstream _s; _s << #y ": " << y; \ + libtorrent::assert_fail("", __LINE__, __FILE__, __func__, _s.str().c_str(), 0); } TORRENT_WHILE_0 + +#else +#define TORRENT_ASSERT_VAL(x, y) TORRENT_ASSERT(x) +#define TORRENT_ASSERT_FAIL_VAL(x) TORRENT_ASSERT_FAIL() +#endif + +#define TORRENT_ASSERT_FAIL() \ + libtorrent::assert_fail("", __LINE__, __FILE__, __func__, nullptr, 0) + +#else +#include +#define TORRENT_ASSERT_PRECOND_MSG(x, msg) assert(x) +#define TORRENT_ASSERT_PRECOND(x) assert(x) +#define TORRENT_ASSERT(x) assert(x) +#define TORRENT_ASSERT_VAL(x, y) assert(x) +#define TORRENT_ASSERT_FAIL_VAL(x) assert(false) +#define TORRENT_ASSERT_FAIL() assert(false) +#endif + +#else // TORRENT_USE_ASSERTS + +#define TORRENT_ASSERT_PRECOND_MSG(a, msg) do {} TORRENT_WHILE_0 +#define TORRENT_ASSERT_PRECOND(a) do {} TORRENT_WHILE_0 +#define TORRENT_ASSERT(a) do {} TORRENT_WHILE_0 +#define TORRENT_ASSERT_VAL(a, b) do {} TORRENT_WHILE_0 +#define TORRENT_ASSERT_FAIL_VAL(a) do {} TORRENT_WHILE_0 +#define TORRENT_ASSERT_FAIL() do {} TORRENT_WHILE_0 + +#endif // TORRENT_USE_ASSERTS + +#endif // TORRENT_ASSERT_HPP_INCLUDED diff --git a/include/libtorrent/aux_/alert_manager.hpp b/include/libtorrent/aux_/alert_manager.hpp new file mode 100644 index 0000000..d5f6237 --- /dev/null +++ b/include/libtorrent/aux_/alert_manager.hpp @@ -0,0 +1,180 @@ +/* + +Copyright (c) 2003-2013, Daniel Wallin +Copyright (c) 2013, 2015-2020, Arvid Norberg +Copyright (c) 2016, 2018, 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALERT_MANAGER_HPP_INCLUDED +#define TORRENT_ALERT_MANAGER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/aux_/heterogeneous_queue.hpp" +#include "libtorrent/stack_allocator.hpp" +#include "libtorrent/alert_types.hpp" // for abi_alert_count +#include "libtorrent/aux_/array.hpp" + +#include +#include // for std::forward +#include +#include +#include +#include + +#ifndef TORRENT_DISABLE_EXTENSIONS +#include "libtorrent/extensions.hpp" +#include // for shared_ptr +#include +#endif + +namespace libtorrent { +namespace aux { + + struct TORRENT_EXTRA_EXPORT alert_manager + { + explicit alert_manager(int queue_limit + , alert_category_t alert_mask = alert_category::error); + + alert_manager(alert_manager const&) = delete; + alert_manager& operator=(alert_manager const&) = delete; + + ~alert_manager(); + + template + void emplace_alert(Args&&... args) try + { + std::unique_lock lock(m_mutex); + + heterogeneous_queue& queue = m_alerts[m_generation]; + + // don't add more than this number of alerts, unless it's a + // high priority alert, in which case we try harder to deliver it + // for high priority alerts, double the upper limit + if (queue.size() / (1 + static_cast(T::priority)) >= m_queue_size_limit) + { + // record that we dropped an alert of this type + m_dropped.set(T::alert_type); + return; + } + + T& alert = queue.emplace_back( + m_allocations[m_generation], std::forward(args)...); + + maybe_notify(&alert); + } + catch (std::bad_alloc const&) + { + // record that we dropped an alert of this type + std::unique_lock lock(m_mutex); + m_dropped.set(T::alert_type); + } + + bool pending() const; + void get_all(std::vector& alerts); + + template + bool should_post() const + { + return bool(m_alert_mask.load(std::memory_order_relaxed) & T::static_category); + } + + alert* wait_for_alert(time_duration max_wait); + + void set_alert_mask(alert_category_t const m) noexcept + { + m_alert_mask = m; + } + + alert_category_t alert_mask() const noexcept + { + return m_alert_mask; + } + + int alert_queue_size_limit() const noexcept { return m_queue_size_limit; } + int set_alert_queue_size_limit(int queue_size_limit_); + + void set_notify_function(std::function const& fun); + +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extension(std::shared_ptr ext); +#endif + + private: + + void maybe_notify(alert* a); + + // this mutex protects everything. Since it's held while executing user + // callbacks (the notify function and extension on_alert()) it must be + // recursive to support recursively post new alerts. + mutable std::recursive_mutex m_mutex; + std::condition_variable_any m_condition; + std::atomic m_alert_mask; + int m_queue_size_limit; + + // a bitfield where each bit represents an alert type. Every time we drop + // an alert (because the queue is full or of some other error) we set the + // corresponding bit in this mask, to communicate to the client that it + // may have missed an update. + std::bitset m_dropped; + + // this function (if set) is called whenever the number of alerts in + // the alert queue goes from 0 to 1. The client is expected to wake up + // its main message loop for it to poll for alerts (using get_alerts()). + // That call will drain every alert in one atomic operation and this + // notification function will be called again the next time an alert is + // posted to the queue + std::function m_notify; + + // this is either 0 or 1, it indicates which m_alerts and m_allocations + // the alert_manager is allowed to use right now. This is swapped when + // the client calls get_all(), at which point all of the alert objects + // passed to the client will be owned by libtorrent again, and reset. + int m_generation = 0; + + // this is where all alerts are queued up. There are two heterogeneous + // queues to double buffer the thread access. The std::mutex in the alert + // manager gives exclusive access to m_alerts[m_generation] and + // m_allocations[m_generation] whereas the other copy is exclusively + // used by the client thread. + aux::array, 2> m_alerts; + + // this is a stack where alerts can allocate variable length content, + // such as strings, to go with the alerts. + aux::array m_allocations; + +#ifndef TORRENT_DISABLE_EXTENSIONS + std::list> m_ses_extensions; +#endif + }; +} +} + +#endif diff --git a/include/libtorrent/aux_/aligned_union.hpp b/include/libtorrent/aux_/aligned_union.hpp new file mode 100644 index 0000000..9a9b4a6 --- /dev/null +++ b/include/libtorrent/aux_/aligned_union.hpp @@ -0,0 +1,65 @@ +/* + +Copyright (c) 2017, Alden Torres +Copyright (c) 2017, 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALIGNED_UNION_HPP_INCLUDE +#define TORRENT_ALIGNED_UNION_HPP_INCLUDE + +#include + +namespace libtorrent { namespace aux { + +constexpr std::size_t max(std::size_t a) +{ return a; } + +constexpr std::size_t max(std::size_t a, std::size_t b) +{ return a > b ? a : b; } + +template +constexpr std::size_t max(std::size_t a, std::size_t b, Vals... v) +{ return max(a, max(b, v...)); } + +// this is for backwards compatibility with not-quite C++11 compilers +// and for C++23 which deprecated std::aligned_union +template +struct aligned_union +{ + struct type + { + alignas(max(alignof(Types)...)) + char buffer[max(Len, max(sizeof(Types)...))]; + }; +}; + +}} + +#endif diff --git a/include/libtorrent/aux_/alloca.hpp b/include/libtorrent/aux_/alloca.hpp new file mode 100644 index 0000000..b81ba49 --- /dev/null +++ b/include/libtorrent/aux_/alloca.hpp @@ -0,0 +1,117 @@ +/* + +Copyright (c) 2009-2010, 2012, 2017-2020, Arvid Norberg +Copyright (c) 2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALLOCA_HPP_INCLUDED +#define TORRENT_ALLOCA_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" +#include // for iterator_traits +#include // for addressof + +namespace libtorrent { namespace aux { + +template +inline void uninitialized_default_construct(ForwardIt first, ForwardIt last) +{ + using Value = typename std::iterator_traits::value_type; + ForwardIt current = first; + try { + for (; current != last; ++current) { + ::new (static_cast(std::addressof(*current))) Value; + } + } catch (...) { + for (; first != current; ++first) { + first->~Value(); + } + throw; + } +} + +template +struct alloca_destructor +{ + static std::ptrdiff_t const cutoff = 4096 / sizeof(T); + + span objects; + ~alloca_destructor() + { + if (objects.size() > cutoff) + { + delete [] objects.data(); + } + else + { + for (auto& o : objects) + { + TORRENT_UNUSED(o); + o.~T(); + } + } + } +}; + +}} + +#if defined TORRENT_WINDOWS || defined TORRENT_MINGW + +#include +#define TORRENT_ALLOCA_FUN _alloca + +#elif defined TORRENT_BSD + +#include +#define TORRENT_ALLOCA_FUN alloca + +#else + +#include +#define TORRENT_ALLOCA_FUN alloca + +#endif + +#define TORRENT_ALLOCA(v, t, n) ::libtorrent::span v; { \ + auto TORRENT_ALLOCA_size = ::libtorrent::aux::numeric_cast(n); \ + if (TORRENT_ALLOCA_size > ::libtorrent::aux::alloca_destructor::cutoff) {\ + v = ::libtorrent::span(new t[::libtorrent::aux::numeric_cast(n)], TORRENT_ALLOCA_size); \ + } \ + else { \ + auto* TORRENT_ALLOCA_tmp = static_cast(TORRENT_ALLOCA_FUN(sizeof(t) * static_cast(n))); \ + v = ::libtorrent::span(TORRENT_ALLOCA_tmp, TORRENT_ALLOCA_size); \ + ::libtorrent::aux::uninitialized_default_construct(v.begin(), v.end()); \ + } \ +} \ +::libtorrent::aux::alloca_destructor v##_destructor{v} + +#endif // TORRENT_ALLOCA_HPP_INCLUDED diff --git a/include/libtorrent/aux_/allocating_handler.hpp b/include/libtorrent/aux_/allocating_handler.hpp new file mode 100644 index 0000000..3ecfd4d --- /dev/null +++ b/include/libtorrent/aux_/allocating_handler.hpp @@ -0,0 +1,366 @@ +/* + +Copyright (c) 2015-2017, 2019-2022, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2019, Steven Siloti +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALLOCATING_HANDLER_HPP_INCLUDED +#define TORRENT_ALLOCATING_HANDLER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" + +#include "libtorrent/debug.hpp" // for TORRENT_ASSERT + +#include +#include // for shared_ptr + +#ifdef TORRENT_ASIO_DEBUGGING +#include "libtorrent/debug.hpp" +#endif + +namespace libtorrent { namespace aux { + +#ifdef BOOST_ASIO_ENABLE_HANDLER_TRACKING + constexpr std::size_t tracking = 8; +#else + constexpr std::size_t tracking = 0; +#endif + +#if defined _MSC_VER || defined __MINGW64__ + + // windows +#if _HAS_ITERATOR_DEBUGGING > 0 + constexpr std::size_t debug_read_iter = 34 * sizeof(void*); + constexpr std::size_t debug_write_iter = 34 * sizeof(void*); + constexpr std::size_t debug_tick = 4 * sizeof(void*); +#else + constexpr std::size_t debug_read_iter = 0; + constexpr std::size_t debug_write_iter = 0; + constexpr std::size_t debug_tick = 0; +#endif +#if TORRENT_USE_SSL +#ifdef __MINGW64__ + constexpr std::size_t openssl_read_cost = 26 + 21 * sizeof(void*); + constexpr std::size_t openssl_write_cost = 26 + 21 * sizeof(void*); +#else + constexpr std::size_t openssl_read_cost = 26 + 14 * sizeof(void*); + constexpr std::size_t openssl_write_cost = 26 + 14 * sizeof(void*); +#endif +#else + constexpr std::size_t openssl_read_cost = 0; + constexpr std::size_t openssl_write_cost = 0; +#endif + + constexpr std::size_t read_handler_max_size = tracking + debug_read_iter + openssl_read_cost + 102 + 9 * sizeof(void*); + constexpr std::size_t write_handler_max_size = tracking + debug_write_iter + openssl_write_cost + 102 + 9 * sizeof(void*); + constexpr std::size_t udp_handler_max_size = tracking + debug_tick + 144 + 9 * sizeof(void*); + constexpr std::size_t utp_handler_max_size = tracking + debug_tick + 168 + 9 * sizeof(void*); + constexpr std::size_t tick_handler_max_size = tracking + debug_tick + 168; + constexpr std::size_t abort_handler_max_size = tracking + debug_tick + 104; + constexpr std::size_t submit_handler_max_size = tracking + debug_tick + 104; + constexpr std::size_t deferred_handler_max_size = tracking + debug_tick + 112; +#else + + // non-windows + +#ifdef _GLIBCXX_DEBUG +#if defined __clang__ + constexpr std::size_t debug_read_iter = 12 * sizeof(void*); + constexpr std::size_t debug_write_iter = 12 * sizeof(void*); +#else + constexpr std::size_t debug_write_iter = 8 * sizeof(void*); + constexpr std::size_t debug_read_iter = 12 * sizeof(void*); +#endif +#else + constexpr std::size_t debug_read_iter = 0; + constexpr std::size_t debug_write_iter = 0; +#endif + +#if TORRENT_USE_SSL +#ifdef __clang__ + constexpr std::size_t openssl_read_cost = 264; + constexpr std::size_t openssl_write_cost = 216; +#else + constexpr std::size_t openssl_read_cost = 152; + constexpr std::size_t openssl_write_cost = 152; +#endif +#else + constexpr std::size_t openssl_read_cost = 0; + constexpr std::size_t openssl_write_cost = 0; +#endif + +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + constexpr std::size_t fuzzer_write_cost = 32; + constexpr std::size_t fuzzer_read_cost = 80; +#else + constexpr std::size_t fuzzer_write_cost = 0; + constexpr std::size_t fuzzer_read_cost = 0; +#endif + constexpr std::size_t write_handler_max_size = tracking + debug_write_iter + openssl_write_cost + fuzzer_write_cost + 176; + constexpr std::size_t read_handler_max_size = tracking + debug_read_iter + openssl_read_cost + fuzzer_read_cost + 176; + constexpr std::size_t udp_handler_max_size = tracking + 168; + constexpr std::size_t utp_handler_max_size = tracking + 192; + constexpr std::size_t abort_handler_max_size = tracking + 72; + constexpr std::size_t submit_handler_max_size = tracking + 72; + constexpr std::size_t deferred_handler_max_size = tracking + 80; + constexpr std::size_t tick_handler_max_size = tracking + 136; +#endif + + enum HandlerName + { + // when adding a handler here, be sure to update handler_names in + // debug.hpp as well + write_handler, read_handler, udp_handler, tick_handler, abort_handler, + defer_handler, utp_handler, submit_handler + }; + + // this is meant to provide the actual storage for the handler allocator. + // There's only a single slot, so the allocator is only supposed to be used + // for handlers where there's only a single outstanding operation at a time, + // per storage object. For instance, peers only ever have one outstanding + // read operation at a time, so it can reuse its storage for read handlers. + template + struct handler_storage + { + handler_storage() = default; + static constexpr std::size_t size = Size; + static constexpr HandlerName name = Name; + + alignas(alignof(std::max_align_t)) std::array bytes; +#if TORRENT_USE_ASSERTS + bool used = false; +#endif + handler_storage(handler_storage const&) = delete; + }; + + struct TORRENT_EXTRA_EXPORT error_handler_interface + { + virtual void on_exception(std::exception const&) = 0; + virtual void on_error(error_code const&) = 0; + + protected: + ~error_handler_interface() {} + }; + + template + struct required_size { static std::size_t const value = V; }; + + template + struct available_size { static std::size_t const value = V; }; + + template + struct assert_message + { + static_assert(Required::value <= Available::value + , "Handler buffer not large enough, please increase it"); + static std::size_t const value = Available::value; + }; + + template + struct handler_allocator + { + template + friend struct handler_allocator; + + using value_type = T; + using size_type = std::size_t; + + friend bool operator==(handler_allocator lhs, handler_allocator rhs) + { return lhs.m_storage == rhs.m_storage; } + friend bool operator!=(handler_allocator lhs, handler_allocator rhs) + { return lhs.m_storage != rhs.m_storage; } + + template + struct rebind { + using other = handler_allocator + , available_size, Name>::value, Name>; + static_assert(alignof(U) <= alignof(std::max_align_t), "handler storage is not correctly aligned"); + }; + + explicit handler_allocator(handler_storage* s) : m_storage(s) {} + template + handler_allocator(handler_allocator const& other) : m_storage(other.m_storage) {} + + T* allocate(std::size_t size) + { + TORRENT_UNUSED(size); + TORRENT_ASSERT_VAL(size == 1, size); + TORRENT_ASSERT_VAL(sizeof(T) <= Size, sizeof(T)); + TORRENT_ASSERT(!m_storage->used); +#if TORRENT_USE_ASSERTS + m_storage->used = true; +#endif +#ifdef TORRENT_ASIO_DEBUGGING + record_handler_allocation(static_cast(Name), Size); +#endif + return reinterpret_cast(m_storage->bytes.data()); + } + + void deallocate(T* ptr, std::size_t size) + { + TORRENT_UNUSED(ptr); + TORRENT_UNUSED(size); + + TORRENT_ASSERT_VAL(size == 1, size); + TORRENT_ASSERT_VAL(sizeof(T) <= Size, sizeof(T)); + TORRENT_ASSERT(ptr == reinterpret_cast(m_storage->bytes.data())); + TORRENT_ASSERT(m_storage->used); +#if TORRENT_USE_ASSERTS + m_storage->used = false; +#endif + } + + private: + handler_storage* m_storage; + }; + + // this class is a wrapper for an asio handler object. Its main purpose + // is to pass along additional parameters to the asio handler allocator + // function, as well as providing a distinct type for the handler + // allocator function to overload on + template + struct allocating_handler + { + allocating_handler( + Handler h, handler_storage* s, error_handler_interface* eh) + : handler(std::move(h)) + , storage(s) +#ifndef BOOST_NO_EXCEPTIONS + , error_handler(eh) +#endif + {} + + template + void operator()(A&&... a) + { +#ifdef BOOST_NO_EXCEPTIONS + handler(std::forward(a)...); +#else + try + { + handler(std::forward(a)...); + } + catch (system_error const& e) + { + error_handler->on_error(e.code()); + } + catch (std::exception const& e) + { + error_handler->on_exception(e); + } + catch (...) + { + // this is pretty bad + TORRENT_ASSERT(false); + std::runtime_error e("unknown exception"); + error_handler->on_exception(e); + } +#endif + } + + using allocator_type = handler_allocator; + + allocator_type get_allocator() const noexcept + { return allocator_type{storage}; } + + private: + + Handler handler; + handler_storage* storage; +#ifndef BOOST_NO_EXCEPTIONS + error_handler_interface* error_handler; +#endif + }; + + template + aux::allocating_handler + make_handler(Handler handler + , handler_storage& storage + , error_handler_interface& err_handler) + { + return aux::allocating_handler( + std::forward(handler), &storage, &err_handler); + } + + // TODO: in C++17, Handler and Storage could just use "auto" + template + struct handler + { + explicit handler(std::shared_ptr p) : ptr_(std::move(p)) {} + + std::shared_ptr ptr_; + + template + void operator()(A&&... a) + { +#ifdef BOOST_NO_EXCEPTIONS + (ptr_.get()->*Handler)(std::forward(a)...); +#else + try + { + (ptr_.get()->*Handler)(std::forward(a)...); + } + catch (system_error const& e) + { + (ptr_.get()->*ErrorHandler)(e.code()); + } + catch (std::exception const& e) + { + (ptr_.get()->*ExceptHandler)(e); + } + catch (...) + { + // this is pretty bad + TORRENT_ASSERT(false); + std::runtime_error e("unknown exception"); + (ptr_.get()->*ExceptHandler)(e); + } +#endif + } + + using allocator_type = handler_allocator; + + allocator_type get_allocator() const noexcept + { return allocator_type{&(ptr_.get()->*Storage)}; } + }; +} +} + +#endif diff --git a/include/libtorrent/aux_/announce_entry.hpp b/include/libtorrent/aux_/announce_entry.hpp new file mode 100644 index 0000000..cfa4865 --- /dev/null +++ b/include/libtorrent/aux_/announce_entry.hpp @@ -0,0 +1,216 @@ +/* + +Copyright (c) 2016, 2018, Alden Torres +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_AUX_ANNOUNCE_ENTRY_HPP_INCLUDED +#define TORRENT_AUX_ANNOUNCE_ENTRY_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/listen_socket_handle.hpp" +#include "libtorrent/aux_/array.hpp" +#include "libtorrent/info_hash.hpp" + +#include +#include +#include + +namespace libtorrent { + struct torrent; +namespace aux { + + struct TORRENT_EXTRA_EXPORT announce_infohash + { + announce_infohash(); + + // if this tracker has returned an error or warning message + // that message is stored here + std::string message; + + // if this tracker failed the last time it was contacted + // this error code specifies what error occurred + error_code last_error; + + // the time of next tracker announce + time_point32 next_announce = (time_point32::min)(); + + // no announces before this time + time_point32 min_announce = (time_point32::min)(); + + // TODO: include the number of peers received from this tracker, at last + // announce + + // these are either -1 or the scrape information this tracker last + // responded with. *incomplete* is the current number of downloaders in + // the swarm, *complete* is the current number of seeds in the swarm and + // *downloaded* is the cumulative number of completed downloads of this + // torrent, since the beginning of time (from this tracker's point of + // view). + + // if this tracker has returned scrape data, these fields are filled in + // with valid numbers. Otherwise they are set to -1. ``incomplete`` counts + // the number of current downloaders. ``complete`` counts the number of + // current peers completed the download, or "seeds". ``downloaded`` is the + // cumulative number of completed downloads. + int scrape_incomplete = -1; + int scrape_complete = -1; + int scrape_downloaded = -1; + + // the number of times in a row we have failed to announce to this + // tracker. + std::uint8_t fails : 7; + + // true while we're waiting for a response from the tracker. + bool updating : 1; + + // set to true when we get a valid response from an announce + // with event=started. If it is set, we won't send start in the subsequent + // announces. + bool start_sent : 1; + + // set to true when we send a event=completed. + bool complete_sent : 1; + + // internal + bool triggered_manually : 1; + + // reset announce counters and clears the started sent flag. + // The announce_endpoint will look like we've never talked to + // the tracker. + void reset(); + + // updates the failure counter and time-outs for re-trying. + // This is called when the tracker announce fails. + void failed(int backoff_ratio, seconds32 retry_interval = seconds32(0)); + + // returns true if we can announce to this tracker now. + // The current time is passed in as ``now``. The ``is_seed`` + // argument is necessary because once we become a seed, we + // need to announce right away, even if the re-announce timer + // hasn't expired yet. + bool can_announce(time_point now, bool is_seed, std::uint8_t fail_limit) const; + + // returns true if the last time we tried to announce to this + // tracker succeeded, or if we haven't tried yet. + bool is_working() const { return fails == 0; } + }; + + struct announce_entry; + + // announces are sent to each tracker using every listen socket + // this class holds information about one listen socket for one tracker + struct TORRENT_EXTRA_EXPORT announce_endpoint + { + // internal + announce_endpoint(aux::listen_socket_handle const& s, bool completed); + + // the local endpoint of the listen interface associated with this endpoint + tcp::endpoint local_endpoint; + + // torrents can be announced using multiple info hashes + // for different protocol versions + + // info_hashes[0] is the v1 info hash (SHA1) + // info_hashes[1] is the v2 info hash (truncated SHA-256) + aux::array info_hashes; + + // reset announce counters and clears the started sent flag. + // The announce_endpoint will look like we've never talked to + // the tracker. + void reset(); + + // set to false to not announce from this endpoint + bool enabled : 1; + + // internal + aux::listen_socket_handle socket; + }; + + // this class holds information about one bittorrent tracker, as it + // relates to a specific torrent. + struct TORRENT_EXTRA_EXPORT announce_entry + { + // constructs a tracker announce entry with ``u`` as the URL. + explicit announce_entry(string_view u); + + // constructs the internal announce entry from the user facing one + explicit announce_entry(lt::announce_entry const&); + announce_entry(); + ~announce_entry(); + announce_entry(announce_entry const&); + announce_entry& operator=(announce_entry const&) &; + + // tracker URL as it appeared in the torrent file + std::string url; + + // the current ``&trackerid=`` argument passed to the tracker. + // this is optional and is normally empty (in which case no + // trackerid is sent). + std::string trackerid; + + // each local listen socket (endpoint) will announce to the tracker. This + // list contains state per endpoint. + std::vector endpoints; + + // the tier this tracker belongs to + std::uint8_t tier = 0; + + // the max number of failures to announce to this tracker in + // a row, before this tracker is not used anymore. 0 means unlimited + std::uint8_t fail_limit = 0; + + // a bitmask specifying which sources we got this tracker from. + std::uint8_t source:4; + + // set to true the first time we receive a valid response + // from this tracker. + bool verified:1; + + // reset announce counters and clears the started sent flag. + // The announce_entry will look like we've never talked to + // the tracker. + void reset(); + + // internal + announce_endpoint* find_endpoint(aux::listen_socket_handle const& s); + }; + +} +} + +#endif + diff --git a/include/libtorrent/aux_/apply_pad_files.hpp b/include/libtorrent/aux_/apply_pad_files.hpp new file mode 100644 index 0000000..514e4db --- /dev/null +++ b/include/libtorrent/aux_/apply_pad_files.hpp @@ -0,0 +1,105 @@ +/* + +Copyright (c) 2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_APPLY_PAD_FILES_HPP_INCLUDED +#define TORRENT_APPLY_PAD_FILES_HPP_INCLUDED + +#include "libtorrent/file_storage.hpp" +#include "libtorrent/units.hpp" + +namespace libtorrent { +namespace aux { + +// calls fun for every piece that overlaps a pad file, passing in the number +// of bytes, in that piece, that's a padfile +template +void apply_pad_files(file_storage const& fs, Fun&& fun) +{ + for (auto const i : fs.file_range()) + { + if (!fs.pad_file_at(i) || fs.file_size(i) == 0) continue; + + // pr points to the last byte of the pad file + peer_request const pr = fs.map_file(i, fs.file_size(i) - 1, 0); + + int const piece_size = fs.piece_length(); + + // This pad file may be the last file in the torrent, and the + // last piece may have an odd size. + if ((pr.start + 1) % piece_size != 0 && i < prev(fs.end_file())) + { + // this is a pre-requisite of the piece picker. Pad files + // that don't align with pieces are kind of useless anyway. + // They probably aren't real padfiles, treat them as normal + // files. + continue; + } + + // A pad file may span multiple pieces. This is especially + // likely in v2 torrents where file sizes are aligned to powers + // of two pieces. We loop from the end of the pad file + // + // For example, we may have this situation: + // + // pr.start + // | + // v + // +-----+-----+-----+ + // | ##|#####|#####| + // +-----+-----+-----+ + // \ / + // - file_size - + // + // We need to declare all #-parts of the pieces as pad bytes to + // the piece picker. + + piece_index_t piece = pr.piece; + std::int64_t pad_bytes_left = fs.file_size(i); + + while (pad_bytes_left > 0) + { + // The last piece may have an odd size, that's why + // we ask for the piece size for every piece. (it would be + // odd, but it's still possible). + int const bytes = int(std::min(pad_bytes_left, std::int64_t(fs.piece_size(piece)))); + TORRENT_ASSERT(bytes > 0); + fun(piece, bytes); + pad_bytes_left -= bytes; + --piece; + } + } +} + +} // namespace aux +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/aux_/array.hpp b/include/libtorrent/aux_/array.hpp new file mode 100644 index 0000000..9b2e076 --- /dev/null +++ b/include/libtorrent/aux_/array.hpp @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2017, Alden Torres +Copyright (c) 2018, 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ARRAY_HPP +#define TORRENT_ARRAY_HPP + +#include + +#include "libtorrent/aux_/container_wrapper.hpp" + +namespace libtorrent { namespace aux { + + template + using array = container_wrapper>; + +}} + +#endif diff --git a/include/libtorrent/aux_/bandwidth_limit.hpp b/include/libtorrent/aux_/bandwidth_limit.hpp new file mode 100644 index 0000000..d982f41 --- /dev/null +++ b/include/libtorrent/aux_/bandwidth_limit.hpp @@ -0,0 +1,105 @@ +/* + +Copyright (c) 2007, 2009-2010, 2012, 2014, 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BANDWIDTH_CHANNEL_HPP_INCLUDED +#define TORRENT_BANDWIDTH_CHANNEL_HPP_INCLUDED + +#include +#include + +#include "libtorrent/assert.hpp" + +namespace libtorrent { +namespace aux { + +// member of peer_connection +struct TORRENT_EXTRA_EXPORT bandwidth_channel +{ + static constexpr int inf = (std::numeric_limits::max)(); + + bandwidth_channel(); + + // 0 means infinite + void throttle(int limit); + int throttle() const + { + TORRENT_ASSERT_VAL(m_limit >= 0, m_limit); + TORRENT_ASSERT_VAL(m_limit < inf, m_limit); + return m_limit; + } + + int quota_left() const; + void update_quota(int dt_milliseconds); + + // this is used when connections disconnect with + // some quota left. It's returned to its bandwidth + // channels. + void return_quota(int amount); + void use_quota(int amount); + + // this is an optimization. If there is more than one second + // of quota built up in this channel, just apply it right away + // instead of introducing a delay to split it up evenly. This + // should especially help in situations where a single peer + // has a capacity under the rate limit, but would otherwise be + // held back by the latency of getting bandwidth from the limiter + bool need_queueing(int amount) + { + if (m_quota_left - amount < m_limit) return true; + m_quota_left -= amount; + return false; + } + + // used as temporary storage while distributing + // bandwidth + int tmp; + + // this is the number of bytes to distribute this round + int distribute_quota; + +private: + + // this is the amount of bandwidth we have + // been assigned without using yet. + std::int64_t m_quota_left; + + // the limit is the number of bytes + // per second we are allowed to use. + std::int32_t m_limit; +}; + +} +} + +#endif diff --git a/include/libtorrent/aux_/bandwidth_manager.hpp b/include/libtorrent/aux_/bandwidth_manager.hpp new file mode 100644 index 0000000..e4599ef --- /dev/null +++ b/include/libtorrent/aux_/bandwidth_manager.hpp @@ -0,0 +1,94 @@ +/* + +Copyright (c) 2007, 2009, 2011-2016, 2020, Arvid Norberg +Copyright (c) 2016, 2018, 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BANDWIDTH_MANAGER_HPP_INCLUDED +#define TORRENT_BANDWIDTH_MANAGER_HPP_INCLUDED + +#include +#include + +#include "libtorrent/aux_/invariant_check.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/bandwidth_limit.hpp" +#include "libtorrent/aux_/bandwidth_queue_entry.hpp" +#include "libtorrent/aux_/bandwidth_socket.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent { +namespace aux { + +struct TORRENT_EXTRA_EXPORT bandwidth_manager +{ + explicit bandwidth_manager(int channel); + + void close(); + +#if TORRENT_USE_ASSERTS + bool is_queued(bandwidth_socket const* peer) const; +#endif + + int queue_size() const; + std::int64_t queued_bytes() const; + + // non prioritized means that, if there's a line for bandwidth, + // others will cut in front of the non-prioritized peers. + // this is used by web seeds + // returns the number of bytes to assign to the peer, or 0 + // if the peer's 'assign_bandwidth' callback will be called later + int request_bandwidth(std::shared_ptr peer + , int blk, int priority, bandwidth_channel** chan, int num_channels); + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + void update_quotas(time_duration const& dt); + +private: + + // these are the consumers that want bandwidth + std::vector m_queue; + // the number of bytes all the requests in queue are for + std::int64_t m_queued_bytes; + + // this is the channel within the consumers + // that bandwidth is assigned to (upload or download) + int m_channel; + + bool m_abort; +}; + +} +} + +#endif diff --git a/include/libtorrent/aux_/bandwidth_queue_entry.hpp b/include/libtorrent/aux_/bandwidth_queue_entry.hpp new file mode 100644 index 0000000..7ba427e --- /dev/null +++ b/include/libtorrent/aux_/bandwidth_queue_entry.hpp @@ -0,0 +1,77 @@ +/* + +Copyright (c) 2007, 2009, 2012, 2014-2015, 2020, Arvid Norberg +Copyright (c) 2016, 2018, 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BANDWIDTH_QUEUE_ENTRY_HPP_INCLUDED +#define TORRENT_BANDWIDTH_QUEUE_ENTRY_HPP_INCLUDED + +#include + +#include "libtorrent/aux_/bandwidth_limit.hpp" +#include "libtorrent/aux_/bandwidth_socket.hpp" +#include "libtorrent/aux_/array.hpp" + +namespace libtorrent { +namespace aux { + +struct TORRENT_EXTRA_EXPORT bw_request +{ + bw_request(std::shared_ptr pe + , int blk, int prio); + + std::shared_ptr peer; + // 1 is normal prio + int priority; + // the number of bytes assigned to this request so far + int assigned; + // once assigned reaches this, we dispatch the request function + int request_size; + + // the max number of rounds for this request to survive + // this ensures that requests gets responses at very low + // rate limits, when the requested size would take a long + // time to satisfy + int ttl; + + // loops over the bandwidth channels and assigns bandwidth + // from the most limiting one + int assign_bandwidth(); + + static constexpr int max_bandwidth_channels = 10; + // we don't actually support more than 10 channels per peer + aux::array channel{}; +}; + +} +} + +#endif diff --git a/include/libtorrent/aux_/bandwidth_socket.hpp b/include/libtorrent/aux_/bandwidth_socket.hpp new file mode 100644 index 0000000..a32cdf0 --- /dev/null +++ b/include/libtorrent/aux_/bandwidth_socket.hpp @@ -0,0 +1,51 @@ +/* + +Copyright (c) 2009, 2015, 2017-2018, 2020, Arvid Norberg +Copyright (c) 2016, 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BANDWIDTH_SOCKET_HPP_INCLUDED +#define TORRENT_BANDWIDTH_SOCKET_HPP_INCLUDED + +#include "libtorrent/aux_/export.hpp" + +namespace libtorrent { +namespace aux { + + struct TORRENT_EXTRA_EXPORT bandwidth_socket + { + virtual void assign_bandwidth(int channel, int amount) = 0; + virtual bool is_disconnecting() const = 0; + virtual ~bandwidth_socket() {} + }; +} +} + +#endif // TORRENT_BANDWIDTH_SOCKET_HPP_INCLUDED diff --git a/include/libtorrent/aux_/bind_to_device.hpp b/include/libtorrent/aux_/bind_to_device.hpp new file mode 100644 index 0000000..db18cfa --- /dev/null +++ b/include/libtorrent/aux_/bind_to_device.hpp @@ -0,0 +1,137 @@ +/* + +Copyright (c) 2016, 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BIND_TO_DEVICE_HPP_INCLUDED +#define TORRENT_BIND_TO_DEVICE_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" + +#if TORRENT_USE_IFCONF || TORRENT_USE_NETLINK || TORRENT_USE_SYSCTL +#include // for SO_BINDTODEVICE +#include +#endif + +namespace libtorrent { namespace aux { + +#if defined SO_BINDTODEVICE + + struct bind_to_device + { + explicit bind_to_device(char const* device): m_value(device) {} + template + int level(Protocol const&) const { return SOL_SOCKET; } + template + int name(Protocol const&) const { return SO_BINDTODEVICE; } + template + char const* data(Protocol const&) const { return m_value; } + template + size_t size(Protocol const&) const { return strlen(m_value) + 1; } + private: + char const* m_value; + }; + + template + void bind_device(T& sock, char const* device, error_code& ec) + { + sock.set_option(bind_to_device(device), ec); + } + +#define TORRENT_HAS_BINDTODEVICE 1 + +#elif defined IP_BOUND_IF + + struct bind_to_device + { + explicit bind_to_device(unsigned int idx): m_value(idx) {} + template + int level(Protocol const&) const { return IPPROTO_IP; } + template + int name(Protocol const&) const { return IP_BOUND_IF; } + template + char const* data(Protocol const&) const { return reinterpret_cast(&m_value); } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + private: + unsigned int m_value; + }; + + template + void bind_device(T& sock, char const* device, error_code& ec) + { + unsigned int const if_idx = if_nametoindex(device); + if (if_idx == 0) + { + ec.assign(errno, system_category()); + return; + } + sock.set_option(bind_to_device(if_idx), ec); + } + +#define TORRENT_HAS_BINDTODEVICE 1 + +#elif defined IP_FORCE_OUT_IFP + + struct bind_to_device + { + explicit bind_to_device(char const* device): m_value(device) {} + template + int level(Protocol const&) const { return SOL_SOCKET; } + template + int name(Protocol const&) const { return IP_FORCE_OUT_IFP; } + template + char const* data(Protocol const&) const { return m_value; } + template + size_t size(Protocol const&) const { return strlen(m_value) + 1; } + private: + char const* m_value; + }; + + template + void bind_device(T& sock, char const* device, error_code& ec) + { + sock.set_option(bind_to_device(device), ec); + } + +#define TORRENT_HAS_BINDTODEVICE 1 + +#else + +#define TORRENT_HAS_BINDTODEVICE 0 + +#endif + +} } + +#endif + diff --git a/include/libtorrent/aux_/buffer.hpp b/include/libtorrent/aux_/buffer.hpp new file mode 100644 index 0000000..7d8fe0d --- /dev/null +++ b/include/libtorrent/aux_/buffer.hpp @@ -0,0 +1,170 @@ +/* +Copyright (c) 2005, 2007, 2009, 2013-2021, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Tim Niederhausen +Copyright (c) 2019, Fabrice Fontaine +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of Rasterbar Software nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BUFFER_HPP_INCLUDED +#define TORRENT_BUFFER_HPP_INCLUDED + +#include +#include // for numeric_limits +#include // malloc/free/realloc +#include // for std::swap +#include + +#include "libtorrent/aux_/invariant_check.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/aux_/throw.hpp" + +#if defined __GLIBC__ +#include +#elif defined _MSC_VER +#include +#elif defined __FreeBSD__ +#include +#elif defined __APPLE__ +#include +#endif + +namespace libtorrent { +namespace aux { + +// the buffer is allocated once and cannot be resized. The size() may be +// larger than requested, in case the underlying allocator over allocated. In +// order to "grow" an allocation, create a new buffer and initialize it by +// the range of bytes from the existing, and move-assign the new over the +// old. +class buffer +{ +public: + using difference_type = std::ptrdiff_t; + using index_type = std::ptrdiff_t; + + // allocate an uninitialized buffer of the specified size + explicit buffer(difference_type size = 0) + { + TORRENT_ASSERT(size < (std::numeric_limits::max)()); + + if (size == 0) return; + + // this rounds up the size to be 8 bytes aligned + // it mostly makes sense for platforms without support + // for a variation of "malloc_size()" + size = (size + 7) & (~difference_type(0x7)); + + // we have to use malloc here, to be compatible with the fancy query + // functions below + m_begin = static_cast(std::malloc(static_cast(size))); + if (m_begin == nullptr) aux::throw_ex(); + + // the actual allocation may be larger than we requested. If so, let the + // user take advantage of every single byte +#if (defined __GLIBC__ && !defined __UCLIBC__) || defined __FreeBSD__ + m_size = static_cast(::malloc_usable_size(m_begin)); +#elif defined _MSC_VER + m_size = static_cast(::_msize(m_begin)); +#elif defined __APPLE__ + m_size = static_cast(::malloc_size(m_begin)); +#else + m_size = size; +#endif + } + + // allocate an uninitialized buffer of the specified size + // and copy the initialization range into the start of the buffer + buffer(difference_type const size, span initialize) + : buffer(size) + { + TORRENT_ASSERT(initialize.size() <= size); + if (!initialize.empty()) + { + std::copy(initialize.begin(), initialize.begin() + + (std::min)(initialize.size(), size), m_begin); + } + } + + buffer(buffer const& b) = delete; + + buffer(buffer&& b) + : m_begin(b.m_begin) + , m_size(b.m_size) + { + b.m_begin = nullptr; + b.m_size = 0; + } + + buffer& operator=(buffer&& b) + { + if (&b == this) return *this; + std::free(m_begin); + m_begin = b.m_begin; + m_size = b.m_size; + b.m_begin = nullptr; + b.m_size = 0; + return *this; + } + + buffer& operator=(buffer const& b) = delete; + + ~buffer() { std::free(m_begin); } + + char* data() { return m_begin; } + char const* data() const { return m_begin; } + difference_type size() const { return m_size; } + + bool empty() const { return m_size == 0; } + char& operator[](index_type const i) { TORRENT_ASSERT(i < size()); return m_begin[i]; } + char const& operator[](difference_type const i) const { TORRENT_ASSERT(i < size()); return m_begin[i]; } + + char* begin() { return m_begin; } + char const* begin() const { return m_begin; } + char* end() { return m_begin + m_size; } + char const* end() const { return m_begin + m_size; } + + void swap(buffer& b) + { + using std::swap; + swap(m_begin, b.m_begin); + swap(m_size, b.m_size); + } + +private: + char* m_begin = nullptr; + // m_begin points to an allocation of this size. + difference_type m_size = 0; +}; + +} +} + +#endif // TORRENT_BUFFER_HPP_INCLUDED diff --git a/include/libtorrent/aux_/byteswap.hpp b/include/libtorrent/aux_/byteswap.hpp new file mode 100644 index 0000000..5d93b40 --- /dev/null +++ b/include/libtorrent/aux_/byteswap.hpp @@ -0,0 +1,100 @@ +/* + +Copyright (c) 2010, 2014-2017, 2020, Arvid Norberg +Copyright (c) 2015, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BYTESWAP_HPP_INCLUDED +#define TORRENT_BYTESWAP_HPP_INCLUDED + +// this header makes sure htonl(), nothl(), htons() and ntohs() +// are available + +#include "libtorrent/config.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#ifdef TORRENT_WINDOWS +#include +#else +// posix header +// for ntohl and htonl +#include +#endif + +namespace libtorrent { namespace aux { +// these need to be within the disabled warnings because on OSX +// the htonl and ntohl macros cause lots of old-style case warnings +inline std::uint32_t host_to_network(std::uint32_t x) +{ return htonl(x); } + +inline std::uint32_t network_to_host(std::uint32_t x) +{ return ntohl(x); } + +inline std::uint16_t host_to_network(std::uint16_t x) +{ return htons(x); } + +inline std::uint16_t network_to_host(std::uint16_t x) +{ return ntohs(x); } + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +inline std::uint32_t swap_byteorder(std::uint32_t const x) +{ +#ifdef __GNUC__ + return __builtin_bswap32(x); +#else + return (x & 0xff000000) >> 24 + | (x & 0x00ff0000) >> 8 + | (x & 0x0000ff00) << 8 + | (x & 0x000000ff) << 24; +#endif +} + +inline std::uint32_t little_endian_to_host(std::uint32_t x) +{ +#if BOOST_ENDIAN_BIG_BYTE + return swap_byteorder(x); +#elif BOOST_ENDIAN_LITTLE_BYTE + return x; +#else +#error "unknown endian" +#endif +} + +} +} + +#endif // TORRENT_BYTESWAP_HPP_INCLUDED + diff --git a/include/libtorrent/aux_/chained_buffer.hpp b/include/libtorrent/aux_/chained_buffer.hpp new file mode 100644 index 0000000..f45b332 --- /dev/null +++ b/include/libtorrent/aux_/chained_buffer.hpp @@ -0,0 +1,235 @@ +/* + +Copyright (c) 2007, 2011, 2013-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CHAINED_BUFFER_HPP_INCLUDED +#define TORRENT_CHAINED_BUFFER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/aux_/buffer.hpp" + +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#ifdef _MSC_VER +// visual studio requires the value in a deque to be copyable. C++11 +// has looser requirements depending on which functions are actually used. +#define TORRENT_CPP98_DEQUE 1 +#else +#define TORRENT_CPP98_DEQUE 0 +#endif + +namespace libtorrent { +namespace aux { + + // TODO: 2 this type should probably be renamed to send_buffer + struct TORRENT_EXTRA_EXPORT chained_buffer : private single_threaded + { + chained_buffer(): m_bytes(0), m_capacity(0) + { + thread_started(); +#if TORRENT_USE_ASSERTS + m_destructed = false; +#endif + } + + private: + + // destructs/frees the holder object + using destruct_holder_fun = void (*)(void*); + using move_construct_holder_fun = void (*)(void*, void*); + + struct buffer_t + { + buffer_t() {} +#if TORRENT_CPP98_DEQUE + buffer_t(buffer_t&& rhs) noexcept + { + destruct_holder = rhs.destruct_holder; + move_holder = rhs.move_holder; + buf = rhs.buf; + size = rhs.size; + used_size = rhs.used_size; + move_holder(&holder, &rhs.holder); + } + buffer_t& operator=(buffer_t&& rhs) & noexcept + { + destruct_holder(&holder); + destruct_holder = rhs.destruct_holder; + move_holder = rhs.move_holder; + buf = rhs.buf; + size = rhs.size; + used_size = rhs.used_size; + move_holder(&holder, &rhs.holder); + return *this; + } + buffer_t(buffer_t const& rhs) noexcept + : buffer_t(std::move(const_cast(rhs))) {} + buffer_t& operator=(buffer_t const& rhs) & noexcept + { return this->operator=(std::move(const_cast(rhs))); } +#else + buffer_t(buffer_t&&) = delete; + buffer_t& operator=(buffer_t&&) = delete; + buffer_t(buffer_t const&) = delete; + buffer_t& operator=(buffer_t const&) = delete; +#endif + + destruct_holder_fun destruct_holder; +#if TORRENT_CPP98_DEQUE + move_construct_holder_fun move_holder; +#endif + alignas(alignof(std::max_align_t)) std::array holder; + + char* buf = nullptr; // the first byte of the buffer + int size = 0; // the total size of the buffer + int used_size = 0; // this is the number of bytes to send/receive + }; + + public: + + bool empty() const { return m_bytes == 0; } + int size() const { return m_bytes; } + int capacity() const { return m_capacity; } + + void pop_front(int bytes_to_pop); + + template + void append_buffer(Holder buffer, int used_size) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(int(buffer.size()) >= used_size); + m_vec.emplace_back(); + buffer_t& b = m_vec.back(); + init_buffer_entry(b, std::move(buffer), used_size); + } + + template + void prepend_buffer(Holder buffer, int used_size) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(int(buffer.size()) >= used_size); + m_vec.emplace_front(); + buffer_t& b = m_vec.front(); + init_buffer_entry(b, std::move(buffer), used_size); + } + + // returns the number of bytes available at the + // end of the last chained buffer. + int space_in_last_buffer(); + + // tries to copy the given buffer to the end of the + // last chained buffer. If there's not enough room + // it returns nullptr + char* append(span buf); + + // tries to allocate memory from the end + // of the last buffer. If there isn't + // enough room, returns 0 + char* allocate_appendix(int s); + + span build_iovec(int to_send); + + void clear(); + + void build_mutable_iovec(int bytes, std::vector>& vec); + + ~chained_buffer(); + + private: + + template + void init_buffer_entry(buffer_t& b, Holder buf, int used_size) + { + static_assert(sizeof(Holder) <= sizeof(b.holder), "buffer holder too large"); + + b.buf = buf.data(); + b.size = static_cast(buf.size()); + b.used_size = used_size; + +#ifdef _MSC_VER +// this appears to be a false positive msvc warning +#pragma warning(push, 1) +#pragma warning(disable : 4100) +#endif + b.destruct_holder = [](void* holder) + { reinterpret_cast(holder)->~Holder(); }; + +#if TORRENT_CPP98_DEQUE + b.move_holder = [](void* dst, void* src) + { new (dst) Holder(std::move(*reinterpret_cast(src))); }; +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + new (&b.holder) Holder(std::move(buf)); + + m_bytes += used_size; + TORRENT_ASSERT(m_capacity < (std::numeric_limits::max)() - b.size); + m_capacity += b.size; + TORRENT_ASSERT(m_bytes <= m_capacity); + } + + template + void build_vec(int bytes, std::vector& vec); + + // this is the list of all the buffers we want to + // send + std::deque m_vec; + + // this is the number of bytes in the send buf. + // this will always be equal to the sum of the + // size of all buffers in vec + int m_bytes; + + // the total size of all buffers in the chain + // including unused space + int m_capacity; + + // this is the vector of buffers used when + // invoking the async write call + std::vector m_tmp_vec; + +#if TORRENT_USE_ASSERTS + bool m_destructed; +#endif + }; +} +} + +#endif diff --git a/include/libtorrent/aux_/container_wrapper.hpp b/include/libtorrent/aux_/container_wrapper.hpp new file mode 100644 index 0000000..a9ac9e3 --- /dev/null +++ b/include/libtorrent/aux_/container_wrapper.hpp @@ -0,0 +1,136 @@ +/* + +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CONTAINER_WRAPPER_HPP +#define TORRENT_CONTAINER_WRAPPER_HPP + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/index_range.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" + +#include + +namespace libtorrent { namespace aux { + + template + struct container_wrapper : Base + { + using underlying_index = typename underlying_index_t::type; + + // pull in constructors from Base class + using Base::Base; + container_wrapper() = default; + // tested to fail with _MSC_VER <= 1916. The actual version condition +#if !defined _MSC_VER + constexpr +#endif + explicit container_wrapper(Base&& b) : Base(std::move(b)) {} + + explicit container_wrapper(IndexType const s) + : Base(numeric_cast(static_cast(s))) {} + + decltype(auto) operator[](IndexType idx) const + { + TORRENT_ASSERT(idx >= IndexType(0)); + TORRENT_ASSERT(idx < end_index()); + return this->Base::operator[](std::size_t(static_cast(idx))); + } + + decltype(auto) operator[](IndexType idx) + { + TORRENT_ASSERT(idx >= IndexType(0)); + TORRENT_ASSERT(idx < end_index()); + return this->Base::operator[](std::size_t(static_cast(idx))); + } + + IndexType end_index() const + { + TORRENT_ASSERT(this->size() <= std::size_t((std::numeric_limits::max)())); + return IndexType(numeric_cast(this->size())); + } + + // returns an object that can be used in a range-for to iterate over all + // indices + index_range range() const noexcept + { + return {IndexType{0}, end_index()}; + } + + template ::value>::type> + void resize(underlying_index s) + { + TORRENT_ASSERT(s >= 0); + this->Base::resize(std::size_t(s)); + } + + template ::value>::type> + void resize(underlying_index s, T const& v) + { + TORRENT_ASSERT(s >= 0); + this->Base::resize(std::size_t(s), v); + } + + void resize(std::size_t s) + { + TORRENT_ASSERT(s <= std::size_t((std::numeric_limits::max)())); + this->Base::resize(s); + } + + void resize(std::size_t s, T const& v) + { + TORRENT_ASSERT(s <= std::size_t((std::numeric_limits::max)())); + this->Base::resize(s, v); + } + + template ::value>::type> + void reserve(underlying_index s) + { + TORRENT_ASSERT(s >= 0); + this->Base::reserve(std::size_t(s)); + } + + void reserve(std::size_t s) + { + TORRENT_ASSERT(s <= std::size_t((std::numeric_limits::max)())); + this->Base::reserve(s); + } + }; +}} + +#endif + diff --git a/include/libtorrent/aux_/cpuid.hpp b/include/libtorrent/aux_/cpuid.hpp new file mode 100644 index 0000000..a8a9ed4 --- /dev/null +++ b/include/libtorrent/aux_/cpuid.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2010, 2014-2017, 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CPUID_HPP_INCLUDED +#define TORRENT_CPUID_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +namespace libtorrent { namespace aux { + + // initialized by static initializers (in cpuid.cpp) + TORRENT_EXTRA_EXPORT extern bool const sse42_support; + TORRENT_EXTRA_EXPORT extern bool const mmx_support; + TORRENT_EXTRA_EXPORT extern bool const arm_neon_support; + TORRENT_EXTRA_EXPORT extern bool const arm_crc32c_support; +} } + +#endif // TORRENT_CPUID_HPP_INCLUDED diff --git a/include/libtorrent/aux_/deferred_handler.hpp b/include/libtorrent/aux_/deferred_handler.hpp new file mode 100644 index 0000000..57e7903 --- /dev/null +++ b/include/libtorrent/aux_/deferred_handler.hpp @@ -0,0 +1,81 @@ +/* + +Copyright (c) 2017, 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DEFERRED_HANDLER_HPP +#define TORRENT_DEFERRED_HANDLER_HPP + +#include "libtorrent/assert.hpp" +#include "libtorrent/io_context.hpp" + +namespace libtorrent { namespace aux { + +template +struct handler_wrapper +{ + handler_wrapper(bool& in_flight, Handler&& h) + : m_handler(std::move(h)) + , m_in_flight(in_flight) {} + + template + void operator()(Args&&... a) + { + TORRENT_ASSERT(m_in_flight); + m_in_flight = false; + m_handler(std::forward(a)...); + } + + // forward allocator to the underlying handler's + using allocator_type = typename Handler::allocator_type; + + allocator_type get_allocator() const noexcept + { return m_handler.get_allocator(); } + +private: + Handler m_handler; + bool& m_in_flight; +}; + +struct deferred_handler +{ + template + void post_deferred(lt::io_context& ios, Handler&& h) + { + if (m_in_flight) return; + m_in_flight = true; + post(ios, handler_wrapper(m_in_flight, std::forward(h))); + } +private: + bool m_in_flight = false; +}; + +}} +#endif diff --git a/include/libtorrent/aux_/deprecated.hpp b/include/libtorrent/aux_/deprecated.hpp new file mode 100644 index 0000000..bd53e52 --- /dev/null +++ b/include/libtorrent/aux_/deprecated.hpp @@ -0,0 +1,68 @@ +/* + +Copyright (c) 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DEPRECATED_HPP_INCLUDED +#define TORRENT_DEPRECATED_HPP_INCLUDED + +#if !defined TORRENT_BUILDING_LIBRARY +# define TORRENT_DEPRECATED [[deprecated]] +#else +# define TORRENT_DEPRECATED +#endif + +#if defined __clang__ + +// ====== CLANG ======== + +# if !defined TORRENT_BUILDING_LIBRARY +// TODO: figure out which version of clang this is supported in +# define TORRENT_DEPRECATED_ENUM __attribute__ ((deprecated)) +# endif + +#elif defined __GNUC__ + +// ======== GCC ======== + +// deprecation markup is only enabled when libtorrent +// headers are included by clients, not while building +// libtorrent itself +# if __GNUC__ >= 6 && !defined TORRENT_BUILDING_LIBRARY +# define TORRENT_DEPRECATED_ENUM __attribute__ ((deprecated)) +# endif + +#endif + +#ifndef TORRENT_DEPRECATED_ENUM +#define TORRENT_DEPRECATED_ENUM +#endif + +#endif diff --git a/include/libtorrent/aux_/deque.hpp b/include/libtorrent/aux_/deque.hpp new file mode 100644 index 0000000..dc18a78 --- /dev/null +++ b/include/libtorrent/aux_/deque.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2017-2018, 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DEQUE_HPP +#define TORRENT_DEQUE_HPP + +#include + +#include "libtorrent/aux_/container_wrapper.hpp" + +namespace libtorrent { namespace aux { + + template + using deque = container_wrapper>; + +}} + +#endif diff --git a/include/libtorrent/aux_/dev_random.hpp b/include/libtorrent/aux_/dev_random.hpp new file mode 100644 index 0000000..7f4dd31 --- /dev/null +++ b/include/libtorrent/aux_/dev_random.hpp @@ -0,0 +1,80 @@ +/* + +Copyright (c) 2017, Alden Torres +Copyright (c) 2017-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DEV_RANDOM_HPP_INCLUDED +#define TORRENT_DEV_RANDOM_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/error_code.hpp" // for system_error +#include "libtorrent/aux_/throw.hpp" + +#include + +namespace libtorrent { namespace aux { + + struct dev_random + { + // the choice of /dev/urandom over /dev/random is based on: + // https://www.mail-archive.com/cryptography@randombit.net/msg04763.html + // https://security.stackexchange.com/questions/3936/is-a-rand-from-dev-urandom-secure-for-a-login-key/3939#3939 + dev_random() + : m_fd(::open("/dev/urandom", O_RDONLY)) + { + if (m_fd < 0) + { + throw_ex(error_code(errno, system_category())); + } + } + dev_random(dev_random const&) = delete; + dev_random& operator=(dev_random const&) = delete; + + void read(span buffer) + { + std::int64_t const ret = ::read(m_fd, buffer.data() + , static_cast(buffer.size())); + if (ret != int(buffer.size())) + { + throw_ex(errors::no_entropy); + } + } + + ~dev_random() { ::close(m_fd); } + + private: + int m_fd; + }; +}} + +#endif + diff --git a/include/libtorrent/aux_/directory.hpp b/include/libtorrent/aux_/directory.hpp new file mode 100644 index 0000000..db34f28 --- /dev/null +++ b/include/libtorrent/aux_/directory.hpp @@ -0,0 +1,79 @@ +/* + +Copyright (c) 2016-2017, Steven Siloti +Copyright (c) 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DIRECTORY_HPP_INCLUDED +#define TORRENT_DIRECTORY_HPP_INCLUDED + +#include +#include "libtorrent/error_code.hpp" + +#ifdef TORRENT_WINDOWS +// windows part +#include "libtorrent/aux_/windows.hpp" +#include +#include +#else +// posix part + +#include // for DIR + +#endif +namespace libtorrent { +namespace aux { + +struct TORRENT_EXTRA_EXPORT directory +{ + directory(std::string const& path, error_code& ec); + ~directory(); + + directory(directory const&) = delete; + directory& operator=(directory const&) = delete; + + void next(error_code& ec); + std::string file() const; + bool done() const { return m_done; } +private: +#ifdef TORRENT_WINDOWS + HANDLE m_handle; + WIN32_FIND_DATAW m_fd; +#else + DIR* m_handle; + std::string m_name; +#endif + bool m_done; +}; + + +} +} +#endif diff --git a/include/libtorrent/aux_/disable_deprecation_warnings_push.hpp b/include/libtorrent/aux_/disable_deprecation_warnings_push.hpp new file mode 100644 index 0000000..fb2079f --- /dev/null +++ b/include/libtorrent/aux_/disable_deprecation_warnings_push.hpp @@ -0,0 +1,51 @@ +/* + +Copyright (c) 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated" +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#if __GNUC__ >= 9 +#pragma GCC diagnostic ignored "-Wdeprecated-copy" +#endif +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + +#ifdef _MSC_VER +#pragma warning(push, 1) +#pragma warning(disable: 4996) +#endif diff --git a/include/libtorrent/aux_/disable_warnings_pop.hpp b/include/libtorrent/aux_/disable_warnings_pop.hpp new file mode 100644 index 0000000..5cf7efe --- /dev/null +++ b/include/libtorrent/aux_/disable_warnings_pop.hpp @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif diff --git a/include/libtorrent/aux_/disable_warnings_push.hpp b/include/libtorrent/aux_/disable_warnings_push.hpp new file mode 100644 index 0000000..6ef8303 --- /dev/null +++ b/include/libtorrent/aux_/disable_warnings_push.hpp @@ -0,0 +1,116 @@ +/* + +Copyright (c) 2015-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016-2017, 2019, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wall" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wswitch-enum" +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wundef" +#pragma GCC diagnostic ignored "-Wmissing-noreturn" +#pragma GCC diagnostic ignored "-Wdeprecated" +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wpedantic" +#pragma GCC diagnostic ignored "-Wunused-local-typedefs" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#if __GNUC__ >= 6 +#pragma GCC diagnostic ignored "-Wshift-overflow" +#pragma GCC diagnostic ignored "-Wshift-count-overflow" +#pragma GCC diagnostic ignored "-Wshift-count-negative" +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif +#if __GNUC__ >= 7 +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#pragma GCC diagnostic ignored "-Wnoexcept-type" +#endif +#if __GNUC__ >= 8 +#pragma GCC diagnostic ignored "-Wcast-function-type" +#endif +#if __GNUC__ >= 9 +#pragma GCC diagnostic ignored "-Wdeprecated-copy" +#endif +#if __GNUC__ >= 12 +#pragma GCC diagnostic ignored "-Woverflow" +#endif +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wall" +#pragma clang diagnostic ignored "-Weverything" +#pragma clang diagnostic ignored "-Wsign-conversion" +#pragma clang diagnostic ignored "-Wconversion" +#pragma clang diagnostic ignored "-Wswitch-enum" +#pragma clang diagnostic ignored "-Wcovered-switch-default" +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wundef" +#pragma clang diagnostic ignored "-Wweak-vtables" +#pragma clang diagnostic ignored "-Wmissing-noreturn" +#pragma clang diagnostic ignored "-Wdeprecated" +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#pragma clang diagnostic ignored "-Wcast-align" +#pragma clang diagnostic ignored "-Wweak-vtable" +#pragma clang diagnostic ignored "-Wundef" +#pragma clang diagnostic ignored "-Wshadow" +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +#pragma clang diagnostic ignored "-Wc++11-long-long" +#pragma clang diagnostic ignored "-Wc++11-extensions" +#pragma clang diagnostic ignored "-Wextra-semi" +#pragma clang diagnostic ignored "-Wunused-parameter" +#pragma clang diagnostic ignored "-Wreserved-id-macro" +#pragma clang diagnostic ignored "-Wunused-local-typedef" +#pragma clang diagnostic ignored "-Wdisabled-macro-expansion" +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wgnu-folding-constant" +#pragma clang diagnostic ignored "-Wdouble-promotion" +#pragma clang diagnostic ignored "-Wdocumentation-unknown-command" +#pragma clang diagnostic ignored "-Wfloat-equal" +#pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare" +#endif + +#ifdef _MSC_VER +#pragma warning(push, 1) +// warning C4005: macro redefinition +#pragma warning(disable : 4005) +// expression before comma has no effect; expected expression with side-effect +#pragma warning(disable : 4548) +// 'conversion' conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable : 4244) +// potentially uninitialized local variable 'result' used +#pragma warning(disable : 4701) +#endif diff --git a/include/libtorrent/aux_/disk_buffer_pool.hpp b/include/libtorrent/aux_/disk_buffer_pool.hpp new file mode 100644 index 0000000..fff7a9b --- /dev/null +++ b/include/libtorrent/aux_/disk_buffer_pool.hpp @@ -0,0 +1,132 @@ +/* + +Copyright (c) 2011, 2014-2017, 2019-2020, 2022, Arvid Norberg +Copyright (c) 2016, 2020, Alden Torres +Copyright (c) 2016, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_BUFFER_POOL_HPP +#define TORRENT_DISK_BUFFER_POOL_HPP + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_INVARIANT_CHECKS +#include +#endif +#include +#include +#include +#include + +#include "libtorrent/io_context.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/disk_buffer_holder.hpp" // for buffer_allocator_interface + +namespace libtorrent { + + struct settings_interface; + struct disk_observer; + +namespace aux { + + struct TORRENT_EXTRA_EXPORT disk_buffer_pool final + : buffer_allocator_interface + { + explicit disk_buffer_pool(io_context& ios); + ~disk_buffer_pool(); + disk_buffer_pool(disk_buffer_pool const&) = delete; + disk_buffer_pool& operator=(disk_buffer_pool const&) = delete; + + char* allocate_buffer(char const* category); + char* allocate_buffer(bool& exceeded, std::shared_ptr o + , char const* category); + void free_disk_buffer(char* b) override { free_buffer(b); } + void free_buffer(char* buf); + void free_multiple_buffers(span bufvec); + + int in_use() const + { + std::unique_lock l(m_pool_mutex); + return m_in_use; + } + + void set_settings(settings_interface const& sett); + + private: + + void free_buffer_impl(char* buf, std::unique_lock& l); + char* allocate_buffer_impl(std::unique_lock& l, char const* category); + + // number of disk buffers currently allocated + int m_in_use; + + // cache size limit + int m_max_use; + + // if we have exceeded the limit, we won't start + // allowing allocations again until we drop below + // this low watermark + int m_low_watermark; + + // if we exceed the max number of buffers, we start + // adding up callbacks to this queue. Once the number + // of buffers in use drops below the low watermark, + // we start calling these functions back + std::vector> m_observers; + + // set to true to throttle more allocations + bool m_exceeded_max_size; + + // this is the main thread io_context. Callbacks are + // posted on this in order to have them execute in + // the main thread. + io_context& m_ios; + + void check_buffer_level(std::unique_lock& l); + void remove_buffer_in_use(char* buf); + + mutable std::mutex m_pool_mutex; + + // this is specifically exempt from release_asserts + // since it's a quite costly check. Only for debug + // builds. +#if TORRENT_USE_INVARIANT_CHECKS + std::set m_buffers_in_use; +#endif +#if TORRENT_USE_ASSERTS + int m_magic = 0x1337; + bool m_settings_set = false; +#endif + }; + +} +} + +#endif // TORRENT_DISK_BUFFER_POOL_HPP diff --git a/include/libtorrent/aux_/disk_io_thread_pool.hpp b/include/libtorrent/aux_/disk_io_thread_pool.hpp new file mode 100644 index 0000000..667355d --- /dev/null +++ b/include/libtorrent/aux_/disk_io_thread_pool.hpp @@ -0,0 +1,150 @@ +/* + +Copyright (c) 2016, 2018, 2020, Alden Torres +Copyright (c) 2016, Steven Siloti +Copyright (c) 2017-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. +* Neither the name of the author nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_IO_THREAD_POOL +#define TORRENT_DISK_IO_THREAD_POOL + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/error_code.hpp" + +#include +#include +#include + +namespace libtorrent { +namespace aux { + + struct disk_io_thread_pool; + + struct pool_thread_interface + { + virtual ~pool_thread_interface() {} + + virtual void notify_all() = 0; + virtual void thread_fun(disk_io_thread_pool&, executor_work_guard) = 0; + }; + + // this class implements the policy for creating and destroying I/O threads + // threads are created when job_queued is called to signal the arrival of + // new jobs + // once a minute threads are destroyed if at least one thread has been + // idle for the entire minute + // the pool_thread_interface is used to spawn and notify the worker threads + struct TORRENT_EXTRA_EXPORT disk_io_thread_pool + { + disk_io_thread_pool(pool_thread_interface& thread_iface + , io_context& ios); + ~disk_io_thread_pool(); + + // set the maximum number of I/O threads which may be running + // the actual number of threads will be <= this number + void set_max_threads(int i); + void abort(bool wait); + int max_threads() const { return m_max_threads; } + + // thread_idle, thread_active, and job_queued are NOT thread safe + // all calls to them must be serialized + // it is expected that they will be called while holding the + // job queue mutex + + // these functions should be called by the thread_fun to signal its state + // threads are considered active when they are started so thread_idle should + // be called first + // these calls are not thread safe + void thread_idle() { ++m_num_idle_threads; } + void thread_active(); + + // check if there is an outstanding request for I/O threads to stop + // this is a weak check, if it returns true try_thread_exit may still + // return false + bool should_exit() { return m_threads_to_exit > 0; } + // this should be the last function an I/O thread calls before breaking + // out of its service loop + // if it returns true then the thread MUST exit + // if it returns false the thread should not exit + bool try_thread_exit(std::thread::id id); + + // get the thread id of the first thread in the internal vector + // since this is the first thread it will remain the same until the first + // thread exits + // it can be used to trigger maintenance jobs which should only run on one thread + std::thread::id first_thread_id(); + int num_threads() + { + std::lock_guard l(m_mutex); + return int(m_threads.size()); + } + + // this should be called whenever new jobs are queued + // queue_size is the current size of the job queue + // not thread safe + void job_queued(int queue_size); + + private: + void reap_idle_threads(error_code const& ec); + + // the caller must hold m_mutex + void stop_threads(int num_to_stop); + + pool_thread_interface& m_thread_iface; + + std::atomic m_max_threads; + // the number of threads the reaper decided should exit + std::atomic m_threads_to_exit; + + // must hold m_mutex to access + bool m_abort; + + std::atomic m_num_idle_threads; + // the minimum number of idle threads seen since the last reaping + std::atomic m_min_idle_threads; + + // ensures thread creation/destruction is atomic + std::mutex m_mutex; + + // the actual threads running disk jobs + std::vector m_threads; + + // timer to check for and reap idle threads + deadline_timer m_idle_timer; + + io_context& m_ioc; + }; +} +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/aux_/disk_job_fence.hpp b/include/libtorrent/aux_/disk_job_fence.hpp new file mode 100644 index 0000000..35df14e --- /dev/null +++ b/include/libtorrent/aux_/disk_job_fence.hpp @@ -0,0 +1,118 @@ +/* + +Copyright (c) 2016-2020, 2022, Arvid Norberg +Copyright (c) 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_JOB_FENCE_HPP_INCLUDE +#define TORRENT_DISK_JOB_FENCE_HPP_INCLUDE + +#include "libtorrent/config.hpp" +#include "libtorrent/tailqueue.hpp" + +#include +#include + +namespace libtorrent { + +struct counters; + +namespace aux { + + struct mmap_disk_job; + + // implements the disk I/O job fence used by the default_storage + // to provide to the disk thread. Whenever a disk job needs + // exclusive access to the storage for that torrent, it raises + // the fence, blocking all new jobs, until there are no longer + // any outstanding jobs on the torrent, then the fence is lowered + // and it can be performed, along with the backlog of jobs that + // accrued while the fence was up + struct TORRENT_EXTRA_EXPORT disk_job_fence + { + disk_job_fence() = default; + +#if TORRENT_USE_ASSERTS + ~disk_job_fence() + { + TORRENT_ASSERT(int(m_outstanding_jobs) == 0); + TORRENT_ASSERT(m_blocked_jobs.size() == 0); + } +#endif + + // returns one of the fence_* enums. + // if there are no outstanding jobs on the + // storage, fence_post_fence is returned. + // fence_post_none if the fence job was queued. + enum { fence_post_fence = 0, fence_post_none = 1 }; + int raise_fence(mmap_disk_job*, counters&); + bool has_fence() const; + + // called whenever a job completes and is posted back to the + // main network thread. the tailqueue of jobs will have the + // backed-up jobs prepended to it in case this resulted in the + // fence being lowered. + int job_complete(mmap_disk_job*, tailqueue&); + int num_outstanding_jobs() const { return m_outstanding_jobs; } + + // if there is a fence up, returns true and adds the job + // to the queue of blocked jobs + bool is_blocked(mmap_disk_job*); + + // the number of blocked jobs + int num_blocked() const; + + private: + // when > 0, this storage is blocked for new async + // operations until all outstanding jobs have completed. + // at that point, the m_blocked_jobs are issued + // the count is the number of fence job currently in the queue + int m_has_fence = 0; + + // when there's a fence up, jobs are queued up in here + // until the fence is lowered + tailqueue m_blocked_jobs; + + // the number of mmap_disk_job objects there are, belonging + // to this torrent, currently pending, hanging off of + // cached_piece_entry objects. This is used to determine + // when the fence can be lowered + std::atomic m_outstanding_jobs{0}; + + // must be held when accessing m_has_fence and + // m_blocked_jobs + mutable std::mutex m_mutex; + }; + + +}} + +#endif + diff --git a/include/libtorrent/aux_/disk_job_pool.hpp b/include/libtorrent/aux_/disk_job_pool.hpp new file mode 100644 index 0000000..30cca98 --- /dev/null +++ b/include/libtorrent/aux_/disk_job_pool.hpp @@ -0,0 +1,75 @@ +/* + +Copyright (c) 2010, 2013-2017, 2020, 2022, Arvid Norberg +Copyright (c) 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_JOB_POOL +#define TORRENT_DISK_JOB_POOL + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/mmap_disk_job.hpp" // for job_action_t +#include "libtorrent/aux_/pool.hpp" +#include + +namespace libtorrent { +namespace aux { + + struct mmap_disk_job; + + struct TORRENT_EXTRA_EXPORT disk_job_pool + { + disk_job_pool(); + ~disk_job_pool(); + + mmap_disk_job* allocate_job(job_action_t type); + void free_job(mmap_disk_job* j); + void free_jobs(mmap_disk_job** j, int num); + + int jobs_in_use() const { return m_jobs_in_use; } + int read_jobs_in_use() const { return m_read_jobs; } + int write_jobs_in_use() const { return m_write_jobs; } + + private: + + // total number of in-use jobs + int m_jobs_in_use; + // total number of in-use read jobs + int m_read_jobs; + // total number of in-use write jobs + int m_write_jobs; + + std::mutex m_job_mutex; + aux::object_pool m_job_pool; + }; +} +} + +#endif // TORRENT_DISK_JOB_POOL diff --git a/include/libtorrent/aux_/drive_info.hpp b/include/libtorrent/aux_/drive_info.hpp new file mode 100644 index 0000000..ff231de --- /dev/null +++ b/include/libtorrent/aux_/drive_info.hpp @@ -0,0 +1,49 @@ +/* + +Copyright (c) 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include + +namespace libtorrent { +namespace aux { + +enum class drive_info +{ + spinning, + ssd_disk, + ssd_dax, + remote, +}; + +drive_info get_drive_info(std::string const& path); + +} +} diff --git a/include/libtorrent/aux_/ed25519.hpp b/include/libtorrent/aux_/ed25519.hpp new file mode 100644 index 0000000..8f7f2db --- /dev/null +++ b/include/libtorrent/aux_/ed25519.hpp @@ -0,0 +1,18 @@ +#ifndef ED25519_HPP +#define ED25519_HPP + +#include "libtorrent/aux_/export.hpp" // for TORRENT_EXPORT +#include // for ptrdiff_t, size_t + +namespace libtorrent { +namespace aux { + +void TORRENT_EXTRA_EXPORT ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed); +void TORRENT_EXTRA_EXPORT ed25519_sign(unsigned char *signature, const unsigned char *message, std::ptrdiff_t message_len, const unsigned char *public_key, const unsigned char *private_key); +int TORRENT_EXTRA_EXPORT ed25519_verify(const unsigned char *signature, const unsigned char *message, std::ptrdiff_t message_len, const unsigned char *public_key); +void TORRENT_EXTRA_EXPORT ed25519_add_scalar(unsigned char *public_key, unsigned char *private_key, const unsigned char *scalar); +void TORRENT_EXTRA_EXPORT ed25519_key_exchange(unsigned char *shared_secret, const unsigned char *public_key, const unsigned char *private_key); + +} } + +#endif // ED25519_HPP diff --git a/include/libtorrent/aux_/escape_string.hpp b/include/libtorrent/aux_/escape_string.hpp new file mode 100644 index 0000000..0f039da --- /dev/null +++ b/include/libtorrent/aux_/escape_string.hpp @@ -0,0 +1,96 @@ +/* + +Copyright (c) 2004-2005, 2007, 2009, 2012-2018, 2020, Arvid Norberg +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ESCAPE_STRING_HPP_INCLUDED +#define TORRENT_ESCAPE_STRING_HPP_INCLUDED + +#include +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/flags.hpp" +#if TORRENT_USE_I2P +#include +#include "libtorrent/span.hpp" +#endif + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT std::string unescape_string(string_view s, error_code& ec); + // replaces all disallowed URL characters by their %-encoding + TORRENT_EXTRA_EXPORT std::string escape_string(string_view str); + // same as escape_string but does not encode '/' + TORRENT_EXTRA_EXPORT std::string escape_path(string_view str); + // if the url does not appear to be encoded, and it contains illegal url characters + // it will be encoded + TORRENT_EXTRA_EXPORT std::string maybe_url_encode(std::string const& url); + + TORRENT_EXTRA_EXPORT string_view trim(string_view); + TORRENT_EXTRA_EXPORT string_view::size_type find(string_view haystack + , string_view needle, string_view::size_type pos); + + // returns true if the given string (not 0-terminated) contains + // characters that would need to be escaped if used in a URL + TORRENT_EXTRA_EXPORT bool need_encoding(char const* str, int len); + + // encodes a string using the base64 scheme + TORRENT_EXTRA_EXPORT std::string base64encode(std::string const& s); +#if TORRENT_USE_I2P + // encodes a string using the base32 scheme + TORRENT_EXTRA_EXPORT std::string base32encode_i2p(span s); + TORRENT_EXTRA_EXPORT std::vector base64decode_i2p(string_view s); +#endif + TORRENT_EXTRA_EXPORT std::string base32decode(string_view s); + + // replaces \ with / + TORRENT_EXTRA_EXPORT void convert_path_to_posix(std::string& path); + + TORRENT_EXTRA_EXPORT std::string read_until(char const*& str, char delim + , char const* end); + +#if defined TORRENT_WINDOWS + TORRENT_EXTRA_EXPORT std::wstring convert_to_wstring(std::string const& s); + TORRENT_EXTRA_EXPORT std::string convert_from_wstring(std::wstring const& s); +#endif + +#if TORRENT_NATIVE_UTF8 + inline std::string const& convert_to_native(std::string const& s) { return s; } + inline std::string const& convert_from_native(std::string const& s) { return s; } +#else + TORRENT_EXTRA_EXPORT std::string convert_to_native(std::string const& s); + TORRENT_EXTRA_EXPORT std::string convert_from_native(std::string const& s); +#endif +} + +#endif // TORRENT_ESCAPE_STRING_HPP_INCLUDED diff --git a/include/libtorrent/aux_/export.hpp b/include/libtorrent/aux_/export.hpp new file mode 100644 index 0000000..77a744f --- /dev/null +++ b/include/libtorrent/aux_/export.hpp @@ -0,0 +1,157 @@ +/* + +Copyright (c) 2014-2015, 2017-2020, Arvid Norberg +Copyright (c) 2019, Alden Torres +Copyright (c) 2019, Steven Siloti +Copyright (c) 2021, Matthew Guidry +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_EXPORT_HPP_INCLUDED +#define TORRENT_EXPORT_HPP_INCLUDED + +#include +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/deprecated.hpp" + +#if !defined TORRENT_ABI_VERSION +# ifdef TORRENT_NO_DEPRECATE +# define TORRENT_ABI_VERSION 3 +# else +# define TORRENT_ABI_VERSION 1 +# endif +#endif + +#if TORRENT_ABI_VERSION >= 3 +# define TORRENT_VERSION_NAMESPACE_3 inline namespace v2 { +# define TORRENT_VERSION_NAMESPACE_3_END } +#else +# define TORRENT_VERSION_NAMESPACE_3 +# define TORRENT_VERSION_NAMESPACE_3_END +#endif + +#if TORRENT_ABI_VERSION >= 2 +# define TORRENT_VERSION_NAMESPACE_2 inline namespace v1_2 { +# define TORRENT_VERSION_NAMESPACE_2_END } +#else +# define TORRENT_VERSION_NAMESPACE_2 +# define TORRENT_VERSION_NAMESPACE_2_END +#endif + +#ifdef TORRENT_USE_LIBGCRYPT +# define TORRENT_CRYPTO_NAMESPACE inline namespace gcry { +# define TORRENT_CRYPTO_NAMESPACE_END } +#elif TORRENT_USE_COMMONCRYPTO +# define TORRENT_CRYPTO_NAMESPACE inline namespace cc { +# define TORRENT_CRYPTO_NAMESPACE_END } +#elif TORRENT_USE_CRYPTOAPI +# define TORRENT_CRYPTO_NAMESPACE inline namespace capi { +# define TORRENT_CRYPTO_NAMESPACE_END } +#elif defined TORRENT_USE_WOLFSSL +# define TORRENT_CRYPTO_NAMESPACE inline namespace wcrypto { +# define TORRENT_CRYPTO_NAMESPACE_END } +#elif defined TORRENT_USE_LIBCRYPTO +# define TORRENT_CRYPTO_NAMESPACE inline namespace lcrypto { +# define TORRENT_CRYPTO_NAMESPACE_END } +#else +# define TORRENT_CRYPTO_NAMESPACE inline namespace builtin { +# define TORRENT_CRYPTO_NAMESPACE_END } +#endif + +// backwards compatibility with older versions of boost +#if !defined BOOST_SYMBOL_EXPORT && !defined BOOST_SYMBOL_IMPORT +# if defined _MSC_VER || defined __MINGW32__ +# define BOOST_SYMBOL_EXPORT __declspec(dllexport) +# define BOOST_SYMBOL_IMPORT __declspec(dllimport) +# elif __GNUC__ >= 4 +# define BOOST_SYMBOL_EXPORT __attribute__((visibility("default"))) +# define BOOST_SYMBOL_IMPORT __attribute__((visibility("default"))) +# else +# define BOOST_SYMBOL_EXPORT +# define BOOST_SYMBOL_IMPORT +# endif +#endif + +// clang on windows complain if you mark a symbol with visibility hidden that's +// already being exported with dllexport. It seems dllexport doesn't support +// omitting some members of an exported class +#if !defined TORRENT_EXPORT_EXTRA \ + && ((defined __GNUC__ && __GNUC__ >= 4) || defined __clang__) && !defined _WIN32 +# define TORRENT_UNEXPORT __attribute__((visibility("hidden"))) +#else +# define TORRENT_UNEXPORT +#endif + +#if defined TORRENT_BUILDING_SHARED +# define TORRENT_EXPORT BOOST_SYMBOL_EXPORT +#elif defined TORRENT_LINKING_SHARED +# define TORRENT_EXPORT BOOST_SYMBOL_IMPORT +#endif + +// when this is specified, export a bunch of extra +// symbols, mostly for the unit tests to reach +#if defined TORRENT_EXPORT_EXTRA +# if defined TORRENT_BUILDING_SHARED +# define TORRENT_EXTRA_EXPORT BOOST_SYMBOL_EXPORT +# elif defined TORRENT_LINKING_SHARED +# define TORRENT_EXTRA_EXPORT BOOST_SYMBOL_IMPORT +# endif +#endif + +#ifndef TORRENT_EXPORT +# define TORRENT_EXPORT +#endif + +#ifndef TORRENT_EXTRA_EXPORT +# define TORRENT_EXTRA_EXPORT +#endif + +// only export this type if deprecated functions are enabled +// mingw doesn't like combining C++11 attributes with __attribute__ apparently +#if defined __MINGW64__ || defined __MINGW32__ + +# if TORRENT_ABI_VERSION >= 2 +# define TORRENT_DEPRECATED_EXPORT TORRENT_EXTRA_EXPORT +# else +# define TORRENT_DEPRECATED_EXPORT TORRENT_EXPORT +# endif + +#else + +# if TORRENT_ABI_VERSION >= 2 +# define TORRENT_DEPRECATED_EXPORT TORRENT_DEPRECATED TORRENT_EXTRA_EXPORT +# else +# define TORRENT_DEPRECATED_EXPORT TORRENT_DEPRECATED TORRENT_EXPORT +# endif + +#endif + +#endif + diff --git a/include/libtorrent/aux_/ffs.hpp b/include/libtorrent/aux_/ffs.hpp new file mode 100644 index 0000000..24e2951 --- /dev/null +++ b/include/libtorrent/aux_/ffs.hpp @@ -0,0 +1,71 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2017-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FFS_HPP_INCLUDE +#define TORRENT_FFS_HPP_INCLUDE + +#include +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/span.hpp" + +namespace libtorrent { +namespace aux { + + // For a general reference of the problems these routines are about + // see http://en.wikipedia.org/wiki/Find_first_set + + // these functions expect the range to be in big-endian byte order + TORRENT_EXTRA_EXPORT int count_leading_zeros_sw(span buf); + // if this function is called in an unsupported platform, returns -1 + // consider call always count_leading_zeros(buf) + TORRENT_EXTRA_EXPORT int count_leading_zeros_hw(span buf); + + // this function statically determines if hardware or software is used + // and expect the range to be in big-endian byte order + TORRENT_EXTRA_EXPORT int count_leading_zeros(span buf); + + // these functions expect the range to be in big-endian byte order + TORRENT_EXTRA_EXPORT int count_trailing_ones_sw(span buf); + // if this function is called in an unsupported platform, returns -1 + // consider call always count_trailing_ones(buf) + TORRENT_EXTRA_EXPORT int count_trailing_ones_hw(span buf); + + // this function statically determines if hardware or software is used + // and expect the range to be in big-endian byte order + TORRENT_EXTRA_EXPORT int count_trailing_ones(span buf); + + // returns the index of the most significant set bit. + TORRENT_EXTRA_EXPORT int log2p1(std::uint32_t v); +}} + +#endif // TORRENT_FFS_HPP_INCLUDE diff --git a/include/libtorrent/aux_/file_descriptor.hpp b/include/libtorrent/aux_/file_descriptor.hpp new file mode 100644 index 0000000..9dee474 --- /dev/null +++ b/include/libtorrent/aux_/file_descriptor.hpp @@ -0,0 +1,59 @@ +/* +Copyright (c) 2022, Arvid Norberg +All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef TORRENT_FILE_DESCRIPTOR_HPP_INCLUDED +#define TORRENT_FILE_DESCRIPTOR_HPP_INCLUDED + +#include + +namespace libtorrent { +namespace aux { + +struct file_descriptor +{ + file_descriptor(int fd) : m_fd(fd) {} + + ~file_descriptor() + { + if (m_fd >= 0) ::close(m_fd); + } + + file_descriptor(file_descriptor const&) = delete; + file_descriptor(file_descriptor&& rhs) + : m_fd(rhs.m_fd) + { + rhs.m_fd = -1; + } + int fd() const { return m_fd; } +private: + int m_fd; +}; + +} +} + +#endif diff --git a/include/libtorrent/aux_/file_pointer.hpp b/include/libtorrent/aux_/file_pointer.hpp new file mode 100644 index 0000000..b7c181c --- /dev/null +++ b/include/libtorrent/aux_/file_pointer.hpp @@ -0,0 +1,78 @@ +/* + +Copyright (c) 2020-2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FILE_POINTER_HPP +#define TORRENT_FILE_POINTER_HPP + +#include +#include // for swap +#include + +#include "libtorrent/config.hpp" + +namespace libtorrent { +namespace aux { + +struct file_pointer +{ + file_pointer() : ptr(nullptr) {} + explicit file_pointer(FILE* p) : ptr(p) {} + ~file_pointer() { if (ptr != nullptr) ::fclose(ptr); } + file_pointer(file_pointer const&) = delete; + file_pointer(file_pointer&& f) : ptr(f.ptr) { f.ptr = nullptr; } + file_pointer& operator=(file_pointer const&) = delete; + file_pointer& operator=(file_pointer&& f) + { + std::swap(ptr, f.ptr); + return *this; + } + FILE* file() const { return ptr; } +private: + FILE* ptr; +}; + +inline int portable_fseeko(FILE* const f, std::int64_t const offset, int const whence) +{ +#ifdef TORRENT_WINDOWS + return ::_fseeki64(f, offset, whence); +#elif TORRENT_HAS_FSEEKO + return ::fseeko(f, offset, whence); +#else + int const fd = ::fileno(f); + return ::lseek64(fd, offset, whence) == -1 ? -1 : 0; +#endif +} + +} +} + +#endif diff --git a/include/libtorrent/aux_/file_progress.hpp b/include/libtorrent/aux_/file_progress.hpp new file mode 100644 index 0000000..8e998da --- /dev/null +++ b/include/libtorrent/aux_/file_progress.hpp @@ -0,0 +1,109 @@ +/* + +Copyright (c) 2015-2021, Arvid Norberg +Copyright (c) 2019, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FILE_PROGRESS_HPP_INCLUDE +#define TORRENT_FILE_PROGRESS_HPP_INCLUDE + +#include +#include +#include + +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/vector.hpp" + +#if TORRENT_USE_INVARIANT_CHECKS +#include "libtorrent/bitfield.hpp" +#include "libtorrent/aux_/invariant_check.hpp" +#endif + +#if TORRENT_USE_INVARIANT_CHECKS +#include "libtorrent/aux_/invariant_check.hpp" +#include "libtorrent/bitfield.hpp" +#endif + +namespace libtorrent { + +struct piece_picker; +class file_storage; + +namespace aux { + + struct TORRENT_EXTRA_EXPORT file_progress + { + file_progress() = default; + + void init(piece_picker const& picker + , file_storage const& fs); + + void export_progress(vector &fp); + + std::int64_t total_on_disk() const + { + return m_total_on_disk; + } + + bool empty() const { return m_file_progress.empty(); } + void clear(); + + void update(file_storage const& fs, piece_index_t index + , std::function const& completed_cb); + + private: + + // the total number of bytes downloaded to non-pad files + std::int64_t m_total_on_disk = 0; + + // this vector contains the number of bytes completely + // downloaded (as in passed-hash-check) in each file. + // this lets us trigger on individual files completing + // the vector is allocated lazily, when file progress + // is first queried by the client + vector m_file_progress; + +#if TORRENT_USE_INVARIANT_CHECKS + friend struct libtorrent::invariant_access; + void check_invariant() const; + + // this is used to assert we never add the same piece twice + typed_bitfield m_have_pieces; + + // to make sure we never say we've downloaded more bytes of a file than + // its file size + vector m_file_sizes; + vector m_pad_file; +#endif + }; +} } + +#endif diff --git a/include/libtorrent/aux_/file_view_pool.hpp b/include/libtorrent/aux_/file_view_pool.hpp new file mode 100644 index 0000000..bae53f2 --- /dev/null +++ b/include/libtorrent/aux_/file_view_pool.hpp @@ -0,0 +1,248 @@ +/* + +Copyright (c) 2006, 2009, 2013-2021, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2019, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FILE_VIEW_POOL_HPP +#define TORRENT_FILE_VIEW_POOL_HPP + +#include "libtorrent/config.hpp" + +#if TORRENT_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE + +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/mmap.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#define BOOST_BIND_NO_PLACEHOLDERS + +#include +#include +#include +#include + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + +class file_storage; +struct open_file_state; + +namespace aux { + + namespace mi = boost::multi_index; + + TORRENT_EXTRA_EXPORT file_open_mode_t to_file_open_mode(open_mode_t, bool const mmapped); + + // this is an internal cache of open file mappings. + struct TORRENT_EXTRA_EXPORT file_view_pool + { + // ``size`` specifies the number of allowed files handles + // to hold open at any given time. + explicit file_view_pool(int size = 40); + ~file_view_pool(); + + file_view_pool(file_view_pool const&) = delete; + file_view_pool& operator=(file_view_pool const&) = delete; + + // return an open file handle to file at ``file_index`` in the + // file_storage ``fs`` opened at save path ``p``. ``m`` is the + // file open mode (see file::open_mode_t). + std::shared_ptr + open_file(storage_index_t st, std::string const& p + , file_index_t file_index, file_storage const& fs, open_mode_t m +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + , std::shared_ptr open_unmap_lock +#endif + ); + + // release all file views belonging to the specified storage_interface + // (``st``) the overload that takes ``file_index`` releases only the file + // with that index in storage ``st``. + void release(); + void release(storage_index_t st); + void release(storage_index_t st, file_index_t file_index); + + // update the allowed number of open file handles to ``size``. + void resize(int size); + + // returns the current limit of number of allowed open file views held + // by the file_view_pool. + int size_limit() const { return m_size; } + + std::vector get_status(storage_index_t st) const; + + void close_oldest(); + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + void flush_next_file(); + void record_file_write(storage_index_t st, file_index_t file_index + , uint64_t pages); +#endif + + private: + + std::shared_ptr remove_oldest(std::unique_lock&); + + int m_size; + + using file_id = std::pair; + + struct file_entry + { + file_entry(file_id k + , string_view name + , open_mode_t const m + , std::int64_t const size +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + , std::shared_ptr open_unmap_lock +#endif + ) + : key(k) + , mapping(std::make_shared(file_handle(name, size, m), m, size +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + , open_unmap_lock +#endif + )) + , mode(m) + {} + + file_id key; + std::shared_ptr mapping; + time_point last_use{aux::time_now()}; +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + std::uint64_t dirty_bytes; +#endif + open_mode_t mode{}; + }; + + using files_container = mi::multi_index_container< + file_entry, + mi::indexed_by< + // look up files by (torrent, file) key + mi::ordered_unique>, + // look up files by least recently used. New items are added to the + // back, and old items are removed from the front. + mi::sequenced<> +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + // look up files with dirty pages + , mi::ordered_non_unique> +#endif + > + >; + + struct wait_open_entry + { + boost::intrusive::list_member_hook<> list_hook; + + std::condition_variable cond; + + // the open file is passed back to the waiting threads, just in case + // the pool size is so small that it's otherwise evicted between + // being notified and waking up to look for it. + std::shared_ptr mapping; + + // if opening the file fails, waiters are also notified but there + // won't be a mapping. Then this error code is set. + lt::storage_error error = {}; + }; + + struct opening_file_entry + { + boost::intrusive::list_member_hook<> list_hook; + + file_id file_key; + + // the open mode for the file the thread is opening. A thread + // needing a file opened in read-write mode should not wait for a + // thread opening the file in read mode + open_mode_t mode{}; + + boost::intrusive::list + , &wait_open_entry::list_hook> + > waiters; + }; + + void notify_file_open(opening_file_entry& ofe, std::shared_ptr, lt::storage_error const&); + + file_entry open_file_impl(std::string const& p + , file_index_t file_index, file_storage const& fs + , open_mode_t m, file_id file_key +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + , std::shared_ptr open_unmap_lock +#endif + ); + + // In order to avoid multiple threads opening the same file in parallel, + // just to race to add it to the pool. This list, also protected by + // m_mutex, contains files that one thread is currently opening. If + // another thread also need this file, it can add itself to the waiters + // list. The condition variable will then be notified when the file has + // been opened. + boost::intrusive::list + , &opening_file_entry::list_hook> + > m_opening_files; + + // maps storage pointer, file index pairs to the lru entry for the file + files_container m_files; + mutable std::mutex m_mutex; + + // the boost.multi-index container is not no-throw move constructable. In + // order to destruct m_files without holding the mutex, we need this + // separate pre-allocated container to move it into before releasing the + // mutex and clearing it. + files_container m_deferred_destruction; + mutable std::mutex m_destruction_mutex; + }; + +} +} + +#endif // HAVE_MMAP || HAVE_MAP_VIEW_OF_FILE + +#endif diff --git a/include/libtorrent/aux_/generate_peer_id.hpp b/include/libtorrent/aux_/generate_peer_id.hpp new file mode 100644 index 0000000..56a6151 --- /dev/null +++ b/include/libtorrent/aux_/generate_peer_id.hpp @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2018, 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_GENERATE_PEER_ID_HPP_INCLUDED +#define TORRENT_GENERATE_PEER_ID_HPP_INCLUDED + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/aux_/export.hpp" + +namespace libtorrent { namespace aux { + +struct session_settings; + +TORRENT_EXTRA_EXPORT peer_id generate_peer_id(session_settings const& sett); + +}} + +#endif + diff --git a/include/libtorrent/aux_/has_block.hpp b/include/libtorrent/aux_/has_block.hpp new file mode 100644 index 0000000..4b88101 --- /dev/null +++ b/include/libtorrent/aux_/has_block.hpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2018, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HAS_BLOCK_HPP_INCLUDED +#define TORRENT_HAS_BLOCK_HPP_INCLUDED + +#include "libtorrent/piece_block.hpp" +#include "libtorrent/peer_connection.hpp" // for pending_block + +namespace libtorrent { namespace aux { + + struct has_block + { + has_block(has_block const&) = default; + // explicitly disallow assignment, to silence msvc warning + has_block& operator=(has_block const&) = delete; + + explicit has_block(piece_block const& b): block(b) {} + bool operator()(pending_block const& pb) const + { return pb.block == block; } + private: + piece_block const& block; + }; + +}} + +#endif diff --git a/include/libtorrent/aux_/hasher512.hpp b/include/libtorrent/aux_/hasher512.hpp new file mode 100644 index 0000000..5d5a53d --- /dev/null +++ b/include/libtorrent/aux_/hasher512.hpp @@ -0,0 +1,134 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2016-2017, 2019, Andrei Kurushin +Copyright (c) 2017-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HASHER512_HPP_INCLUDED +#define TORRENT_HASHER512_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/span.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#ifdef TORRENT_USE_LIBGCRYPT +#include + +#elif TORRENT_USE_COMMONCRYPTO +#include + +#elif TORRENT_USE_CNG +#include "libtorrent/aux_/win_cng.hpp" + +#elif TORRENT_USE_CRYPTOAPI_SHA_512 +#include "libtorrent/aux_/win_crypto_provider.hpp" + +#elif defined TORRENT_USE_LIBCRYPTO + + extern "C" { + #include + } + +#else +#include "libtorrent/aux_/sha512.hpp" +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { +namespace aux { + + using sha512_hash = digest32<512>; + + // internal + struct TORRENT_EXTRA_EXPORT hasher512 + { + // this is a SHA-512 hash class. + // + // You use it by first instantiating it, then call ``update()`` to feed it + // with data. i.e. you don't have to keep the entire buffer of which you want to + // create the hash in memory. You can feed the hasher parts of it at a time. When + // You have fed the hasher with all the data, you call ``final()`` and it + // will return the sha1-hash of the data. + // + // The constructor that takes a ``char const*`` and an integer will construct the + // sha1 context and feed it the data passed in. + // + // If you want to reuse the hasher object once you have created a hash, you have to + // call ``reset()`` to reinitialize it. + // + // The built-in software version of the sha512-algorithm is from LibTomCrypt + hasher512(); + + // this is the same as default constructing followed by a call to + // ``update(data)``. + explicit hasher512(span data); + hasher512(hasher512 const&); + hasher512& operator=(hasher512 const&) &; + + // append the following bytes to what is being hashed + hasher512& update(span data); + + // store the SHA-512 digest of the buffers previously passed to + // update() and the hasher constructor. + sha512_hash final(); + + // restore the hasher state to be as if the hasher has just been + // default constructed. + void reset(); + + // hidden + ~hasher512(); + + private: + +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_hd_t m_context; +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA512_CTX m_context; +#elif TORRENT_USE_CNG + aux::cng_hash m_context; +#elif TORRENT_USE_CRYPTOAPI_SHA_512 + aux::crypt_hash m_context; +#elif defined TORRENT_USE_LIBCRYPTO + SHA512_CTX m_context; +#else + sha512_ctx m_context; +#endif + }; + +} +} + +#endif // TORRENT_HASHER512_HPP_INCLUDED diff --git a/include/libtorrent/aux_/heterogeneous_queue.hpp b/include/libtorrent/aux_/heterogeneous_queue.hpp new file mode 100644 index 0000000..760c73d --- /dev/null +++ b/include/libtorrent/aux_/heterogeneous_queue.hpp @@ -0,0 +1,301 @@ +/* + +Copyright (c) 2015-2017, 2019-2020, Arvid Norberg +Copyright (c) 2018, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HETEROGENEOUS_QUEUE_HPP_INCLUDED +#define TORRENT_HETEROGENEOUS_QUEUE_HPP_INCLUDED + +#include +#include +#include // for malloc +#include +#include +#include + +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/throw.hpp" + +#ifdef TORRENT_ADDRESS_SANITIZER +#include +#endif + +namespace libtorrent { +namespace aux { + + struct free_deleter + { void operator()(char* ptr) { return std::free(ptr); } }; + + inline std::size_t calculate_pad_bytes(char const* inptr, std::size_t alignment) + { + std::uintptr_t const ptr = reinterpret_cast(inptr); + std::uintptr_t const offset = ptr & (alignment - 1); + return (alignment - offset) & (alignment - 1); + } +} // namespace aux + + template + struct heterogeneous_queue + { + heterogeneous_queue() : m_storage(nullptr, aux::free_deleter()) {} + heterogeneous_queue(heterogeneous_queue const&) = delete; + heterogeneous_queue& operator=(heterogeneous_queue const&) = delete; + + template + typename std::enable_if::value, U&>::type + emplace_back(Args&&... args) + { + // make the conservative assumption that we'll need the maximum padding + // for this object, just for purposes of growing the storage + if (std::size_t(m_size) + sizeof(header_t) + alignof(U) + sizeof(U) > std::size_t(m_capacity)) + grow_capacity(sizeof(header_t) + alignof(U) + sizeof(U)); + + char* ptr = m_storage.get() + m_size; + + std::size_t const pad_bytes = aux::calculate_pad_bytes(ptr + sizeof(header_t), alignof(U)); + + // pad_bytes is only 8 bits in the header, so types that need more than + // 256 byte alignment may not be supported + static_assert(alignof(U) <= 256 + , "heterogeneous_queue does not support types with alignment requirements > 256"); + +#ifdef TORRENT_ADDRESS_SANITIZER + std::size_t const hdr_len = sizeof(U) + + aux::calculate_pad_bytes(ptr + sizeof(header_t) + pad_bytes + sizeof(U) + , alignof(header_t)); + + int const new_size = m_size + int(sizeof(header_t) + pad_bytes + hdr_len); + + __sanitizer_annotate_contiguous_container( + m_storage.get() + , m_storage.get() + m_capacity + , ptr + , m_storage.get() + new_size); +#endif + + // if this assert triggers, the type being added to the queue has + // alignment requirements stricter than what malloc() returns. This is + // not supported + TORRENT_ASSERT((reinterpret_cast(m_storage.get()) + & (alignof(U) - 1)) == 0); + + // make sure the current position in the storage is aligned for + // creating a heder_t object + TORRENT_ASSERT((reinterpret_cast(ptr) + & (alignof(header_t) - 1)) == 0); + + // length prefix + header_t* hdr = new (ptr) header_t; + hdr->pad_bytes = static_cast(pad_bytes); + hdr->move = &move; + ptr += sizeof(header_t) + pad_bytes; + hdr->len = static_cast(sizeof(U) + + aux::calculate_pad_bytes(ptr + sizeof(U), alignof(header_t))); + + // make sure ptr is correctly aligned for the object we're about to + // create there + TORRENT_ASSERT((reinterpret_cast(ptr) + & (alignof(U) - 1)) == 0); + + // construct in-place + U* const ret = new (ptr) U(std::forward(args)...); + + // if we constructed the object without throwing any exception + // update counters to indicate the new item is in there + ++m_num_items; + m_size += int(sizeof(header_t) + pad_bytes + hdr->len); +#ifdef TORRENT_ADDRESS_SANITIZER + TORRENT_ASSERT(m_size == new_size); +#endif + return *ret; + } + + void get_pointers(std::vector& out) + { + out.clear(); + + char* ptr = m_storage.get(); + char const* const end = m_storage.get() + m_size; + while (ptr < end) + { + header_t* hdr = reinterpret_cast(ptr); + ptr += sizeof(header_t) + hdr->pad_bytes; + TORRENT_ASSERT(ptr + hdr->len <= end); + out.push_back(reinterpret_cast(ptr)); + ptr += hdr->len; + } + } + + void swap(heterogeneous_queue& rhs) + { + std::swap(m_storage, rhs.m_storage); + std::swap(m_capacity, rhs.m_capacity); + std::swap(m_size, rhs.m_size); + std::swap(m_num_items, rhs.m_num_items); + } + + int size() const { return m_num_items; } + bool empty() const { return m_num_items == 0; } + + void clear() + { + char* ptr = m_storage.get(); + char const* const end = m_storage.get() + m_size; + while (ptr < end) + { + header_t* hdr = reinterpret_cast(ptr); + ptr += sizeof(header_t) + hdr->pad_bytes; + TORRENT_ASSERT(ptr + hdr->len <= end); + T* a = reinterpret_cast(ptr); + a->~T(); + ptr += hdr->len; + hdr->~header_t(); + } + +#ifdef TORRENT_ADDRESS_SANITIZER + if (m_size > 0) + { + __sanitizer_annotate_contiguous_container( + m_storage.get() + , m_storage.get() + m_capacity + , m_storage.get() + m_size + , m_storage.get()); + } +#endif + m_size = 0; + m_num_items = 0; + } + + T* front() + { + if (m_size == 0) return nullptr; + + TORRENT_ASSERT(m_size > 1); + char* ptr = m_storage.get(); + header_t* hdr = reinterpret_cast(ptr); + TORRENT_ASSERT(sizeof(header_t) + hdr->pad_bytes + hdr->len + <= std::size_t(m_size)); + ptr += sizeof(header_t) + hdr->pad_bytes; + return reinterpret_cast(ptr); + } + + ~heterogeneous_queue() { clear(); } + + private: + + // this header is put in front of every element. It tells us + // how many bytes it's using for its allocation, and it + // also tells us how to move this type if we need to grow our + // allocation. + struct header_t + { + // the size of the object. From the start of the object, skip this many + // bytes to get to the next header. Meaning this includes sufficient + // padding to have the next entry be appropriately aligned for header_t + std::uint16_t len; + + // the number of pad bytes between the end of this + // header and the start of the object. This supports allocating types with + // stricter alignment requirements + std::uint8_t pad_bytes; + + void (*move)(char* dst, char* src); + }; + + void grow_capacity(int const size) + { + int const amount_to_grow = (std::max)(size + , (std::max)(m_capacity * 3 / 2, 128)); + + // we use malloc() to guarantee alignment + std::unique_ptr new_storage( + static_cast(std::malloc(std::size_t(m_capacity + amount_to_grow))) + , aux::free_deleter()); + + if (!new_storage) + aux::throw_ex(); + + char* src = m_storage.get(); + char* dst = new_storage.get(); + char const* const end = m_storage.get() + m_size; + while (src < end) + { + header_t* src_hdr = reinterpret_cast(src); + new (dst) header_t(*src_hdr); + src += sizeof(header_t) + src_hdr->pad_bytes; + dst += sizeof(header_t) + src_hdr->pad_bytes; + int const len = src_hdr->len; + TORRENT_ASSERT(src + len <= end); + // this is no-throw + src_hdr->move(dst, src); + src_hdr->~header_t(); + src += len; + dst += len; + } + + m_storage.swap(new_storage); + m_capacity += amount_to_grow; + +#ifdef TORRENT_ADDRESS_SANITIZER + __sanitizer_annotate_contiguous_container( + m_storage.get() + , m_storage.get() + m_capacity + , m_storage.get() + , m_storage.get() + m_size); +#endif + } + + template + static void move(char* dst, char* src) noexcept + { + static_assert(std::is_nothrow_move_constructible::value + , "heterogeneous queue only supports noexcept move constructible types"); + static_assert(std::is_nothrow_destructible::value + , "heterogeneous queue only supports noexcept destructible types"); + U& rhs = *reinterpret_cast(src); + + TORRENT_ASSERT((reinterpret_cast(dst) & (alignof(U) - 1)) == 0); + + new (dst) U(std::move(rhs)); + rhs.~U(); + } + + std::unique_ptr m_storage; + // number of bytes of storage allocated + int m_capacity = 0; + // the number of bytes used under m_storage + int m_size = 0; + // the number of objects allocated in m_storage + int m_num_items = 0; + }; +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/aux_/instantiate_connection.hpp b/include/libtorrent/aux_/instantiate_connection.hpp new file mode 100644 index 0000000..d4756c9 --- /dev/null +++ b/include/libtorrent/aux_/instantiate_connection.hpp @@ -0,0 +1,58 @@ +/* + +Copyright (c) 2007, 2010, 2012, 2015, 2017, 2019-2020, Arvid Norberg +Copyright (c) 2018, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_INSTANTIATE_CONNECTION +#define TORRENT_INSTANTIATE_CONNECTION + +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/aux_/proxy_settings.hpp" +#include "libtorrent/aux_/socket_type.hpp" + +namespace libtorrent { + + struct utp_socket_manager; + +namespace aux { + + // instantiate a socket_type (s) according to the specified criteria + TORRENT_EXTRA_EXPORT + aux::socket_type instantiate_connection(io_context& + , aux::proxy_settings const& ps + , void* ssl_context + , utp_socket_manager* sm + , bool peer_connection + , bool tracker_connection); +}} + +#endif diff --git a/include/libtorrent/aux_/invariant_check.hpp b/include/libtorrent/aux_/invariant_check.hpp new file mode 100644 index 0000000..cd854c5 --- /dev/null +++ b/include/libtorrent/aux_/invariant_check.hpp @@ -0,0 +1,86 @@ +// Copyright Daniel Wallin 2004. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef TORRENT_INVARIANT_ACCESS_HPP_INCLUDED +#define TORRENT_INVARIANT_ACCESS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include + +#if TORRENT_USE_INVARIANT_CHECKS + +namespace libtorrent { + + struct invariant_access + { + template + static void check_invariant(T const& self) + { + self.check_invariant(); + } + }; + +namespace aux { + + template + void check_invariant(T const& x) + { +#ifndef BOOST_NO_EXCEPTIONS + try + { + invariant_access::check_invariant(x); + } + catch (std::exception const& err) + { + std::fprintf(stderr, "invariant_check failed with exception: %s\n" + , err.what()); + } + catch (...) + { + std::fprintf(stderr, "invariant_check failed with exception\n"); + } +#else + invariant_access::check_invariant(x); +#endif + } + + struct invariant_checker {}; + + template + struct invariant_checker_impl : invariant_checker + { + explicit invariant_checker_impl(T const& self_) : self(self_) + { check_invariant(self); } + + invariant_checker_impl(invariant_checker_impl&& rhs) + : self(rhs.self), armed(rhs.armed) + { rhs.armed = false; } + + invariant_checker_impl(invariant_checker_impl const& rhs) = delete; + invariant_checker_impl& operator=(invariant_checker_impl const&) = delete; + + ~invariant_checker_impl() { if (armed) check_invariant(self); } + + T const& self; + bool armed = true; + }; + + template + invariant_checker_impl make_invariant_checker(T const& x) + { + return invariant_checker_impl(x); + } +} +} + +#define INVARIANT_CHECK \ + aux::invariant_checker const& _invariant_check = aux::make_invariant_checker(*this); \ + (void)_invariant_check +#else +#define INVARIANT_CHECK do {} TORRENT_WHILE_0 +#endif + +#endif // TORRENT_INVARIANT_ACCESS_HPP_INCLUDED + diff --git a/include/libtorrent/aux_/io.hpp b/include/libtorrent/aux_/io.hpp new file mode 100644 index 0000000..0edcf0a --- /dev/null +++ b/include/libtorrent/aux_/io.hpp @@ -0,0 +1,180 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2016-2018, 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_AUX_IO_HPP_INCLUDED +#define TORRENT_AUX_IO_HPP_INCLUDED + +#include +#include +#include +#include "libtorrent/span.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" + +namespace libtorrent { namespace aux { + + template struct type {}; + + // reads an integer from a byte stream + // in big endian byte order and converts + // it to native endianness + template + inline typename std::enable_if::type + read_impl(span& view, type) + { + T ret = 0; + for (Byte const b : view.first(sizeof(T))) + { + ret <<= 8; + ret |= static_cast(b); + } + view = view.subspan(int(sizeof(T))); + return ret; + } + + template + inline typename std::enable_if::value + && (std::is_integral::value || std::is_enum::value) + && sizeof(Byte)==1>::type + write_impl(In data, span& view) + { + T val = static_cast(data); + TORRENT_ASSERT(data == static_cast(val)); + int shift = int(sizeof(T)) * 8; + for (Byte& b : view.first(sizeof(T))) + { + shift -= 8; + b = static_cast((val >> shift) & 0xff); + } + view = view.subspan(int(sizeof(T))); + } + + // the single-byte case is separate to avoid a warning on the shift-left by + // 8 bits (which invokes undefined behavior) + + template + inline typename std::enable_if::type + read_impl(span& view, type) + { + std::uint8_t ret = static_cast(view[0]); + view = view.subspan(1); + return ret; + } + + template + inline typename std::enable_if::type + read_impl(span& view, type) + { + std::uint8_t ret = static_cast(view[0]); + view = view.subspan(1); + return ret; + } + + // -- adaptors + + template + std::int64_t read_int64(span& view) + { return read_impl(view, type()); } + + template + std::uint64_t read_uint64(span& view) + { return read_impl(view, type()); } + + template + std::uint32_t read_uint32(span& view) + { return read_impl(view, type()); } + + template + std::int32_t read_int32(span& view) + { return read_impl(view, type()); } + + template + std::int16_t read_int16(span& view) + { return read_impl(view, type()); } + + template + std::uint16_t read_uint16(span& view) + { return read_impl(view, type()); } + + template + std::int8_t read_int8(span& view) + { return read_impl(view, type()); } + + template + std::uint8_t read_uint8(span& view) + { return read_impl(view, type()); } + + + template + void write_uint64(T val, span& view) + { write_impl(val, view); } + + template + void write_int64(T val, span& view) + { write_impl(val, view); } + + template + void write_uint32(T val, span& view) + { write_impl(val, view); } + + template + void write_int32(T val, span& view) + { write_impl(val, view); } + + template + void write_uint16(T val, span& view) + { write_impl(val, view); } + + template + void write_int16(T val, span& view) + { write_impl(val, view); } + + template + void write_uint8(T val, span& view) + { write_impl(val, view); } + + template + void write_int8(T val, span& view) + { write_impl(val, view); } + + template + inline int write_string(std::string const& str, span& view) + { + TORRENT_ASSERT(view.size() >= numeric_cast(str.size())); + std::copy(str.begin(), str.end(), view.begin()); + view = view.subspan(int(str.size())); + return int(str.size()); + } + +}} + +#endif // TORRENT_AUX_IO_HPP_INCLUDED diff --git a/include/libtorrent/aux_/ip_helpers.hpp b/include/libtorrent/aux_/ip_helpers.hpp new file mode 100644 index 0000000..82eef95 --- /dev/null +++ b/include/libtorrent/aux_/ip_helpers.hpp @@ -0,0 +1,68 @@ +/* + +Copyright (c) 2015-2017, Steven Siloti +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IP_HELPERS_HPP_INCLUDED +#define TORRENT_IP_HELPERS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/aux_/export.hpp" + +namespace libtorrent { +namespace aux { + + TORRENT_EXTRA_EXPORT bool is_global(address const& a); + TORRENT_EXTRA_EXPORT bool is_local(address const& a); + TORRENT_EXTRA_EXPORT bool is_link_local(address const& addr); + TORRENT_EXTRA_EXPORT bool is_teredo(address const& addr); + TORRENT_EXTRA_EXPORT bool is_ip_address(std::string const& host); + + // internal + template + bool is_v4(Endpoint const& ep) + { + return ep.protocol() == Endpoint::protocol_type::v4(); + } + template + bool is_v6(Endpoint const& ep) + { + return ep.protocol() == Endpoint::protocol_type::v6(); + } + + TORRENT_EXTRA_EXPORT address ensure_v6(address const& a); + +} +} + +#endif diff --git a/include/libtorrent/aux_/ip_notifier.hpp b/include/libtorrent/aux_/ip_notifier.hpp new file mode 100644 index 0000000..dd849ee --- /dev/null +++ b/include/libtorrent/aux_/ip_notifier.hpp @@ -0,0 +1,59 @@ +/* + +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2016, Steven Siloti +Copyright (c) 2017, 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IP_NOTIFIER_HPP_INCLUDED +#define TORRENT_IP_NOTIFIER_HPP_INCLUDED + +#include +#include + +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_context.hpp" + +namespace libtorrent { namespace aux { + + struct TORRENT_EXTRA_EXPORT ip_change_notifier + { + // cb will be invoked when a change is detected in the + // system's IP addresses + virtual void async_wait(std::function cb) = 0; + virtual void cancel() = 0; + + virtual ~ip_change_notifier() {} + }; + + TORRENT_EXTRA_EXPORT std::unique_ptr create_ip_notifier(io_context& ios); +}} + +#endif diff --git a/include/libtorrent/aux_/keepalive.hpp b/include/libtorrent/aux_/keepalive.hpp new file mode 100644 index 0000000..2e5fb30 --- /dev/null +++ b/include/libtorrent/aux_/keepalive.hpp @@ -0,0 +1,102 @@ +/* + +Copyright (c) 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_KEEP_ALIVE_HPP_INCLUDED +#define TORRENT_KEEP_ALIVE_HPP_INCLUDED + +#if !defined _WIN32 + +#include "libtorrent/config.hpp" + +#include // for IPPROTO_TCP + +namespace libtorrent { +namespace aux { + +#if defined TCP_KEEPIDLE +#define TORRENT_HAS_KEEPALIVE_IDLE + struct tcp_keepalive_idle + { + explicit tcp_keepalive_idle(int seconds): m_value(seconds) {} + template + int level(Protocol const&) const { return IPPROTO_TCP; } + template + int name(Protocol const&) const { return TCP_KEEPIDLE; } + template + char const* data(Protocol const&) const { return reinterpret_cast(&m_value); } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + private: + int m_value; + }; +#elif defined TCP_KEEPALIVE +#define TORRENT_HAS_KEEPALIVE_IDLE + struct tcp_keepalive_idle + { + explicit tcp_keepalive_idle(int seconds): m_value(seconds) {} + template + int level(Protocol const&) const { return IPPROTO_TCP; } + template + int name(Protocol const&) const { return TCP_KEEPALIVE; } + template + char const* data(Protocol const&) const { return reinterpret_cast(&m_value); } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + private: + int m_value; + }; +#endif + +#ifdef TCP_KEEPINTVL +#define TORRENT_HAS_KEEPALIVE_INTERVAL + struct tcp_keepalive_interval + { + explicit tcp_keepalive_interval(int seconds): m_value(seconds) {} + template + int level(Protocol const&) const { return IPPROTO_TCP; } + template + int name(Protocol const&) const { return TCP_KEEPINTVL; } + template + char const* data(Protocol const&) const { return reinterpret_cast(&m_value); } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + private: + int m_value; + }; +#endif +} +} + +#endif // _WIN32 + +#endif + diff --git a/include/libtorrent/aux_/listen_socket_handle.hpp b/include/libtorrent/aux_/listen_socket_handle.hpp new file mode 100644 index 0000000..a20a012 --- /dev/null +++ b/include/libtorrent/aux_/listen_socket_handle.hpp @@ -0,0 +1,94 @@ +/* + +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, Alden Torres +Copyright (c) 2020, 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. +* Neither the name of the author nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_LISTEN_SOCKET_HANDLE_HPP_INCLUDED +#define TORRENT_LISTEN_SOCKET_HANDLE_HPP_INCLUDED + +#include "libtorrent/address.hpp" +#include "libtorrent/socket.hpp" // for tcp::endpoint +#include + +namespace libtorrent { namespace aux { + + struct listen_socket_t; + + struct TORRENT_EXTRA_EXPORT listen_socket_handle + { + friend struct session_impl; + + listen_socket_handle() = default; + + listen_socket_handle(std::shared_ptr s) // NOLINT + : m_sock(s) + {} + + listen_socket_handle(listen_socket_handle const& o) = default; + listen_socket_handle& operator=(listen_socket_handle const& o) = default; + listen_socket_handle(listen_socket_handle&& o) = default; + listen_socket_handle& operator=(listen_socket_handle&& o) = default; + + explicit operator bool() const { return !m_sock.expired(); } + + address get_external_address() const; + tcp::endpoint get_local_endpoint() const; + bool can_route(address const&) const; + + std::string device() const; + + bool is_ssl() const; + + bool operator==(listen_socket_handle const& o) const + { + return !m_sock.owner_before(o.m_sock) && !o.m_sock.owner_before(m_sock); + } + + bool operator!=(listen_socket_handle const& o) const + { + return !(*this == o); + } + + bool operator<(listen_socket_handle const& o) const + { return m_sock.owner_before(o.m_sock); } + + listen_socket_t* get() const; + + std::weak_ptr get_ptr() const { return m_sock; } + + private: + std::weak_ptr m_sock; + }; + +} } + +#endif diff --git a/include/libtorrent/aux_/lsd.hpp b/include/libtorrent/aux_/lsd.hpp new file mode 100644 index 0000000..9d7b0cf --- /dev/null +++ b/include/libtorrent/aux_/lsd.hpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_LSD_HPP +#define LIBTORRENT_LSD_HPP + +#include "libtorrent/config.hpp" +#include "libtorrent/socket.hpp" // for tcp::endpoint +#include "libtorrent/sha1_hash.hpp" + +namespace libtorrent { namespace aux { + struct TORRENT_EXTRA_EXPORT lsd_callback + { + virtual void on_lsd_peer(tcp::endpoint const& peer, sha1_hash const& ih) = 0; +#ifndef TORRENT_DISABLE_LOGGING + virtual bool should_log_lsd() const = 0; + virtual void log_lsd(char const* msg) const = 0; +#endif + + protected: + ~lsd_callback() {} + }; +}} + +#endif // LIBTORRENT_LSD_HPP diff --git a/include/libtorrent/aux_/merkle.hpp b/include/libtorrent/aux_/merkle.hpp new file mode 100644 index 0000000..349961d --- /dev/null +++ b/include/libtorrent/aux_/merkle.hpp @@ -0,0 +1,162 @@ +/* + +Copyright (c) 2015, 2017, 2019-2021, Arvid Norberg +Copyright (c) 2017, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_MERKLE_HPP_INCLUDED +#define TORRENT_MERKLE_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/aux_/vector.hpp" +#include +#include // pair + +namespace libtorrent { + + struct bitfield; + + // given layer and offset from the start of the layer, return the nodex + // index + TORRENT_EXTRA_EXPORT int merkle_to_flat_index(int layer, int offset); + + // given layer, returns index to the layer's first node + TORRENT_EXTRA_EXPORT int merkle_layer_start(int layer); + + // given the number of blocks, how many leaves do we need? this rounds up to + // an even power of 2 + TORRENT_EXTRA_EXPORT int merkle_num_leafs(int); + + // returns the number of nodes in the tree, given the number of leaves + TORRENT_EXTRA_EXPORT int merkle_num_nodes(int); + + // given the number of leafs in the tree, returns the index to the first + // leaf + TORRENT_EXTRA_EXPORT int merkle_first_leaf(int num_leafs); + + // takes the number of leaves and returns the height of the merkle tree. + // does not include the root node in the layer count. i.e. if there's only a + // root hash, there are 0 layers. Note that the number of leaves must be + // valid, i.e. a power of 2. + TORRENT_EXTRA_EXPORT int merkle_num_layers(int); + TORRENT_EXTRA_EXPORT int merkle_get_parent(int); + TORRENT_EXTRA_EXPORT int merkle_get_sibling(int); + TORRENT_EXTRA_EXPORT int merkle_get_first_child(int); + TORRENT_EXTRA_EXPORT int merkle_get_first_child(int tree_node, int depth); + + // given a tree and the number of leaves, expect all leaf hashes to be set and + // compute all other hashes starting with the leaves. + TORRENT_EXTRA_EXPORT void merkle_fill_tree(span tree, int num_leafs, int level_start); + TORRENT_EXTRA_EXPORT void merkle_fill_tree(span tree, int num_leafs); + + // fills in nodes that can be computed from a tree with arbitrary nodes set + // all "orphan" hashes, i.e ones that do not contribute towards computing + // the root, will be cleared. + TORRENT_EXTRA_EXPORT void merkle_fill_partial_tree(span tree); + + // given a merkle tree (`tree`), clears all hashes in the range of nodes: + // [ level_start, level_start+ num_leafs), as well as all of their parents, + // within the sub-tree. It does not clear the root of the sub-tree. + // see unit test for examples. + TORRENT_EXTRA_EXPORT void merkle_clear_tree(span tree, int num_leafs, int level_start); + + // given the leaf hashes, computes the merkle root hash. The pad is the hash + // to use for the right-side padding, in case the number of leaves is not a + // power of two. + TORRENT_EXTRA_EXPORT sha256_hash merkle_root(span leaves, sha256_hash const& pad = {}); + + TORRENT_EXTRA_EXPORT + sha256_hash merkle_root_scratch(span leaves, int num_leafs + , sha256_hash pad, std::vector& scratch_space); + + // given a flat index, return which layer the node is in + TORRENT_EXTRA_EXPORT int merkle_get_layer(int idx); + // given a flat index, return the offset in the layer + TORRENT_EXTRA_EXPORT int merkle_get_layer_offset(int idx); + + // given "blocks" number of leafs in the full tree (i.e. at the block level) + // and given "pieces" nodes in the piece layer, compute the pad hash for the + // piece layer + TORRENT_EXTRA_EXPORT sha256_hash merkle_pad(int blocks, int pieces); + + // validates and inserts the uncle hashes (and the specified node) into the + // target tree. "node" is the hash at target_node_idx and uncle_hashes are + // the uncle hashes all the way up to the root of target_tree, to prove "node" + // is valid. The hashes are only inserted into target_tree if they validate. + // returns true if all hashes validated correctly, and false otherwise. + // + // For example, consider the following tree (target_tree): + // + // R + // 2 _ + // _ _ _ 1 + //_ _ _ _ N 0 _ _ + // The root R is expected to be known and set in target_tree. + // if we're inserting the hash N, the uncle hashes provide proof of it being + // valid by containing 0, 1 and two (as marked in the tree above) + // Any non-zero hash encountered in target_tree is assumed to be valid, and + // will terminate the validation early, either successful (if there's a + // match) or unsuccessful (if there's a mismatch). + TORRENT_EXTRA_EXPORT + bool merkle_validate_and_insert_proofs(span target_tree + , int target_node_idx, sha256_hash const& node, span uncle_hashes); + + TORRENT_EXTRA_EXPORT + bool merkle_validate_node(sha256_hash const& left, sha256_hash const& right + , sha256_hash const& parent); + + // validates hashes from src and copies the valid ones to dst given root as + // the expected root of the tree (i.e. index 0) + // src and dst must be the same size. dst is expected to be initialized + // cleared (or only have valid hashes set), this function will not clear + // hashes in dst that are invalid in src. + TORRENT_EXTRA_EXPORT + void merkle_validate_copy(span src, span dst + , sha256_hash const& root, bitfield& verified_leafs); + + TORRENT_EXTRA_EXPORT + bool merkle_validate_single_layer(span tree); + + // given a leaf index (0-based index in the leaf layer) and a tree, return + // the leafs_start, leafs_size and root_index representing a subtree that + // can be validated. The block_index and leaf_size is the range of the leaf + // layer that can be verified, and the root_index is the node that needs to + // be known in (tree) to do so. The num_valid_leafs specifies how many of + // the leafs that are actually *supposed* to be non-zero. Any leafs beyond + // these are padding and expected to be zero. + // The caller must validate the hash at root_index. + TORRENT_EXTRA_EXPORT + std::tuple merkle_find_known_subtree(span const tree + , int block_index, int num_valid_leafs); +} + +#endif diff --git a/include/libtorrent/aux_/merkle_tree.hpp b/include/libtorrent/aux_/merkle_tree.hpp new file mode 100644 index 0000000..3057d2a --- /dev/null +++ b/include/libtorrent/aux_/merkle_tree.hpp @@ -0,0 +1,226 @@ +/* + +Copyright (c) 2020-2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_MERKLE_TREE_HPP_INCLUDED +#define TORRENT_MERKLE_TREE_HPP_INCLUDED + +#include +#include +#include // for pair +#include + +#include "libtorrent/sha1_hash.hpp" // for sha256_hash +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/optional.hpp" +#include "libtorrent/bitfield.hpp" +#if TORRENT_USE_INVARIANT_CHECKS +#include "libtorrent/aux_/invariant_check.hpp" +#endif + +namespace libtorrent { +namespace aux { + +struct add_hashes_result_t +{ + std::vector passed; + std::vector>> failed; +}; + +// represents the merkle tree for files belonging to a torrent. +// Each file has a root-hash and a "piece layer", i.e. the level in the tree +// representing whole pieces. Those hashes are likely to be included in .torrent +// files and known up-front. + +// The invariant of the tree is that all interior nodes (i.e. all but the very +// bottom leaf nodes, representing block hashes) are either set and valid, or +// clear. No invalid hashes are allowed, and they can only be added by also +// providing proof of being valid. + +// The leaf blocks on the other hand, MAY be invalid. For instance, when adding +// a magnet link for a torrent that we already have files for. Once we have the +// metadata, we have files on disk but no hashes. We won't know whether the data +// on disk is valid or not, until we've downloaded the hashes to validate them. + +// Idea for future space optimization: +// while downloading, we need to store interior nodes of this tree. However, we +// don't need to store the padding. a SHA-256 is 32 bytes. Instead of storing +// the full (padded) tree of SHA-256 hashes, store the full tree of 32 bit +// signed integers, being indices into the actual storage for the tree. We could +// even grow the storage lazily. Instead of storing the padding hashes, use +// negative indices to refer to fixed SHA-256(0), and SHA-256(SHA-256(0)) and so +// on +struct TORRENT_EXTRA_EXPORT merkle_tree +{ + // TODO: remove this constructor. Don't support "uninitialized" trees. This + // also requires not constructing these for pad-files and small files as + // well. So, a sparse hash list in torrent_info + merkle_tree() = default; + merkle_tree(int num_blocks, int blocks_per_piece, char const* r); + + sha256_hash root() const; + + void load_tree(span t, std::vector const& verified); + void load_sparse_tree(span t, std::vector const& mask + , std::vector const& verified); + void load_verified_bits(std::vector const& verified); + + std::size_t size() const; + int end_index() const { return int(size()); } + + bool has_node(int idx) const; + + bool compare_node(int idx, sha256_hash const& h) const; + + sha256_hash operator[](int idx) const; + + std::vector build_vector() const; + std::pair, aux::vector> build_sparse_vector() const; + + // get bits indicating if each leaf hash is verified + std::vector verified_leafs() const; + + // returns true if the entire tree is known and verified + bool is_complete() const; + + // returns true if all block hashes in the specified range have been verified + bool blocks_verified(int block_idx, int num_blocks) const; + + bool load_piece_layer(span piece_layer); + + // the leafs in "tree" must be block hashes (i.e. leaf hashes in the this + // tree). This function inserts those hashes as well as the nodes up the + // tree. The destination start index is the index, in this tree, to the first leaf + // where "tree" will be inserted. + // inserts the nodes in "proofs" as a path up the tree. The proofs are + // sibling hashes, as they are returned from add_hashes(). The hashes must + // be valid. + // if the hashes are not valid, or the uncle hashes fail validation, nullopt + // is returned. + boost::optional add_hashes( + int dest_start_idx + , piece_index_t::diff_type file_piece_offset + , span hashes + , span uncle_hashes); + + aux::vector get_piece_layer() const; + + enum class set_block_result + { + ok, unknown, hash_failed, block_hash_failed + }; + + std::tuple set_block(int block_index + , sha256_hash const& h); + + std::vector get_hashes(int base + , int index, int count, int proof_layers) const; + +private: + + // set to an empty tree + void clear(); + + sha256_hash get_impl(int idx, std::vector& scratch_space) const; + + int blocks_per_piece() const { return 1 << m_blocks_per_piece_log; } + // the number tree levels per piece. This is 0 if the block layer is also + // the piece layer. + int piece_levels() const { return m_blocks_per_piece_log; } + + int block_layer_start() const; + int piece_layer_start() const; + int num_pieces() const; + int num_leafs() const; + + void optimize_storage(); + void optimize_storage_piece_layer(); + void allocate_full(); + + // a pointer to the root hash for this file. + char const* m_root = nullptr; + + // this is either the full tree, or some sparse representation of it, + // depending on m_mode + // TODO: make this a std::unique_ptr + aux::vector m_tree; + + // when the full tree is allocated, this has one bit for each block hash. a + // 1 means we have verified the block hash to be correct, otherwise the block + // hash may represent what's on disk, but we haven't been able to verify it + // yet + bitfield m_block_verified; + + // number of blocks in the file this tree represents. The number of leafs in + // the tree is rounded up to an even power of 2. + int m_num_blocks = 0; + + // the number of blocks per piece, specified as how many steps to shift + // right 1 to get the number of blocks in one piece. This is a compact + // representation that's valid because pieces are always powers of 2. + // this is necessary to know which layer in the tree the piece layer is. + std::uint8_t m_blocks_per_piece_log = 0; + + enum class mode_t : std::uint8_t + { + // a default constructed tree is truly empty. It does not even have a + // root hash + uninitialized_tree, + + // we don't have any hashes in this tree. m_tree should be empty + // an empty tree still always have the root hash (available as root()) + empty_tree, + + // in this mode, m_tree represents the full tree, including padding. + full_tree, + + // in this mode, m_tree represents the piece layer only, no padding + // and all piece layer hashes are stored and valid + piece_layer, + + // in this mode, m_tree represents the block (leaf) layer only, no padding + // and all block layer hashes are stored and valid + block_layer + }; + mode_t m_mode = mode_t::uninitialized_tree; + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; + friend struct libtorrent::invariant_access; +#endif +}; + +} +} + +#endif diff --git a/include/libtorrent/aux_/mmap.hpp b/include/libtorrent/aux_/mmap.hpp new file mode 100644 index 0000000..470ea49 --- /dev/null +++ b/include/libtorrent/aux_/mmap.hpp @@ -0,0 +1,143 @@ +/* + +Copyright (c) 2016, 2018-2022, Arvid Norberg +Copyright (c) 2019, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_MMAP_HPP +#define TORRENT_MMAP_HPP + +#include "libtorrent/config.hpp" + +#if TORRENT_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE + +#include "libtorrent/disk_interface.hpp" // for open_file_state +#include "libtorrent/aux_/open_mode.hpp" +#include "libtorrent/file.hpp" // for file_handle + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + +#include "libtorrent/aux_/windows.hpp" +#include + +#endif // TORRENT_HAVE_MAP_VIEW_OF_FILE + +namespace libtorrent { + +// for now +using byte = char; + +namespace aux { + + // files smaller than this will not be mapped into memory, they will just + // have a file descriptor to be used with regular pread/pwrite calls + std::int64_t const mapped_file_cutoff = 1024 * 1024; + + using namespace flags; + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + struct TORRENT_EXTRA_EXPORT file_mapping_handle + { + file_mapping_handle(file_handle file, open_mode_t mode, std::int64_t size); + ~file_mapping_handle(); + file_mapping_handle(file_mapping_handle const&) = delete; + file_mapping_handle& operator=(file_mapping_handle const&) = delete; + + file_mapping_handle(file_mapping_handle&& fm); + file_mapping_handle& operator=(file_mapping_handle&& fm) &; + + HANDLE handle() const { return m_mapping; } + handle_type fd() const { return m_file.fd(); } + private: + void close(); + file_handle m_file; + HANDLE m_mapping; + }; +#endif + + struct TORRENT_EXTRA_EXPORT file_mapping : std::enable_shared_from_this + { + file_mapping(file_handle file, open_mode_t mode, std::int64_t file_size +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + , std::shared_ptr open_unmap_lock +#endif + ); + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + void flush(); +#endif + + // non-copyable + file_mapping(file_mapping const&) = delete; + file_mapping& operator=(file_mapping const&) = delete; + + file_mapping(file_mapping&& rhs); + file_mapping& operator=(file_mapping&& rhs) &; + ~file_mapping(); + + handle_type fd() const { return m_file.fd(); } + + bool has_memory_map() const { return m_mapping != nullptr; } + + // the memory range this file has been mapped into + span range() + { + TORRENT_ASSERT(m_mapping); + return { static_cast(m_mapping), static_cast(m_size) }; + } + + // hint the kernel that we probably won't need this part of the file + // anytime soon + void dont_need(span range); + + // hint the kernel that the given (dirty) range of pages should be + // flushed to disk + void page_out(span range); + + private: + + void close(); + + std::int64_t m_size; +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + file_mapping_handle m_file; + std::shared_ptr m_open_unmap_lock; +#else + file_handle m_file; +#endif + void* m_mapping; + }; +} // aux +} // libtorrent + +#endif // HAVE_MMAP || HAVE_MAP_VIEW_OF_FILE + +#endif + diff --git a/include/libtorrent/aux_/mmap_disk_job.hpp b/include/libtorrent/aux_/mmap_disk_job.hpp new file mode 100644 index 0000000..13e6bd8 --- /dev/null +++ b/include/libtorrent/aux_/mmap_disk_job.hpp @@ -0,0 +1,233 @@ +/* + +Copyright (c) 2014-2020, 2022, Arvid Norberg +Copyright (c) 2016-2018, 2020, Alden Torres +Copyright (c) 2017-2018, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_IO_JOB_HPP +#define TORRENT_DISK_IO_JOB_HPP + +#include "libtorrent/fwd.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/tailqueue.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/session_types.hpp" +#include "libtorrent/flags.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include +#include +#include + +namespace libtorrent { + + struct mmap_storage; + +namespace aux { + + // internal + enum class job_action_t : std::uint8_t + { + read + , write + , hash + , hash2 + , move_storage + , release_files + , delete_files + , check_fastresume + , rename_file + , stop_torrent + , file_priority + , clear_piece + , partial_read + , num_job_ids + }; + + // disk_io_jobs are allocated in a pool allocator in disk_io_thread + // they are always allocated from the network thread, posted + // (as pointers) to the disk I/O thread, and then passed back + // to the network thread for completion handling and to be freed. + // each mmap_disk_job can belong to one tailqueue. The job queue + // in the disk thread, is one, the jobs waiting on completion + // on a cache piece (in block_cache) is another, and a job + // waiting for a storage fence to be lowered is another. Jobs + // are never in more than one queue at a time. Only passing around + // pointers and chaining them back and forth into lists saves + // a lot of heap allocation churn of using general purpose + // containers. + struct TORRENT_EXTRA_EXPORT mmap_disk_job : tailqueue_node + { + mmap_disk_job(); + mmap_disk_job(mmap_disk_job const&) = delete; + mmap_disk_job& operator=(mmap_disk_job const&) = delete; + + void call_callback(); + + // this is set by the storage object when a fence is raised + // for this job. It means that this no other jobs on the same + // storage will execute in parallel with this one. It's used + // to lower the fence when the job has completed + static constexpr disk_job_flags_t fence = 1_bit; + + // this job is currently being performed, or it's hanging + // on a cache piece that may be flushed soon + static constexpr disk_job_flags_t in_progress = 2_bit; + + // this is set for jobs that we're no longer interested in. Any aborted + // job that's executed should immediately fail with operation_aborted + // instead of executing + static constexpr disk_job_flags_t aborted = 6_bit; + + // for read and write, this is the disk_buffer_holder + // for other jobs, it may point to other job-specific types + // for move_storage and rename_file this is a string + boost::variant + , remove_flags_t + > argument; + + // the disk storage this job applies to (if applicable) + std::shared_ptr storage; + + // this is called when operation completes + + using read_handler = std::function; + using write_handler = std::function; + using hash_handler = std::function; + using hash2_handler = std::function; + using move_handler = std::function; + using release_handler = std::function; + using check_handler = std::function; + using rename_handler = std::function; + using clear_piece_handler = std::function; + using set_file_prio_handler = std::function)>; + + boost::variant callback; + + // the error code from the file operation + // on error, this also contains the path of the + // file the disk operation failed on + storage_error error; + + union un + { + un() {} + // result for hash jobs + struct hash_args + { + sha1_hash piece_hash; + span block_hashes; + } h; + sha256_hash piece_hash2; + + // this is used for check_fastresume to pass in a vector of hard-links + // to create. Each element corresponds to a file in the file_storage. + // The string is the absolute path of the identical file to create + // the hard link to. + aux::vector* links; + + struct io_args + { + // for read and write, the offset into the piece + // the read or write should start + // for hash jobs, this is the first block the hash + // job is still holding a reference to. The end of + // the range of blocks a hash jobs holds references + // to is always the last block in the piece. + std::int32_t offset; + + // number of bytes 'buffer' points to. Used for read & write + std::uint16_t buffer_size; + + // this is used for partial_read. It's the number of bytes to skip + // into the buffer that we're reading into. + std::uint16_t buffer_offset; + + } io; + } d; + + // arguments used for read and write + // the piece this job applies to + union { + piece_index_t piece; + file_index_t file_index; + }; + + // the type of job this is + job_action_t action = job_action_t::read; + + // return value of operation + status_t ret = status_t::no_error; + + // flags controlling this job + disk_job_flags_t flags = disk_job_flags_t{}; + + move_flags_t move_flags = move_flags_t::always_replace_files; + +#if TORRENT_USE_ASSERTS + bool in_use = false; + + // set to true when the job is added to the completion queue. + // to make sure we don't add it twice + mutable bool job_posted = false; + + // set to true when the callback has been called once + // used to make sure we don't call it twice + mutable bool callback_called = false; + + // this is true when the job is blocked by a storage_fence + mutable bool blocked = false; +#endif + }; + +} +} + +#endif // TORRENT_DISK_IO_JOB_HPP diff --git a/include/libtorrent/aux_/netlink_utils.hpp b/include/libtorrent/aux_/netlink_utils.hpp new file mode 100644 index 0000000..190780c --- /dev/null +++ b/include/libtorrent/aux_/netlink_utils.hpp @@ -0,0 +1,99 @@ +/* + +Copyright (c) 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_NETLINK_UTILS_HPP_INCLUDED +#define TORRENT_NETLINK_UTILS_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_NETLINK + +#include +#include + +namespace libtorrent { +namespace aux { + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-compare" +#pragma clang diagnostic ignored "-Wcast-qual" +#pragma clang diagnostic ignored "-Wcast-align" +#endif + // these are here to concentrate all the shady casts these macros expand to, + // to disable the warnings for them all + inline bool nlmsg_ok(nlmsghdr const* hdr, int const len) + { return NLMSG_OK(hdr, len); } + + inline nlmsghdr const* nlmsg_next(nlmsghdr const* hdr, int& len) + { return NLMSG_NEXT(hdr, len); } + + inline void const* nlmsg_data(nlmsghdr const* hdr) + { return NLMSG_DATA(hdr); } + + inline rtattr const* rtm_rta(rtmsg const* hdr) + { return static_cast(RTM_RTA(hdr)); } + + inline std::size_t rtm_payload(nlmsghdr const* hdr) + { return RTM_PAYLOAD(hdr); } + + inline bool rta_ok(rtattr const* rt, std::size_t const len) + { return RTA_OK(rt, len); } + + inline void const* rta_data(rtattr const* rt) + { return RTA_DATA(rt); } + + inline rtattr const* rta_next(rtattr const* rt, std::size_t& len) + { return RTA_NEXT(rt, len); } + + inline rtattr const* ifa_rta(ifaddrmsg const* ifa) + { return static_cast(IFA_RTA(ifa)); } + + inline std::size_t ifa_payload(nlmsghdr const* hdr) + { return IFA_PAYLOAD(hdr); } + + inline rtattr const* ifla_rta(ifinfomsg const* ifinfo) + { return static_cast(IFLA_RTA(ifinfo)); } + + inline std::size_t ifla_payload(nlmsghdr const* hdr) + { return IFLA_PAYLOAD(hdr); } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +} +} + +#endif // TORRENT_USE_NETLINK + +#endif + diff --git a/include/libtorrent/aux_/noexcept_movable.hpp b/include/libtorrent/aux_/noexcept_movable.hpp new file mode 100644 index 0000000..94f0dc2 --- /dev/null +++ b/include/libtorrent/aux_/noexcept_movable.hpp @@ -0,0 +1,112 @@ +/* + +Copyright (c) 2017-2019, 2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef TORRENT_NOEXCEPT_MOVABLE_HPP_INCLUDED +#define TORRENT_NOEXCEPT_MOVABLE_HPP_INCLUDED + +#include +#include + +namespace libtorrent { +namespace aux { + +#if defined _MSC_VER && defined TORRENT_BUILD_SIMULATOR + // see simulation/test_error_handling.cpp for a description of this variable + extern thread_local int g_must_not_fail; + + template + T&& wrap(T&& v) { + ++g_must_not_fail; + return v; + } +#endif + + // this is a silly wrapper for address and endpoint to make their move + // constructors be noexcept (because boost.asio is incorrectly making them + // potentially throwing). This can be removed once libtorrent uses the + // networking TS. + template + struct noexcept_movable : T + { + noexcept_movable() = default; +#if defined _MSC_VER && defined TORRENT_BUILD_SIMULATOR + noexcept_movable(noexcept_movable&& rhs) noexcept + : T(std::forward(wrap(rhs))) + { + --g_must_not_fail; + } + noexcept_movable(T&& rhs) noexcept : T(std::forward(wrap(rhs))) // NOLINT + { + --g_must_not_fail; + } +#else + noexcept_movable(noexcept_movable&& rhs) noexcept + : T(std::forward(rhs)) {} + noexcept_movable(T&& rhs) noexcept : T(std::forward(rhs)) {} // NOLINT +#endif + noexcept_movable(noexcept_movable const& rhs) = default; + noexcept_movable(T const& rhs) : T(rhs) {} // NOLINT + noexcept_movable& operator=(noexcept_movable const& rhs) = default; + noexcept_movable& operator=(noexcept_movable&& rhs) = default; + using T::T; + using T::operator=; + }; + + template + struct noexcept_move_only : T + { +#if defined _MSC_VER && defined TORRENT_BUILD_SIMULATOR + noexcept_move_only(noexcept_move_only&& rhs) noexcept + : T(std::forward(wrap(rhs))) + { + --g_must_not_fail; + } + noexcept_move_only(T&& rhs) noexcept : T(std::forward(wrap(rhs))) // NOLINT + { + --g_must_not_fail; + } +#else + noexcept_move_only(noexcept_move_only&& rhs) noexcept + : T(std::forward(rhs)) + {} + noexcept_move_only(T&& rhs) noexcept : T(std::forward(rhs)) {} // NOLINT +#endif + noexcept_move_only(noexcept_move_only const& rhs) = default; + noexcept_move_only(T const& rhs) : T(rhs) {} // NOLINT + noexcept_move_only& operator=(noexcept_move_only const& rhs) = default; + noexcept_move_only& operator=(noexcept_move_only&& rhs) = default; + using T::T; + }; + +} +} + +#endif diff --git a/include/libtorrent/aux_/numeric_cast.hpp b/include/libtorrent/aux_/numeric_cast.hpp new file mode 100644 index 0000000..0e6fcb3 --- /dev/null +++ b/include/libtorrent/aux_/numeric_cast.hpp @@ -0,0 +1,71 @@ +/* + +Copyright (c) 2017, Alden Torres +Copyright (c) 2018, 2020, Arvid Norberg +Copyright (c) 2018, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_NUMERIC_CAST_HPP +#define TORRENT_NUMERIC_CAST_HPP + +#include +#include +#include + +#include "libtorrent/assert.hpp" + +namespace libtorrent { namespace aux { + + template ::value && std::is_integral::value>::type> + T numeric_cast(In v) + { + T r = static_cast(v); + TORRENT_ASSERT(v == static_cast(r)); + TORRENT_ASSERT(std::is_unsigned::value || std::is_signed::value + || std::int64_t(v) >= 0); + TORRENT_ASSERT(std::is_signed::value || std::is_unsigned::value + || std::size_t(v) <= std::size_t((std::numeric_limits::max)())); + return r; + } + + // in C++ 17 you can use std::clamp + template + T clamp(T v, T lo, T hi) + { + TORRENT_ASSERT(lo <= hi); + if (v < lo) return lo; + if (hi < v) return hi; + return v; + } + +}} + +#endif diff --git a/include/libtorrent/aux_/open_mode.hpp b/include/libtorrent/aux_/open_mode.hpp new file mode 100644 index 0000000..cbb72a6 --- /dev/null +++ b/include/libtorrent/aux_/open_mode.hpp @@ -0,0 +1,63 @@ +/* + +Copyright (c) 2016-2017, 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_OPEN_MODE_HPP +#define TORRENT_OPEN_MODE_HPP + +#include +#include "libtorrent/flags.hpp" + +namespace libtorrent { +namespace aux { + + // hidden + using open_mode_t = flags::bitfield_flag; + + namespace open_mode { + constexpr open_mode_t read_only{0}; + constexpr open_mode_t write = 0_bit; + constexpr open_mode_t no_cache = 1_bit; + constexpr open_mode_t truncate = 2_bit; + constexpr open_mode_t no_atime = 3_bit; + constexpr open_mode_t sequential_access = 4_bit; + constexpr open_mode_t hidden = 5_bit; + constexpr open_mode_t sparse = 6_bit; + constexpr open_mode_t executable = 7_bit; + constexpr open_mode_t allow_set_file_valid_data = 8_bit; + constexpr open_mode_t no_mmap = 9_bit; + } +} // aux + +} // libtorrent + +#endif + diff --git a/include/libtorrent/aux_/packet_buffer.hpp b/include/libtorrent/aux_/packet_buffer.hpp new file mode 100644 index 0000000..f7ff6c5 --- /dev/null +++ b/include/libtorrent/aux_/packet_buffer.hpp @@ -0,0 +1,122 @@ +/* + +Copyright (c) 2010-2018, Arvid Norberg, Daniel Wallin. +Copyright (c) 2010-2012, 2014-2020, Arvid Norberg +Copyright (c) 2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PACKET_BUFFER_HPP_INCLUDED +#define TORRENT_PACKET_BUFFER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/unique_ptr.hpp" +#include "libtorrent/aux_/packet_pool.hpp" // for packet_ptr/packet_deleter +#include +#include +#include // for unique_ptr + +namespace libtorrent { +namespace aux { + + struct packet; + + // this is a circular buffer that automatically resizes + // itself as elements are inserted. Elements are indexed + // by integers and are assumed to be sequential. Unless the + // old elements are removed when new elements are inserted, + // the buffer will be resized. + + // m_capacity is the number of elements in m_array + // and must be an even 2^x. + // m_first is the lowest index that has an element + // it also determines which indices the other slots + // refers to. Since it's a circular buffer, it wraps + // around. For example + + // m_first = 9 + // | refers to index 14 + // | | + // V V + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | | | | | | | | | | | | | | | | | mask = (m_capacity-1) + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // ^ + // | + // refers to index 15 + + // whenever the element at the cursor is removed, the + // cursor is bumped to the next occupied element + + class TORRENT_EXTRA_EXPORT packet_buffer + { + public: + using index_type = std::uint32_t; + + packet_ptr insert(index_type idx, packet_ptr value); + + int size() const { return m_size; } + + bool empty() const { return m_size == 0; } + + std::uint32_t capacity() const + { return m_capacity; } + + packet* at(index_type idx) const; + + packet_ptr remove(index_type idx); + + void reserve(std::uint32_t size); + + index_type cursor() const { return m_first; } + + index_type span() const { return (m_last - m_first) & 0xffff; } + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + private: + aux::unique_ptr m_storage; + std::uint32_t m_capacity = 0; + + // this is the total number of elements that are occupied + // in the array + int m_size = 0; + + // This defines the first index that is part of the m_storage. + // last is one passed the last used slot + index_type m_first{0}; + index_type m_last{0}; + }; + +} +} + +#endif // TORRENT_PACKET_BUFFER_HPP_INCLUDED diff --git a/include/libtorrent/aux_/packet_pool.hpp b/include/libtorrent/aux_/packet_pool.hpp new file mode 100644 index 0000000..f5a2ac6 --- /dev/null +++ b/include/libtorrent/aux_/packet_pool.hpp @@ -0,0 +1,244 @@ +/* + +Copyright (c) 2017-2018, Alden Torres +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2017, 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PACKET_POOL_HPP +#define TORRENT_PACKET_POOL_HPP + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/throw.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/debug.hpp" // for single_threaded + +#include +#include // for unique_ptr +#include + +#ifdef TORRENT_ADDRESS_SANITIZER +#include +#endif + +namespace libtorrent { +namespace aux { + + // internal: some MTU and protocol header sizes constants + constexpr int TORRENT_IPV4_HEADER = 20; + constexpr int TORRENT_IPV6_HEADER = 40; + constexpr int TORRENT_UDP_HEADER = 8; + constexpr int TORRENT_UTP_HEADER = 20; + constexpr int TORRENT_SOCKS5_HEADER = 6; // plus the size of the destination address + constexpr int TORRENT_ETHERNET_MTU = 1500; + constexpr int TORRENT_TEREDO_MTU = 1280; + constexpr int TORRENT_INET_MIN_MTU = 576; + + // used for out-of-order incoming packets + // as well as sent packets that are waiting to be ACKed + struct packet + { + // the last time this packet was sent + time_point send_time; + +#if TORRENT_USE_ASSERTS + int64_t num_fast_resend; +#endif + + // the number of bytes actually allocated in 'buf' + std::uint16_t allocated; + + // the size of the buffer 'buf' points to + std::uint16_t size; + + // this is the offset to the payload inside the buffer + // this is also used as a cursor to describe where the + // next payload that hasn't been consumed yet starts + std::uint16_t header_size; + + // the number of times this packet has been sent + std::uint8_t num_transmissions:6; + + // true if we need to send this packet again. All + // outstanding packets are marked as needing to be + // resent on timeouts + bool need_resend:1; + + // this is set to true for packets that were + // sent with the DF bit set (Don't Fragment) + bool mtu_probe:1; + + // the actual packet buffer + std::uint8_t buf[1]; + }; + + static_assert((sizeof(packet) & 0x7) == 0, "packet structure size should be divisible by 8"); + + struct packet_deleter + { + // deleter for std::unique_ptr + void operator()(packet* p) const + { + TORRENT_ASSERT(p != nullptr); +#ifdef TORRENT_ADDRESS_SANITIZER + __asan_unpoison_memory_region(p, sizeof(packet)); +#endif + p->~packet(); + std::free(p); + } + }; + + using packet_ptr = std::unique_ptr; + + // internal + inline packet_ptr create_packet(int size) + { + packet* p = static_cast(std::malloc(sizeof(packet) + aux::numeric_cast(size))); + if (p == nullptr) aux::throw_ex(); + p = new (p) packet(); + p->allocated = aux::numeric_cast(size); + return packet_ptr(p); + } + + struct TORRENT_EXTRA_EXPORT packet_slab + { + int allocate_size; + + explicit packet_slab(int const alloc_size, std::size_t const limit = 10) + : allocate_size(alloc_size) + , m_limit(limit) + { + m_storage.reserve(m_limit); + } + + packet_slab(const packet_slab&) = delete; + packet_slab(packet_slab&&) = default; + + void try_push_back(packet_ptr &p) + { + if (m_storage.size() < m_limit) + { +#ifdef TORRENT_ADDRESS_SANITIZER + __asan_poison_memory_region(p.get(), sizeof(packet) + std::size_t(allocate_size)); +#endif + m_storage.push_back(std::move(p)); + } + } + + packet_ptr alloc() + { + if (m_storage.empty()) return create_packet(allocate_size); + auto ret = std::move(m_storage.back()); +#ifdef TORRENT_ADDRESS_SANITIZER + __asan_unpoison_memory_region(ret.get(), sizeof(packet) + std::size_t(allocate_size)); +#endif + m_storage.pop_back(); + return ret; + } + + void decay() + { + if (m_storage.empty()) return; + m_storage.erase(m_storage.end() - 1); + } + + private: + const std::size_t m_limit; + std::vector m_storage; + }; + + // single thread packet allocation packet pool + // can handle common cases of packet size by 3 pools + struct TORRENT_EXTRA_EXPORT packet_pool : private single_threaded + { + // there's a bug in GCC where allocating these in + // member initializer expressions won't propagate exceptions. + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80683 + packet_pool() + : m_syn_slab(TORRENT_UTP_HEADER) + , m_mtu_floor_slab(mtu_floor_size) + , m_mtu_ceiling_slab(mtu_ceiling_size) + {} + packet_pool(packet_pool&&) = default; + + packet_ptr acquire(int const allocate) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(allocate >= 0); + TORRENT_ASSERT(allocate <= (std::numeric_limits::max)()); + + return alloc(allocate); + } + + void release(packet_ptr p) + { + TORRENT_ASSERT(is_single_thread()); + + if (!p) return; + + int const allocated = p->allocated; + + if (allocated == m_syn_slab.allocate_size) { m_syn_slab.try_push_back(p); } + else if (allocated == m_mtu_floor_slab.allocate_size) { m_mtu_floor_slab.try_push_back(p); } + else if (allocated == m_mtu_ceiling_slab.allocate_size) { m_mtu_ceiling_slab.try_push_back(p); } + } + + // periodically free up some of the cached packets + void decay() + { + TORRENT_ASSERT(is_single_thread()); + + m_syn_slab.decay(); + m_mtu_floor_slab.decay(); + m_mtu_ceiling_slab.decay(); + } + + private: + packet_ptr alloc(int const allocate) + { + if (allocate <= m_syn_slab.allocate_size) { return m_syn_slab.alloc(); } + else if (allocate <= m_mtu_floor_slab.allocate_size) { return m_mtu_floor_slab.alloc(); } + else if (allocate <= m_mtu_ceiling_slab.allocate_size) { return m_mtu_ceiling_slab.alloc(); } + + return create_packet(allocate); + } + static constexpr int mtu_floor_size = TORRENT_INET_MIN_MTU - TORRENT_IPV4_HEADER - TORRENT_UDP_HEADER; + static constexpr int mtu_ceiling_size = TORRENT_ETHERNET_MTU - TORRENT_IPV4_HEADER - TORRENT_UDP_HEADER; + packet_slab m_syn_slab; + packet_slab m_mtu_floor_slab; + packet_slab m_mtu_ceiling_slab; + }; +} +} + +#endif // TORRENT_PACKET_POOL_HPP diff --git a/include/libtorrent/aux_/path.hpp b/include/libtorrent/aux_/path.hpp new file mode 100644 index 0000000..88bde12 --- /dev/null +++ b/include/libtorrent/aux_/path.hpp @@ -0,0 +1,180 @@ +/* + +Copyright (c) 2017-2020, Arvid Norberg +Copyright (c) 2017, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PATH_HPP_INCLUDED +#define TORRENT_PATH_HPP_INCLUDED + +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/span.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#ifdef TORRENT_WINDOWS +// windows part +#include "libtorrent/aux_/windows.hpp" +#include +#include +#else + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 +#endif + +#include +#include +#include +#include +#include // for DIR + +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/error_code.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent { + + struct file_status + { + std::int64_t file_size = 0; + std::uint64_t atime = 0; + std::uint64_t mtime = 0; + std::uint64_t ctime = 0; + enum { +#if defined TORRENT_WINDOWS + fifo = 0x1000, // named pipe (fifo) + character_special = 0x2000, // character special + directory = 0x4000, // directory + regular_file = 0x8000 // regular +#else + fifo = 0010000, // named pipe (fifo) + character_special = 0020000, // character special + directory = 0040000, // directory + block_special = 0060000, // block special + regular_file = 0100000, // regular + link = 0120000, // symbolic link + socket = 0140000 // socket +#endif + } modes_t; + int mode = 0; + }; + + // internal flags for stat_file + enum { dont_follow_links = 1 }; + TORRENT_EXTRA_EXPORT void stat_file(std::string const& f, file_status* s + , error_code& ec, int flags = 0); + TORRENT_EXTRA_EXPORT void rename(std::string const& f + , std::string const& newf, error_code& ec); + TORRENT_EXTRA_EXPORT void create_directories(std::string const& f + , error_code& ec); + TORRENT_EXTRA_EXPORT void create_directory(std::string const& f + , error_code& ec); + TORRENT_EXTRA_EXPORT void remove_all(std::string const& f + , error_code& ec); + TORRENT_EXTRA_EXPORT void remove(std::string const& f, error_code& ec); + TORRENT_EXTRA_EXPORT bool exists(std::string const& f, error_code& ec); + TORRENT_EXTRA_EXPORT bool is_directory(std::string const& f + , error_code& ec); + + // file is expected to exist, link will be created to point to it. If hard + // links are not supported by the filesystem or OS, the file will be copied. + TORRENT_EXTRA_EXPORT void hard_link(std::string const& file + , std::string const& link, error_code& ec); + + // split out a path segment from the left side or right side + TORRENT_EXTRA_EXPORT std::pair rsplit_path(string_view p); + TORRENT_EXTRA_EXPORT std::pair lsplit_path(string_view p); + TORRENT_EXTRA_EXPORT std::pair lsplit_path(string_view p, std::size_t pos); + + TORRENT_EXTRA_EXPORT std::string extension(std::string const& f); + TORRENT_EXTRA_EXPORT std::string remove_extension(std::string const& f); + TORRENT_EXTRA_EXPORT bool is_root_path(std::string const& f); + TORRENT_EXTRA_EXPORT bool path_equal(std::string const& lhs, std::string const& rhs); + + // compare each path element individually + TORRENT_EXTRA_EXPORT int path_compare(string_view lhs, string_view lfile + , string_view rhs, string_view rfile); + + // internal used by create_torrent.hpp + TORRENT_EXTRA_EXPORT std::string parent_path(std::string const& f); + TORRENT_EXTRA_EXPORT bool has_parent_path(std::string const& f); + + // internal used by create_torrent.hpp + TORRENT_EXTRA_EXPORT std::string filename(std::string const& f); + TORRENT_EXTRA_EXPORT std::string combine_path(string_view lhs + , string_view rhs); + TORRENT_EXTRA_EXPORT void append_path(std::string& branch + , string_view leaf); + TORRENT_EXTRA_EXPORT std::string lexically_relative(string_view base + , string_view target); + + // internal used by create_torrent.hpp + TORRENT_EXTRA_EXPORT std::string complete(string_view f); + TORRENT_EXTRA_EXPORT bool is_complete(string_view f); + TORRENT_EXTRA_EXPORT std::string current_working_directory(); +#if TORRENT_USE_UNC_PATHS + TORRENT_EXTRA_EXPORT std::string canonicalize_path(string_view f); +#endif + +// internal type alias export should be used at unit tests only + using native_path_string = +#if defined TORRENT_WINDOWS + std::wstring; +#else + std::string; +#endif + +// internal export should be used at unit tests only + TORRENT_EXTRA_EXPORT native_path_string convert_to_native_path_string(std::string const& path); + +#if defined TORRENT_WINDOWS +// internal export should be used at unit tests only + TORRENT_EXTRA_EXPORT std::string convert_from_native_path(wchar_t const* s); +#else +// internal export should be used at unit tests only + TORRENT_EXTRA_EXPORT std::string convert_from_native_path(char const* s); +#endif +} + +#endif // TORRENT_PATH_HPP_INCLUDED diff --git a/include/libtorrent/aux_/polymorphic_socket.hpp b/include/libtorrent/aux_/polymorphic_socket.hpp new file mode 100644 index 0000000..0c71a14 --- /dev/null +++ b/include/libtorrent/aux_/polymorphic_socket.hpp @@ -0,0 +1,188 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_POLYMORPHIC_SOCKET +#define TORRENT_POLYMORPHIC_SOCKET + +#include "libtorrent/error_code.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { +namespace aux { + + template + struct first_element + { using type = T; }; + +#define TORRENT_FWD_CALL(x) \ + return boost::apply_visitor([&](auto& s){ return s.x; }, *this) + + template + struct polymorphic_socket + : boost::variant + { + using first_socket = typename first_element::type; + using endpoint_type = typename first_socket::endpoint_type; + using protocol_type = typename first_socket::protocol_type; + + using receive_buffer_size = typename first_socket::receive_buffer_size; + using send_buffer_size = typename first_socket::send_buffer_size; + + using executor_type = typename first_socket::executor_type; + + template + explicit polymorphic_socket(S s) : boost::variant(std::move(s)) + { + static_assert(std::is_nothrow_move_constructible::value + , "should really be nothrow move contsructible, since it's part of a variant"); + } + polymorphic_socket(polymorphic_socket&&) = default; + ~polymorphic_socket() = default; + + bool is_open() const + { TORRENT_FWD_CALL(is_open()); } + + void open(protocol_type const& p, error_code& ec) + { TORRENT_FWD_CALL(open(p, ec)); } + + void close(error_code& ec) + { TORRENT_FWD_CALL(close(ec)); } + + endpoint_type local_endpoint(error_code& ec) const + { TORRENT_FWD_CALL(local_endpoint(ec)); } + + endpoint_type remote_endpoint(error_code& ec) const + { TORRENT_FWD_CALL(remote_endpoint(ec)); } + + void bind(endpoint_type const& endpoint, error_code& ec) + { TORRENT_FWD_CALL(bind(endpoint, ec)); } + + std::size_t available(error_code& ec) const + { TORRENT_FWD_CALL(available(ec)); } + +#ifndef BOOST_NO_EXCEPTIONS + void open(protocol_type const& p) + { TORRENT_FWD_CALL(open(p)); } + + void close() + { TORRENT_FWD_CALL(close()); } + + endpoint_type local_endpoint() const + { TORRENT_FWD_CALL(local_endpoint()); } + + endpoint_type remote_endpoint() const + { TORRENT_FWD_CALL(remote_endpoint()); } + + void bind(endpoint_type const& endpoint) + { TORRENT_FWD_CALL(bind(endpoint)); } + + std::size_t available() const + { TORRENT_FWD_CALL(available()); } +#endif + + template + std::size_t read_some(Mutable_Buffers const& buffers, error_code& ec) + { TORRENT_FWD_CALL(read_some(buffers, ec)); } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler handler) + { TORRENT_FWD_CALL(async_read_some(buffers, std::move(handler))); } + + template + std::size_t write_some(Const_Buffers const& buffers, error_code& ec) + { TORRENT_FWD_CALL(write_some(buffers, ec)); } + + template + void async_write_some(Const_Buffers const& buffers, Handler handler) + { TORRENT_FWD_CALL(async_write_some(buffers, std::move(handler))); } + + template + void async_connect(endpoint_type const& endpoint, Handler handler) + { TORRENT_FWD_CALL(async_connect(endpoint, std::move(handler))); } + +#ifndef BOOST_NO_EXCEPTIONS + template + void io_control(IO_Control_Command& ioc) + { TORRENT_FWD_CALL(io_control(ioc)); } + + template + std::size_t read_some(Mutable_Buffers const& buffers) + { TORRENT_FWD_CALL(read_some(buffers)); } +#endif + + template + void io_control(IO_Control_Command& ioc, error_code& ec) + { TORRENT_FWD_CALL(io_control(ioc, ec)); } + +#ifndef BOOST_NO_EXCEPTIONS + template + void set_option(SettableSocketOption const& opt) + { TORRENT_FWD_CALL(set_option(opt)); } +#endif + + template + void set_option(SettableSocketOption const& opt, error_code& ec) + { TORRENT_FWD_CALL(set_option(opt, ec)); } + + void non_blocking(bool b, error_code& ec) + { TORRENT_FWD_CALL(non_blocking(b, ec)); } + +#ifndef BOOST_NO_EXCEPTIONS + void non_blocking(bool b) + { TORRENT_FWD_CALL(non_blocking(b)); } +#endif + +#ifndef BOOST_NO_EXCEPTIONS + template + void get_option(GettableSocketOption& opt) + { TORRENT_FWD_CALL(get_option(opt)); } +#endif + + template + void get_option(GettableSocketOption& opt, error_code& ec) + { TORRENT_FWD_CALL(get_option(opt, ec)); } + + // explicitly disallow assignment, to silence msvc warning + polymorphic_socket& operator=(polymorphic_socket const&) = delete; + polymorphic_socket& operator=(polymorphic_socket&&) = default; + }; + +#undef TORRENT_FWD_CALL + +} +} + +#endif // TORRENT_POLYMORPHIC_SOCKET diff --git a/include/libtorrent/aux_/pool.hpp b/include/libtorrent/aux_/pool.hpp new file mode 100644 index 0000000..b46191b --- /dev/null +++ b/include/libtorrent/aux_/pool.hpp @@ -0,0 +1,64 @@ +/* + +Copyright (c) 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_POOL_HPP +#define TORRENT_POOL_HPP + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { +namespace aux { + +struct allocator_new_delete +{ + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + // TODO: ensure the alignment is good here + static char* malloc(size_type const bytes) + { return new char[bytes]; } + static void free(char* const block) + { delete [] block; } +}; + +using pool = boost::pool; + +template +using object_pool = boost::object_pool; + +} +} + +#endif diff --git a/include/libtorrent/aux_/portmap.hpp b/include/libtorrent/aux_/portmap.hpp new file mode 100644 index 0000000..34c7d0b --- /dev/null +++ b/include/libtorrent/aux_/portmap.hpp @@ -0,0 +1,105 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2017-2020, Arvid Norberg +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_AUX_PORTMAP_HPP_INCLUDED +#define LIBTORRENT_AUX_PORTMAP_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/portmap.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/listen_socket_handle.hpp" + +namespace libtorrent { +namespace aux { + + enum class portmap_action : std::uint8_t + { + none, add, del + }; + + struct TORRENT_EXTRA_EXPORT portmap_callback + { + // int: port-mapping index + // address: external address as queried from router + // int: external port + // int: protocol (UDP, TCP) + // error_code: error, an empty error means success + // int: transport is 0 for NAT-PMP and 1 for UPnP + virtual void on_port_mapping(port_mapping_t mapping, address const& ip, int port + , portmap_protocol proto, error_code const& ec, portmap_transport transport + , listen_socket_handle const& ls) = 0; +#ifndef TORRENT_DISABLE_LOGGING + virtual bool should_log_portmap(portmap_transport transport) const = 0; + virtual void log_portmap(portmap_transport transport, char const* msg + , listen_socket_handle const&) const = 0; +#endif + + protected: + ~portmap_callback() {} + }; + + struct base_mapping + { + // the time the port mapping will expire + time_point expires; + + portmap_action act = portmap_action::none; + + // the external (on the NAT router) port + // for the mapping. This is the port we + // should announce to others + int external_port = 0; + + portmap_protocol protocol = portmap_protocol::none; + }; + + inline char const* to_string(portmap_protocol const p) + { + return p == portmap_protocol::udp ? "UDP" : "TCP"; + } + inline char const* to_string(portmap_action const act) + { + switch (act) + { + case portmap_action::none: return "none"; + case portmap_action::add: return "add"; + case portmap_action::del: return "delete"; + } + return ""; + } +}} + +#endif // LIBTORRENT_AUX_PORTMAP_HPP_INCLUDED diff --git a/include/libtorrent/aux_/posix_part_file.hpp b/include/libtorrent/aux_/posix_part_file.hpp new file mode 100644 index 0000000..18927ba --- /dev/null +++ b/include/libtorrent/aux_/posix_part_file.hpp @@ -0,0 +1,139 @@ +/* + +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, d-komarov +Copyright (c) 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_POSIX_PART_FILE_HPP_INCLUDE +#define TORRENT_POSIX_PART_FILE_HPP_INCLUDE + +#include +#include +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/aux_/open_mode.hpp" +#include "libtorrent/aux_/storage_utils.hpp" // for iovec_t +#include "libtorrent/aux_/file_pointer.hpp" + +namespace libtorrent { +namespace aux { + + using slot_index_t = aux::strong_typedef; + + struct TORRENT_EXTRA_EXPORT posix_part_file + { + // create a part file at ``path``, that can hold ``num_pieces`` pieces. + // each piece being ``piece_size`` number of bytes + posix_part_file(std::string path, std::string name, int num_pieces, int piece_size); + ~posix_part_file(); + + int write(span bufs, piece_index_t piece, int offset, error_code& ec); + int read(span buf, piece_index_t piece, int offset, error_code& ec); + int hash(hasher& ph, std::ptrdiff_t len, piece_index_t piece, int offset, error_code& ec); + int hash2(hasher256& ph, std::ptrdiff_t len, piece_index_t piece, int offset, error_code& ec); + + // free the slot the given piece is stored in. We no longer need to store this + // piece in the part file + void free_piece(piece_index_t piece); + + void move_partfile(std::string const& path, error_code& ec); + + // the function is called for every block of data belonging to the + // specified range that's in the posix_part_file. The first parameter is the + // offset within the range + void export_file(std::function)> f + , std::int64_t offset, std::int64_t size, error_code& ec); + + // flush the metadata + void flush_metadata(error_code& ec); + + private: + + enum class open_mode : std::uint8_t + { + read_only, read_write + }; + + file_pointer open_file(open_mode mode, error_code& ec); + void flush_metadata_impl(error_code& ec); + + std::int64_t slot_offset(slot_index_t const slot) const + { + return static_cast(slot) * static_cast(m_piece_size) + + m_header_size; + } + + template + int do_hash(Hasher& ph, std::ptrdiff_t len, piece_index_t piece, int offset, error_code& ec); + + std::string m_path; + std::string const m_name; + + // allocate a slot and return the slot index + slot_index_t allocate_slot(piece_index_t piece); + + // this is a list of unallocated slots in the part file + // within the m_num_allocated range + std::vector m_free_slots; + + // this is the number of slots allocated + slot_index_t m_num_allocated{0}; + + // the max number of pieces in the torrent this part file is + // backing + int const m_max_pieces; + + // number of bytes each piece contains + int const m_piece_size; + + // this is the size of the posix_part_file header, it is added + // to offsets when calculating the offset to read and write + // payload data from + int const m_header_size; + + // if this is true, the metadata in memory has changed since + // we last saved or read it from disk. It means that we + // need to flush the metadata before closing the file + bool m_dirty_metadata = false; + + // maps a piece index to the part-file slot it is stored in + std::unordered_map m_piece_map; + }; +} +} + +#endif diff --git a/include/libtorrent/aux_/posix_storage.hpp b/include/libtorrent/aux_/posix_storage.hpp new file mode 100644 index 0000000..ac626f3 --- /dev/null +++ b/include/libtorrent/aux_/posix_storage.hpp @@ -0,0 +1,122 @@ +/* + +Copyright (c) 2016, 2019-2020, 2022, Arvid Norberg +Copyright (c) 2018, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_POSIX_STORAGE +#define TORRENT_POSIX_STORAGE + +#include "libtorrent/config.hpp" +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/aux_/storage_utils.hpp" // for iovec_t +#include "libtorrent/hex.hpp" // to_hex +#include "libtorrent/aux_/open_mode.hpp" // for aux::open_mode_t +#include "libtorrent/aux_/file_pointer.hpp" +#include "libtorrent/aux_/posix_part_file.hpp" +#include +#include + +namespace libtorrent { +namespace aux { + + struct session_settings; + + struct TORRENT_EXTRA_EXPORT posix_storage + { + explicit posix_storage(storage_params const& p); + file_storage const& files() const; + ~posix_storage(); + + int read(settings_interface const& sett + , span bufs + , piece_index_t const piece, int const offset + , storage_error& error); + + int write(settings_interface const& sett + , span bufs + , piece_index_t const piece, int const offset + , storage_error& error); + + bool has_any_file(storage_error& error); + void set_file_priority(settings_interface const& + , aux::vector& prio + , storage_error& ec); + bool verify_resume_data(add_torrent_params const& rd + , aux::vector const& links + , storage_error& ec); + + void release_files(); + + void delete_files(remove_flags_t options, storage_error& error); + + std::pair move_storage(std::string const& sp + , move_flags_t const flags, storage_error& ec); + + void rename_file(file_index_t const index, std::string const& new_filename, storage_error& ec); + + status_t initialize(settings_interface const&, storage_error& ec); + + private: + + file_pointer open_file(file_index_t idx, open_mode_t mode, std::int64_t offset + , storage_error& ec); + + void need_partfile(); + bool use_partfile(file_index_t index) const; + void use_partfile(file_index_t index, bool b); + + file_storage const& m_files; + std::unique_ptr m_mapped_files; + std::string m_save_path; + stat_cache m_stat_cache; + + aux::vector m_file_priority; + + // this this is an array indexed by file-index. Each slot represents + // whether this file has the part-file enabled for it. This is used for + // backwards compatibility with pre-partfile versions of libtorrent. If + // this vector is empty, the default is that files *do* use the partfile. + // on startup, any 0-priority file that's found in it's original location + // is expected to be an old-style (pre-partfile) torrent storage, and + // those files have their slot set to false in this vector. + // note that the vector is *sparse*, it's only allocated if a file has its + // entry set to false, and only indices up to that entry. + aux::vector m_use_partfile; + + std::string m_part_file_name; + std::unique_ptr m_part_file; + }; +} +} +#endif + diff --git a/include/libtorrent/aux_/proxy_settings.hpp b/include/libtorrent/aux_/proxy_settings.hpp new file mode 100644 index 0000000..57cf0b6 --- /dev/null +++ b/include/libtorrent/aux_/proxy_settings.hpp @@ -0,0 +1,93 @@ +/* + +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PROXY_SETTINGS_HPP_INCLUDED +#define TORRENT_PROXY_SETTINGS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/settings_pack.hpp" + +#include + +namespace libtorrent { + +struct settings_pack; +namespace aux { + + struct session_settings; + + struct TORRENT_EXPORT proxy_settings + { + // defaults constructs proxy settings, initializing it to the default + // settings. + proxy_settings(); + + // construct the proxy_settings object from the settings + // this constructor is implemented in session_impl.cpp + explicit proxy_settings(settings_pack const& sett); + explicit proxy_settings(aux::session_settings const& sett); + + // the name or IP of the proxy server. ``port`` is the port number the + // proxy listens to. If required, ``username`` and ``password`` can be + // set to authenticate with the proxy. + std::string hostname; + + // when using a proxy type that requires authentication, the username + // and password fields must be set to the credentials for the proxy. + std::string username; + std::string password; + + // tells libtorrent what kind of proxy server it is. See proxy_type_t + // enum for options + settings_pack::proxy_type_t type = settings_pack::none; + + // the port the proxy server is running on + std::uint16_t port = 0; + + // defaults to true. It means that hostnames should be attempted to be + // resolved through the proxy instead of using the local DNS service. + // This is only supported by SOCKS5 and HTTP. + bool proxy_hostnames = true; + + // determines whether or not to exempt peer and web seed connections + // from using the proxy. This defaults to true, i.e. peer connections are + // proxied by default. + bool proxy_peer_connections = true; + + // if true, tracker connections are subject to the proxy settings + bool proxy_tracker_connections = true; + }; + +}} + +#endif diff --git a/include/libtorrent/aux_/range.hpp b/include/libtorrent/aux_/range.hpp new file mode 100644 index 0000000..44815ed --- /dev/null +++ b/include/libtorrent/aux_/range.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2017, 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_RANGE_HPP +#define TORRENT_RANGE_HPP + +#include "libtorrent/aux_/vector.hpp" + +namespace libtorrent { namespace aux { + + template + struct iterator_range + { + Iter _begin; + Iter _end; + Iter begin() { return _begin; } + Iter end() { return _end; } + }; + + template + iterator_range range(Iter begin, Iter end) + { return { begin, end}; } + + template + iterator_range range(vector& vec + , IndexType begin, IndexType end) + { + using type = typename underlying_index_t::type; + return {vec.data() + static_cast(begin), vec.data() + static_cast(end)}; + } + + template + iterator_range range(vector const& vec + , IndexType begin, IndexType end) + { + using type = typename underlying_index_t::type; + return {vec.data() + static_cast(begin), vec.data() + static_cast(end)}; + } +}} + +#endif diff --git a/include/libtorrent/aux_/receive_buffer.hpp b/include/libtorrent/aux_/receive_buffer.hpp new file mode 100644 index 0000000..c47777d --- /dev/null +++ b/include/libtorrent/aux_/receive_buffer.hpp @@ -0,0 +1,230 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2018, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_RECEIVE_BUFFER_HPP_INCLUDED +#define TORRENT_RECEIVE_BUFFER_HPP_INCLUDED + +#include "libtorrent/aux_/buffer.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/sliding_average.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" + +#include + +namespace libtorrent { +namespace aux { + +struct TORRENT_EXTRA_EXPORT receive_buffer +{ + friend struct crypto_receive_buffer; + + // explicitly disallow assignment, to silence msvc warning + receive_buffer& operator=(receive_buffer const&) = delete; + + int packet_size() const { return m_packet_size; } + int packet_bytes_remaining() const + { + TORRENT_ASSERT(m_recv_start == 0); + TORRENT_ASSERT(m_packet_size > 0); + return m_packet_size - m_recv_pos; + } + + int max_receive() const; + + bool packet_finished() const { return m_packet_size <= m_recv_pos; } + int pos() const { return m_recv_pos; } + int capacity() const { return aux::numeric_cast(m_recv_buffer.size()); } + int watermark() const { return aux::numeric_cast(m_watermark.mean()); } + + span reserve(int size); + void grow(int limit); + + // tell the buffer we just received more bytes at the end of it. This will + // advance the end cursor + void received(int bytes_transferred) + { + TORRENT_ASSERT(m_packet_size > 0); + m_recv_end += bytes_transferred; + TORRENT_ASSERT(m_recv_pos <= int(m_recv_buffer.size())); + } + + // tell the buffer we consumed some bytes of it. This will advance the read + // cursor + int advance_pos(int bytes); + + // has the read cursor reached the end cursor? + bool pos_at_end() { return m_recv_pos == m_recv_end; } + + // size = the packet size to remove from the receive buffer + // packet_size = the next packet size to receive in the buffer + // offset = the offset into the receive buffer where to remove `size` bytes + void cut(int size, int packet_size, int offset = 0); + + // return the interval between the start of the buffer to the read cursor. + // This is the "current" packet. + span get() const; + +#if !defined TORRENT_DISABLE_ENCRYPTION + // returns the buffer from the current packet start position to the last + // received byte (possibly part of another packet) + span mutable_buffer(); + + // returns the last 'bytes' from the receive buffer + span mutable_buffer(int bytes); +#endif + + // the purpose of this function is to free up and cut off all messages + // in the receive buffer that have been parsed and processed. + void normalize(int force_shrink = 0); + bool normalized() const { return m_recv_start == 0; } + + void reset(int packet_size); + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const + { + TORRENT_ASSERT(m_recv_end >= m_recv_start); + TORRENT_ASSERT(m_recv_end <= int(m_recv_buffer.size())); + TORRENT_ASSERT(m_recv_start <= int(m_recv_buffer.size())); + TORRENT_ASSERT(m_recv_start + m_recv_pos <= int(m_recv_buffer.size())); + } +#endif + +private: + + // m_recv_buffer.data() (start of actual receive buffer) + // | + // | m_recv_start (start of current packet) + // | | + // | | m_recv_pos (number of bytes consumed + // | | | by upper layer, from logical receive buffer) + // | | | + // | x---------x + // | | | m_recv_buffer.size() (end of actual receive buffer) + // | | | | + // v v v v + // *------==========--------- + // ^ + // | + // | + // ------------------->x m_recv_end (end of received data, + // beyond this point is garbage) + // m_recv_buffer + + // the start of the logical receive buffer + int m_recv_start = 0; + + // the number of valid, received bytes in m_recv_buffer + int m_recv_end = 0; + + // the byte offset in m_recv_buffer that we have + // are passing on to the upper layer. This is + // always <= m_recv_end + int m_recv_pos = 0; + + // the size (in bytes) of the bittorrent message + // we're currently receiving + int m_packet_size = 0; + + // keep track of how much of the receive buffer we use, if we're not using + // enough of it we shrink it + sliding_average m_watermark; + + buffer m_recv_buffer; +}; + +#if !defined TORRENT_DISABLE_ENCRYPTION +// Wraps a receive_buffer to provide the ability to inject +// possibly authenticated crypto beneath the bittorrent protocol. +// When authenticated crypto is in use the wrapped receive_buffer +// holds the receive state of the crypto layer while this class +// tracks the state of the bittorrent protocol. +struct crypto_receive_buffer +{ + explicit crypto_receive_buffer(receive_buffer& next) + : m_connection_buffer(next) + {} + + // explicitly disallow assignment, to silence msvc warning + crypto_receive_buffer& operator=(crypto_receive_buffer const&) = delete; + + span mutable_buffer() { return m_connection_buffer.mutable_buffer(); } + + bool packet_finished() const; + + bool crypto_packet_finished() const + { + return m_recv_pos == (std::numeric_limits::max)() + || m_connection_buffer.packet_finished(); + } + + int packet_size() const; + + int crypto_packet_size() const + { + TORRENT_ASSERT(m_recv_pos != (std::numeric_limits::max)()); + return m_connection_buffer.packet_size() - m_recv_pos; + } + + int pos() const; + + void cut(int size, int packet_size, int offset = 0); + + void crypto_cut(int size, int packet_size) + { + TORRENT_ASSERT(m_recv_pos != (std::numeric_limits::max)()); + m_connection_buffer.cut(size, m_recv_pos + packet_size, m_recv_pos); + } + + void reset(int packet_size); + void crypto_reset(int packet_size); + + int advance_pos(int bytes); + + span get() const; + + span mutable_buffer(int bytes); + +private: + + int m_recv_pos = (std::numeric_limits::max)(); + int m_packet_size = 0; + receive_buffer& m_connection_buffer; +}; +#endif // TORRENT_DISABLE_ENCRYPTION + +} // namespace aux +} // namespace libtorrent + +#endif // #ifndef TORRENT_RECEIVE_BUFFER_HPP_INCLUDED diff --git a/include/libtorrent/aux_/resolver.hpp b/include/libtorrent/aux_/resolver.hpp new file mode 100644 index 0000000..8b1e2e7 --- /dev/null +++ b/include/libtorrent/aux_/resolver.hpp @@ -0,0 +1,106 @@ +/* + +Copyright (c) 2010, 2014-2017, 2019-2020, Arvid Norberg +Copyright (c) 2017, 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_RESOLVER_HPP_INCLUDE +#define TORRENT_RESOLVER_HPP_INCLUDE + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include +#include + +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/resolver_interface.hpp" +#include "libtorrent/address.hpp" + +namespace libtorrent { +namespace aux { + +struct TORRENT_EXTRA_EXPORT resolver final : resolver_interface +{ + explicit resolver(io_context& ios); + + void async_resolve(std::string const& host, resolver_flags flags + , callback_t h) override; + + void abort() override; + + void set_cache_timeout(seconds timeout) override; + +private: + + void on_lookup(error_code const& ec, tcp::resolver::results_type ips + , std::string const& hostname); + + struct dns_cache_entry + { + time_point last_seen; + std::vector
    addresses; + }; + + struct failed_dns_cache_entry + { + time_point last_seen; + error_code error; + }; + + std::unordered_map m_cache; + std::unordered_map m_failed_cache; + io_context& m_ios; + + // all lookups in this resolver are aborted on shutdown. + tcp::resolver m_resolver; + + // lookups in this resolver are not aborted on shutdown + tcp::resolver m_critical_resolver; + + // max number of cached entries + int m_max_size; + + // timeout of cache entries + time_duration m_timeout; + + // the callbacks to call when a host resolution completes. This allows to + // attach more callbacks if the same host is looked up multiple times + std::multimap m_callbacks; +}; + +} +} + +#endif diff --git a/include/libtorrent/aux_/resolver_interface.hpp b/include/libtorrent/aux_/resolver_interface.hpp new file mode 100644 index 0000000..9e3c988 --- /dev/null +++ b/include/libtorrent/aux_/resolver_interface.hpp @@ -0,0 +1,79 @@ +/* + +Copyright (c) 2010, 2014-2018, 2020, Arvid Norberg +Copyright (c) 2017, 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_RESOLVER_INTERFACE_HPP_INCLUDE +#define TORRENT_RESOLVER_INTERFACE_HPP_INCLUDE + +#include +#include + +#include "libtorrent/error_code.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/flags.hpp" + +namespace libtorrent { +namespace aux { + +// hidden +using resolver_flags = flags::bitfield_flag; + +struct TORRENT_EXTRA_EXPORT resolver_interface +{ + using callback_t = std::function const&)>; + + // this flag will make async_resolve() only use the cache and fail if we + // don't have a cache entry, regardless of how old it is. This is useful + // when completing the lookup quickly is more important than accuracy, + // like on shutdown + static constexpr resolver_flags cache_only = 0_bit; + + // set this flag for lookups that are not critical during shutdown. i.e. + // for looking up tracker names _except_ when stopping a tracker. + static constexpr resolver_flags abort_on_shutdown = 1_bit; + + virtual void async_resolve(std::string const& host, resolver_flags flags + , callback_t h) = 0; + + virtual void abort() = 0; + + virtual void set_cache_timeout(seconds timeout) = 0; + +protected: + ~resolver_interface() {} +}; + +} +} + +#endif diff --git a/include/libtorrent/aux_/route.h b/include/libtorrent/aux_/route.h new file mode 100644 index 0000000..b2bcea3 --- /dev/null +++ b/include/libtorrent/aux_/route.h @@ -0,0 +1,478 @@ +/* + * Copyright (c) 2000-2008 Apple Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ +/* + * Copyright (c) 1980, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)route.h 8.3 (Berkeley) 4/19/94 + * $FreeBSD: src/sys/net/route.h,v 1.36.2.1 2000/08/16 06:14:23 jayanth Exp $ + */ + +#ifndef _NET_ROUTE_H_ +#define _NET_ROUTE_H_ +#include +#include +#include +#include + +/* + * Kernel resident routing tables. + * + * The routing tables are initialized when interface addresses + * are set by making entries for all directly connected interfaces. + */ + +/* + * A route consists of a destination address and a reference + * to a routing entry. These are often held by protocols + * in their control blocks, e.g. inpcb. + */ +#ifdef PRIVATE +struct rtentry; +struct route { + /* + * N.B: struct route must begin with ro_rt and ro_flags + * because the code does some casts of a 'struct route_in6 *' + * to a 'struct route *'. + */ + struct rtentry *ro_rt; + uint32_t ro_flags; /* route flags (see below) */ + struct sockaddr ro_dst; +}; + +#define ROF_SRCIF_SELECTED 0x1 /* source interface was selected */ + +#else +struct route; +#endif /* PRIVATE */ + +/* + * These numbers are used by reliable protocols for determining + * retransmission behavior and are included in the routing structure. + */ +struct rt_metrics { + u_int32_t rmx_locks; /* Kernel must leave these values alone */ + u_int32_t rmx_mtu; /* MTU for this path */ + u_int32_t rmx_hopcount; /* max hops expected */ + int32_t rmx_expire; /* lifetime for route, e.g. redirect */ + u_int32_t rmx_recvpipe; /* inbound delay-bandwidth product */ + u_int32_t rmx_sendpipe; /* outbound delay-bandwidth product */ + u_int32_t rmx_ssthresh; /* outbound gateway buffer limit */ + u_int32_t rmx_rtt; /* estimated round trip time */ + u_int32_t rmx_rttvar; /* estimated rtt variance */ + u_int32_t rmx_pksent; /* packets sent using this route */ + u_int32_t rmx_filler[4]; /* will be used for T/TCP later */ +}; + +/* + * rmx_rtt and rmx_rttvar are stored as microseconds; + */ +#define RTM_RTTUNIT 1000000 /* units for rtt, rttvar, as units per sec */ + +/* + * We distinguish between routes to hosts and routes to networks, + * preferring the former if available. For each route we infer + * the interface to use from the gateway address supplied when + * the route was entered. Routes that forward packets through + * gateways are marked so that the output routines know to address the + * gateway rather than the ultimate destination. + */ +#ifdef KERNEL_PRIVATE +#include +#ifndef RNF_NORMAL +#include +#endif +/* + * Kernel routing entry structure (private). + */ +struct rtentry { + struct radix_node rt_nodes[2]; /* tree glue, and other values */ +#define rt_key(r) ((struct sockaddr *)((r)->rt_nodes->rn_key)) +#define rt_mask(r) ((struct sockaddr *)((r)->rt_nodes->rn_mask)) + struct sockaddr *rt_gateway; /* value */ + int32_t rt_refcnt; /* # held references */ + uint32_t rt_flags; /* up/down?, host/net */ + struct ifnet *rt_ifp; /* the answer: interface to use */ + struct ifaddr *rt_ifa; /* the answer: interface addr to use */ + struct sockaddr *rt_genmask; /* for generation of cloned routes */ + void *rt_llinfo; /* pointer to link level info cache */ + void (*rt_llinfo_free)(void *); /* link level info free function */ + struct rt_metrics rt_rmx; /* metrics used by rx'ing protocols */ + struct rtentry *rt_gwroute; /* implied entry for gatewayed routes */ + struct rtentry *rt_parent; /* cloning parent of this route */ + uint32_t generation_id; /* route generation id */ + /* + * See bsd/net/route.c for synchronization notes. + */ + decl_lck_mtx_data(, rt_lock); /* lock for routing entry */ +}; +#endif /* KERNEL_PRIVATE */ + +#ifdef KERNEL_PRIVATE +#define rt_use rt_rmx.rmx_pksent +#endif /* KERNEL_PRIVATE */ + +#define RTF_UP 0x1 /* route usable */ +#define RTF_GATEWAY 0x2 /* destination is a gateway */ +#define RTF_HOST 0x4 /* host entry (net otherwise) */ +#define RTF_REJECT 0x8 /* host or net unreachable */ +#define RTF_DYNAMIC 0x10 /* created dynamically (by redirect) */ +#define RTF_MODIFIED 0x20 /* modified dynamically (by redirect) */ +#define RTF_DONE 0x40 /* message confirmed */ +#define RTF_DELCLONE 0x80 /* delete cloned route */ +#define RTF_CLONING 0x100 /* generate new routes on use */ +#define RTF_XRESOLVE 0x200 /* external daemon resolves name */ +#define RTF_LLINFO 0x400 /* generated by link layer (e.g. ARP) */ +#define RTF_STATIC 0x800 /* manually added */ +#define RTF_BLACKHOLE 0x1000 /* just discard pkts (during updates) */ +#define RTF_PROTO2 0x4000 /* protocol specific routing flag */ +#define RTF_PROTO1 0x8000 /* protocol specific routing flag */ + +#define RTF_PRCLONING 0x10000 /* protocol requires cloning */ +#define RTF_WASCLONED 0x20000 /* route generated through cloning */ +#define RTF_PROTO3 0x40000 /* protocol specific routing flag */ + /* 0x80000 unused */ +#define RTF_PINNED 0x100000 /* future use */ +#define RTF_LOCAL 0x200000 /* route represents a local address */ +#define RTF_BROADCAST 0x400000 /* route represents a bcast address */ +#define RTF_MULTICAST 0x800000 /* route represents a mcast address */ +#define RTF_IFSCOPE 0x1000000 /* has valid interface scope */ +#define RTF_CONDEMNED 0x2000000 /* defunct; no longer modifiable */ + /* 0x4000000 and up unassigned */ + +/* + * Routing statistics. + */ +struct rtstat { + short rts_badredirect; /* bogus redirect calls */ + short rts_dynamic; /* routes created by redirects */ + short rts_newgateway; /* routes modified by redirects */ + short rts_unreach; /* lookups which failed */ + short rts_wildcard; /* lookups satisfied by a wildcard */ +}; + +/* + * Structures for routing messages. + */ +struct rt_msghdr { + u_short rtm_msglen; /* to skip over non-understood messages */ + u_char rtm_version; /* future binary compatibility */ + u_char rtm_type; /* message type */ + u_short rtm_index; /* index for associated ifp */ + int rtm_flags; /* flags, incl. kern & message, e.g. DONE */ + int rtm_addrs; /* bitmask identifying sockaddrs in msg */ + pid_t rtm_pid; /* identify sender */ + int rtm_seq; /* for sender to identify action */ + int rtm_errno; /* why failed */ + int rtm_use; /* from rtentry */ + u_int32_t rtm_inits; /* which metrics we are initializing */ + struct rt_metrics rtm_rmx; /* metrics themselves */ +}; + +struct rt_msghdr2 { + u_short rtm_msglen; /* to skip over non-understood messages */ + u_char rtm_version; /* future binary compatibility */ + u_char rtm_type; /* message type */ + u_short rtm_index; /* index for associated ifp */ + int rtm_flags; /* flags, incl. kern & message, e.g. DONE */ + int rtm_addrs; /* bitmask identifying sockaddrs in msg */ + int32_t rtm_refcnt; /* reference count */ + int rtm_parentflags; /* flags of the parent route */ + int rtm_reserved; /* reserved field set to 0 */ + int rtm_use; /* from rtentry */ + u_int32_t rtm_inits; /* which metrics we are initializing */ + struct rt_metrics rtm_rmx; /* metrics themselves */ +}; + + +#define RTM_VERSION 5 /* Up the ante and ignore older versions */ + +/* + * Message types. + */ +#define RTM_ADD 0x1 /* Add Route */ +#define RTM_DELETE 0x2 /* Delete Route */ +#define RTM_CHANGE 0x3 /* Change Metrics or flags */ +#define RTM_GET 0x4 /* Report Metrics */ +#define RTM_LOSING 0x5 /* Kernel Suspects Partitioning */ +#define RTM_REDIRECT 0x6 /* Told to use different route */ +#define RTM_MISS 0x7 /* Lookup failed on this address */ +#define RTM_LOCK 0x8 /* fix specified metrics */ +#define RTM_OLDADD 0x9 /* caused by SIOCADDRT */ +#define RTM_OLDDEL 0xa /* caused by SIOCDELRT */ +#define RTM_RESOLVE 0xb /* req to resolve dst to LL addr */ +#define RTM_NEWADDR 0xc /* address being added to iface */ +#define RTM_DELADDR 0xd /* address being removed from iface */ +#define RTM_IFINFO 0xe /* iface going up/down etc. */ +#define RTM_NEWMADDR 0xf /* mcast group membership being added to if */ +#define RTM_DELMADDR 0x10 /* mcast group membership being deleted */ +#ifdef PRIVATE +#define RTM_GET_SILENT 0x11 +#endif /* PRIVATE */ +#define RTM_IFINFO2 0x12 /* */ +#define RTM_NEWMADDR2 0x13 /* */ +#define RTM_GET2 0x14 /* */ + +/* + * Bitmask values for rtm_inits and rmx_locks. + */ +#define RTV_MTU 0x1 /* init or lock _mtu */ +#define RTV_HOPCOUNT 0x2 /* init or lock _hopcount */ +#define RTV_EXPIRE 0x4 /* init or lock _expire */ +#define RTV_RPIPE 0x8 /* init or lock _recvpipe */ +#define RTV_SPIPE 0x10 /* init or lock _sendpipe */ +#define RTV_SSTHRESH 0x20 /* init or lock _ssthresh */ +#define RTV_RTT 0x40 /* init or lock _rtt */ +#define RTV_RTTVAR 0x80 /* init or lock _rttvar */ + +/* + * Bitmask values for rtm_addrs. + */ +#define RTA_DST 0x1 /* destination sockaddr present */ +#define RTA_GATEWAY 0x2 /* gateway sockaddr present */ +#define RTA_NETMASK 0x4 /* netmask sockaddr present */ +#define RTA_GENMASK 0x8 /* cloning mask sockaddr present */ +#define RTA_IFP 0x10 /* interface name sockaddr present */ +#define RTA_IFA 0x20 /* interface addr sockaddr present */ +#define RTA_AUTHOR 0x40 /* sockaddr for author of redirect */ +#define RTA_BRD 0x80 /* for NEWADDR, broadcast or p-p dest addr */ + +/* + * Index offsets for sockaddr array for alternate internal encoding. + */ +#define RTAX_DST 0 /* destination sockaddr present */ +#define RTAX_GATEWAY 1 /* gateway sockaddr present */ +#define RTAX_NETMASK 2 /* netmask sockaddr present */ +#define RTAX_GENMASK 3 /* cloning mask sockaddr present */ +#define RTAX_IFP 4 /* interface name sockaddr present */ +#define RTAX_IFA 5 /* interface addr sockaddr present */ +#define RTAX_AUTHOR 6 /* sockaddr for author of redirect */ +#define RTAX_BRD 7 /* for NEWADDR, broadcast or p-p dest addr */ +#define RTAX_MAX 8 /* size of array to allocate */ + +struct rt_addrinfo { + int rti_addrs; + struct sockaddr *rti_info[RTAX_MAX]; +}; + +struct route_cb { + int ip_count; + int ip6_count; + int ipx_count; + int ns_count; + int iso_count; + int any_count; +}; + +#ifdef PRIVATE +/* + * For scoped routing; a zero interface scope value means nil/no scope. + */ +#define IFSCOPE_NONE 0 +#endif /* PRIVATE */ + +#ifdef KERNEL_PRIVATE +/* + * Generic call trace used by some subsystems (e.g. route, ifaddr) + */ +#define CTRACE_STACK_SIZE 8 /* depth of stack trace */ +#define CTRACE_HIST_SIZE 4 /* refcnt history size */ +typedef struct ctrace { + void *th; /* thread ptr */ + void *pc[CTRACE_STACK_SIZE]; /* PC stack trace */ +} ctrace_t; + +extern void ctrace_record(ctrace_t *); + +#define RT_LOCK_ASSERT_HELD(_rt) \ + lck_mtx_assert(&(_rt)->rt_lock, LCK_MTX_ASSERT_OWNED) + +#define RT_LOCK_ASSERT_NOTHELD(_rt) \ + lck_mtx_assert(&(_rt)->rt_lock, LCK_MTX_ASSERT_NOTOWNED) + +#define RT_LOCK(_rt) do { \ + if (!rte_debug) \ + lck_mtx_lock(&(_rt)->rt_lock); \ + else \ + rt_lock(_rt, FALSE); \ +} while (0) + +#define RT_LOCK_SPIN(_rt) do { \ + if (!rte_debug) \ + lck_mtx_lock_spin(&(_rt)->rt_lock); \ + else \ + rt_lock(_rt, TRUE); \ +} while (0) + +#define RT_CONVERT_LOCK(_rt) do { \ + RT_LOCK_ASSERT_HELD(_rt); \ + lck_mtx_convert_spin(&(_rt)->rt_lock); \ +} while (0) + +#define RT_UNLOCK(_rt) do { \ + if (!rte_debug) \ + lck_mtx_unlock(&(_rt)->rt_lock); \ + else \ + rt_unlock(_rt); \ +} while (0) + +#define RT_ADDREF_LOCKED(_rt) do { \ + if (!rte_debug) { \ + RT_LOCK_ASSERT_HELD(_rt); \ + if (++(_rt)->rt_refcnt == 0) \ + panic("RT_ADDREF(%p) bad refcnt\n", _rt); \ + } else { \ + rtref(_rt); \ + } \ +} while (0) + +/* + * Spin variant mutex is used here; caller is responsible for + * converting any previously-held similar lock to full mutex. + */ +#define RT_ADDREF(_rt) do { \ + RT_LOCK_SPIN(_rt); \ + RT_ADDREF_LOCKED(_rt); \ + RT_UNLOCK(_rt); \ +} while (0) + +#define RT_REMREF_LOCKED(_rt) do { \ + if (!rte_debug) { \ + RT_LOCK_ASSERT_HELD(_rt); \ + if ((_rt)->rt_refcnt == 0) \ + panic("RT_REMREF(%p) bad refcnt\n", _rt); \ + --(_rt)->rt_refcnt; \ + } else { \ + (void) rtunref(_rt); \ + } \ +} while (0) + +/* + * Spin variant mutex is used here; caller is responsible for + * converting any previously-held similar lock to full mutex. + */ +#define RT_REMREF(_rt) do { \ + RT_LOCK_SPIN(_rt); \ + RT_REMREF_LOCKED(_rt); \ + RT_UNLOCK(_rt); \ +} while (0) + +#define RTFREE(_rt) rtfree(_rt) +#define RTFREE_LOCKED(_rt) rtfree_locked(_rt) + +extern struct route_cb route_cb; +extern struct radix_node_head *rt_tables[AF_MAX+1]; +__private_extern__ lck_mtx_t *rnh_lock; +__private_extern__ int use_routegenid; +__private_extern__ uint32_t route_generation; +__private_extern__ int rttrash; +__private_extern__ unsigned int rte_debug; + +struct ifmultiaddr; +struct proc; + +extern void route_init(void) __attribute__((section("__TEXT, initcode"))); +extern void routegenid_update(void); +extern void rt_ifmsg(struct ifnet *); +extern void rt_missmsg(int, struct rt_addrinfo *, int, int); +extern void rt_newaddrmsg(int, struct ifaddr *, int, struct rtentry *); +extern void rt_newmaddrmsg(int, struct ifmultiaddr *); +extern int rt_setgate(struct rtentry *, struct sockaddr *, struct sockaddr *); +extern void set_primary_ifscope(unsigned int); +extern unsigned int get_primary_ifscope(void); +extern boolean_t rt_inet_default(struct rtentry *, struct sockaddr *); +extern struct rtentry *rt_lookup(boolean_t, struct sockaddr *, + struct sockaddr *, struct radix_node_head *, unsigned int); +extern void rtalloc(struct route *); +extern void rtalloc_ign(struct route *, uint32_t); +extern void rtalloc_ign_locked(struct route *, uint32_t); +extern void rtalloc_scoped_ign(struct route *, uint32_t, unsigned int); +extern void rtalloc_scoped_ign_locked(struct route *, uint32_t, unsigned int); +extern struct rtentry *rtalloc1(struct sockaddr *, int, uint32_t); +extern struct rtentry *rtalloc1_locked(struct sockaddr *, int, uint32_t); +extern struct rtentry *rtalloc1_scoped(struct sockaddr *, int, uint32_t, + unsigned int); +extern struct rtentry *rtalloc1_scoped_locked(struct sockaddr *, int, + uint32_t, unsigned int); +extern void rtfree(struct rtentry *); +extern void rtfree_locked(struct rtentry *); +extern void rtref(struct rtentry *); +/* + * rtunref will decrement the refcount, rtfree will decrement and free if + * the refcount has reached zero and the route is not up. + * Unless you have good reason to do otherwise, use rtfree. + */ +extern int rtunref(struct rtentry *); +extern void rtsetifa(struct rtentry *, struct ifaddr *); +extern int rtinit(struct ifaddr *, int, int); +extern int rtinit_locked(struct ifaddr *, int, int); +extern int rtioctl(unsigned long, caddr_t, struct proc *); +extern void rtredirect(struct ifnet *, struct sockaddr *, struct sockaddr *, + struct sockaddr *, int, struct sockaddr *, struct rtentry **); +extern int rtrequest(int, struct sockaddr *, + struct sockaddr *, struct sockaddr *, int, struct rtentry **); +extern int rtrequest_locked(int, struct sockaddr *, + struct sockaddr *, struct sockaddr *, int, struct rtentry **); +extern int rtrequest_scoped_locked(int, struct sockaddr *, struct sockaddr *, + struct sockaddr *, int, struct rtentry **, unsigned int); +extern unsigned int sa_get_ifscope(struct sockaddr *); +extern void rt_lock(struct rtentry *, boolean_t); +extern void rt_unlock(struct rtentry *); +extern struct sockaddr *rtm_scrub_ifscope(int, struct sockaddr *, + struct sockaddr *, struct sockaddr_storage *); +#endif /* KERNEL_PRIVATE */ + +#endif diff --git a/include/libtorrent/aux_/scope_end.hpp b/include/libtorrent/aux_/scope_end.hpp new file mode 100644 index 0000000..28b8b78 --- /dev/null +++ b/include/libtorrent/aux_/scope_end.hpp @@ -0,0 +1,64 @@ +/* + +Copyright (c) 2016-2017, 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SCOPE_END_HPP_INCLUDED +#define TORRENT_SCOPE_END_HPP_INCLUDED + +#include + +namespace libtorrent { namespace aux { + + template + struct scope_end_impl + { + explicit scope_end_impl(Fun f) : m_fun(std::move(f)) {} + ~scope_end_impl() { if (m_armed) m_fun(); } + + // movable + scope_end_impl(scope_end_impl&&) noexcept = default; + scope_end_impl& operator=(scope_end_impl&&) & noexcept = default; + + // non-copyable + scope_end_impl(scope_end_impl const&) = delete; + scope_end_impl& operator=(scope_end_impl const&) = delete; + void disarm() { m_armed = false; } + private: + Fun m_fun; + bool m_armed = true; + }; + + template + scope_end_impl scope_end(Fun f) { return scope_end_impl(std::move(f)); } +}} + +#endif + diff --git a/include/libtorrent/aux_/session_call.hpp b/include/libtorrent/aux_/session_call.hpp new file mode 100644 index 0000000..10007d8 --- /dev/null +++ b/include/libtorrent/aux_/session_call.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2014, Arvid Norberg, Steven Siloti +Copyright (c) 2014-2016, 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_CALL_HPP_INCLUDED +#define TORRENT_SESSION_CALL_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/session_impl.hpp" + +#include + +namespace libtorrent { namespace aux { + +void blocking_call(); +void dump_call_profile(); + +void torrent_wait(bool& done, aux::session_impl& ses); + +} } // namespace aux namespace libtorrent + +#endif // TORRENT_SESSION_CALL_HPP_INCLUDED + diff --git a/include/libtorrent/aux_/session_impl.hpp b/include/libtorrent/aux_/session_impl.hpp new file mode 100644 index 0000000..5bbad42 --- /dev/null +++ b/include/libtorrent/aux_/session_impl.hpp @@ -0,0 +1,1398 @@ +/* + +Copyright (c) 2006-2022, Arvid Norberg +Copyright (c) 2014-2019, Steven Siloti +Copyright (c) 2015-2020, Alden Torres +Copyright (c) 2015, Thomas +Copyright (c) 2016-2017, Pavel Pimenov +Copyright (c) 2020, Paul-Louis Ageneau +Copyright (c) 2023, Joris Carrier +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_IMPL_HPP_INCLUDED +#define TORRENT_SESSION_IMPL_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/aux_/session_udp_sockets.hpp" +#include "libtorrent/aux_/socket_type.hpp" +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/torrent_peer_allocator.hpp" +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/aux_/allocating_handler.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/aux_/torrent_list.hpp" +#include "libtorrent/session_params.hpp" // for disk_io_constructor_type + +#ifdef TORRENT_SSL_PEERS +#include "libtorrent/ssl_stream.hpp" +#endif + +#include "libtorrent/session.hpp" // for user_load_function_t +#include "libtorrent/ip_voter.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/aux_/ip_notifier.hpp" +#include "libtorrent/session_status.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/aux_/bandwidth_manager.hpp" +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/alert_manager.hpp" // for alert_manager +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/socket_io.hpp" // for print_address +#include "libtorrent/address.hpp" +#include "libtorrent/aux_/utp_socket_manager.hpp" +#include "libtorrent/bloom_filter.hpp" +#include "libtorrent/peer_class.hpp" +#include "libtorrent/peer_class_type_filter.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" +#include "libtorrent/kademlia/dht_state.hpp" +#include "libtorrent/kademlia/announce_flags.hpp" +#include "libtorrent/aux_/resolver.hpp" +#include "libtorrent/aux_/invariant_check.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/aux_/portmap.hpp" +#include "libtorrent/aux_/lsd.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/span.hpp" + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/session_settings.hpp" +#endif + +#if TORRENT_COMPLETE_TYPES_REQUIRED +#include "libtorrent/peer_connection.hpp" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include // for va_start, va_end +#include + +namespace libtorrent { + +TORRENT_VERSION_NAMESPACE_3 + struct plugin; +TORRENT_VERSION_NAMESPACE_3_END + + struct upnp; + struct natpmp; + struct lsd; + struct torrent; + struct alert; + struct torrent_handle; + +namespace dht { + + struct dht_tracker; + class item; + +} + +namespace aux { + + struct session_impl; + struct session_settings; + +#ifndef TORRENT_DISABLE_LOGGING + struct tracker_logger; +#endif + + struct unique_ptr_less + { + using is_transparent = std::true_type; + template + bool operator()(std::unique_ptr const& lhs, std::unique_ptr const& rhs) const + { return lhs < rhs; } + template + bool operator()(std::unique_ptr const& lhs, T* rhs) const + { return lhs.get() < rhs; } + template + bool operator()(T* lhs, std::unique_ptr const& rhs) const + { return lhs < rhs.get(); } + }; + + using listen_socket_flags_t = flags::bitfield_flag; + + struct listen_port_mapping + { + port_mapping_t mapping = port_mapping_t{-1}; + int port = 0; + }; + + struct TORRENT_EXTRA_EXPORT listen_socket_t : utp_socket_interface + { + // we accept incoming connections on this interface + static constexpr listen_socket_flags_t accept_incoming = 0_bit; + + // this interface was specified to be just the local network. If this flag + // is not set, this interface is assumed to have a path to the internet + // (i.e. have a gateway configured) + static constexpr listen_socket_flags_t local_network = 1_bit; + + // this interface was expanded from the user requesting to + // listen on an unspecified address (either IPv4 or IPv6) + static constexpr listen_socket_flags_t was_expanded = 2_bit; + + // there's a proxy configured, and this is the only one interface + // representing that one proxy + static constexpr listen_socket_flags_t proxy = 3_bit; + + listen_socket_t() = default; + + // listen_socket_t should not be copied or moved because + // references to it are held by the DHT and tracker announce + // code. That code expects a listen_socket_t to always refer + // to the same socket. It would be easy to accidentally + // invalidate that assumption if copying or moving were allowed. + listen_socket_t(listen_socket_t const&) = delete; + listen_socket_t(listen_socket_t&&) = delete; + listen_socket_t& operator=(listen_socket_t const&) = delete; + listen_socket_t& operator=(listen_socket_t&&) = delete; + + udp::endpoint get_local_endpoint() override + { + error_code ec; + if (udp_sock) return udp_sock->sock.local_endpoint(ec); + return {local_endpoint.address(), local_endpoint.port()}; + } + + // returns true if this listen socket/interface can reach and be reached + // by the given address. This is useful to know whether it should be + // annoucned to a tracker (given the tracker's IP) or whether it should + // have a SOCKS5 UDP tunnel set up (given the IP of the socks proxy) + bool can_route(address const&) const; + + // this may be empty but can be set + // to the WAN IP address of a NAT router + ip_voter external_address; + + // this is a cached local endpoint for the listen TCP socket + tcp::endpoint local_endpoint; + + address netmask; + + // the name of the device the socket is bound to, may be empty + // if the socket is not bound to a device + std::string device; + + // this is the port that was originally specified to listen on it may be + // different from local_endpoint.port() if we had to retry binding with a + // higher port + int original_port = 0; + + // tcp_external_port and udp_external_port return the port which + // should be published to peers/trackers for this socket + // If there are active NAT mappings the return value will be + // the external port returned by the NAT router, otherwise the + // local listen port is returned + int tcp_external_port() + { + for (auto const& m : tcp_port_mapping) + { + if (m.port != 0) return m.port; + } + return local_endpoint.port(); + } + + int udp_external_port() + { + for (auto const& m : udp_port_mapping) + { + if (m.port != 0) return m.port; + } + if (udp_sock) return udp_sock->sock.local_port(); + return 0; + } + + // 0 is natpmp 1 is upnp + // the order of these arrays determines the priority in + // which their ports will be announced to peers + aux::array tcp_port_mapping; + aux::array udp_port_mapping; + + // indicates whether this is an SSL listen socket or not + transport ssl = transport::plaintext; + + listen_socket_flags_t flags = accept_incoming; + + // the actual sockets (TCP listen socket and UDP socket) + // An entry does not necessarily have a UDP or TCP socket. One of these + // pointers may be nullptr! + // These must be shared_ptr to avoid a dangling reference if an + // incoming packet is in the event queue when the socket is erased + // TODO: make these direct members and generate shared_ptrs to them + // which alias the listen_socket_t shared_ptr + std::shared_ptr sock; + std::shared_ptr udp_sock; + + // since udp packets are expected to be dispatched frequently, this saves + // time on handler allocation every time we read again. + aux::handler_storage udp_handler_storage; + + std::shared_ptr natpmp_mapper; + std::shared_ptr upnp_mapper; + + std::shared_ptr lsd; + + // set to true when we receive an incoming connection from this listen + // socket + bool incoming_connection = false; + }; + + struct TORRENT_EXTRA_EXPORT listen_endpoint_t + { + listen_endpoint_t(address const& adr, int p, std::string dev, transport s + , listen_socket_flags_t f, address const& nmask = address{}) + : addr(adr), netmask(nmask), port(p), device(std::move(dev)), ssl(s), flags(f) {} + + bool operator==(listen_endpoint_t const& o) const + { + return addr == o.addr + && port == o.port + && device == o.device + && ssl == o.ssl + && flags == o.flags; + } + + address addr; + // if this listen endpoint/interface doesn't have a gateway, we cannot + // route outside of our network, this netmask defines the range of our + // local network + address netmask; + int port; + std::string device; + transport ssl; + listen_socket_flags_t flags; + }; + + // partitions sockets based on whether they match one of the given endpoints + // all matched sockets are ordered before unmatched sockets + // matched endpoints are removed from the vector + // returns an iterator to the first unmatched socket + TORRENT_EXTRA_EXPORT std::vector>::iterator + partition_listen_sockets( + std::vector& eps + , std::vector>& sockets); + + TORRENT_EXTRA_EXPORT void interface_to_endpoints( + listen_interface_t const& iface + , listen_socket_flags_t flags + , span const ifs + , std::vector& eps); + + // expand [::] to all IPv6 interfaces for BEP 45 compliance + TORRENT_EXTRA_EXPORT void expand_unspecified_address( + span ifs + , span routes + , std::vector& eps); + + void apply_deprecated_dht_settings(settings_pack& sett, bdecode_node const& s); + + TORRENT_EXTRA_EXPORT void expand_devices(span + , std::vector& eps); + + // this is the link between the main thread and the + // thread started to run the main downloader loop + struct TORRENT_EXTRA_EXPORT session_impl final + : session_interface + , dht::dht_observer + , aux::portmap_callback + , aux::lsd_callback + , single_threaded + , aux::error_handler_interface + , std::enable_shared_from_this + { + // plugin feature-index key map + enum + { + plugins_all_idx = 0, // to store all plugins + plugins_optimistic_unchoke_idx = 1, // optimistic_unchoke_feature + plugins_tick_idx = 2, // tick_feature + plugins_dht_request_idx = 3, // dht_request_feature + plugins_unknown_torrent_idx = 4 // unknown_torrent_feature + }; + + template + void wrap(Fun f, Args&&... a); + +#if TORRENT_USE_INVARIANT_CHECKS + friend struct libtorrent::invariant_access; +#endif + using connection_map = std::set>; + + session_impl(io_context&, settings_pack const&, disk_io_constructor_type, session_flags_t); + ~session_impl() override; + + session_impl(session_impl const&) = delete; + session_impl& operator=(session_impl const&) = delete; + + void start_session(); + + void init_peer_class_filter(bool unlimited_local); + + void call_abort() + { + auto ptr = shared_from_this(); + dispatch(m_io_context, make_handler([ptr] { ptr->abort(); } + , m_abort_handler_storage, *this)); + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + using ext_function_t + = std::function(torrent_handle const&, client_data_t)>; + + struct session_plugin_wrapper : plugin + { + explicit session_plugin_wrapper(ext_function_t f) : m_f(std::move(f)) {} + + std::shared_ptr new_torrent(torrent_handle const& t, client_data_t const user) override + { return m_f(t, user); } + ext_function_t m_f; + }; + + void add_extension(std::function( + torrent_handle const&, client_data_t)> ext); + void add_ses_extension(std::shared_ptr ext); +#endif +#if TORRENT_USE_ASSERTS + bool has_peer(peer_connection const* p) const override; + bool any_torrent_has_peer(peer_connection const* p) const override; + bool is_single_thread() const override { return single_threaded::is_single_thread(); } + bool is_posting_torrent_updates() const override { return m_posting_torrent_updates; } + // this is set while the session is building the + // torrent status update message + bool m_posting_torrent_updates = false; + bool verify_queue_position(torrent const* t, queue_position_t pos) override; +#endif + + void on_exception(std::exception const& e) override; + void on_error(error_code const& ec) override; + + void on_ip_change(error_code const& ec); + void reopen_listen_sockets(bool map_ports = true); + void reopen_outgoing_sockets(); + void reopen_network_sockets(reopen_network_flags_t options); + + torrent_peer_allocator_interface& get_peer_allocator() override + { return m_peer_allocator; } + + io_context& get_context() override { return m_io_context; } + resolver_interface& get_resolver() override { return m_host_resolver; } + + aux::vector& torrent_list(torrent_list_index_t i) override + { + TORRENT_ASSERT(i >= torrent_list_index_t{}); + TORRENT_ASSERT(i < m_torrent_lists.end_index()); + return m_torrent_lists[i]; + } + + // prioritize this torrent to be allocated some connection + // attempts, because this torrent needs more peers. + // this is typically done when a torrent starts out and + // need the initial push to connect peers + void prioritize_connections(std::weak_ptr t) override; + + void async_accept(std::shared_ptr const&, transport); + void on_accept_connection(true_tcp_socket s, error_code const& + , std::weak_ptr, transport); + + void incoming_connection(socket_type); + + std::weak_ptr find_torrent(info_hash_t const&) const override; +#if TORRENT_ABI_VERSION == 1 + //deprecated in 1.2 + + TORRENT_DEPRECATED + void set_load_function(user_load_function_t fun) + { m_user_load_torrent = fun; } +#endif +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + std::vector> find_collection( + std::string const& collection) const override; +#endif + std::weak_ptr find_disconnect_candidate_torrent() const override; + int num_torrents() const override { return int(m_torrents.size()); } + + void insert_torrent(info_hash_t const& ih, std::shared_ptr const& t) override; + + // when downloading metadata for a torrent, we may learn that it is a + // hybrid (supporting v1 and v2), which means it has two info-hashes. + // This function updates the index for the torrent, so we can find it + // using both the v1 and v2 info-hashes. + void update_torrent_info_hash(std::shared_ptr const& t + , info_hash_t const& old_ih) override; + + std::shared_ptr delay_load_torrent(info_hash_t const& info_hash + , peer_connection* pc) override; + void set_queue_position(torrent*, queue_position_t) override; + + void close_connection(peer_connection* p) noexcept override; + +#if !defined TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + void validate_setting(int const int_name, int const min, int const max); + void validate_settings(); +#endif + void apply_settings_pack(std::shared_ptr pack) override; + void apply_settings_pack_impl(settings_pack const& pack); + session_settings const& settings() const override { return m_settings; } + settings_pack get_settings() const; + +#ifndef TORRENT_DISABLE_DHT + dht::dht_tracker* dht() override { return m_dht.get(); } + bool announce_dht() const override { return !m_listen_sockets.empty(); } + + void add_dht_node_name(std::pair const& node); + void add_dht_node(udp::endpoint const& n) override; + void add_dht_router(std::pair const& node); +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + void set_dht_settings(dht::dht_settings const& s); + dht::dht_settings get_dht_settings() const; +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + + // you must give up ownership of the dht state + void set_dht_state(dht::dht_state&& state); + + void set_dht_storage(dht::dht_storage_constructor_type sc); + void start_dht(); + void stop_dht(); + bool has_dht() const override; + + // this is called for torrents when they are started + // it will prioritize them for announcing to + // the DHT, to get the initial peers quickly + void prioritize_dht(std::weak_ptr t) override; + + void get_immutable_callback(sha1_hash target + , dht::item const& i); + void get_mutable_callback(dht::item const& i, bool); + + void dht_get_immutable_item(sha1_hash const& target); + + void dht_get_mutable_item(std::array key + , std::string salt = std::string()); + + void dht_put_immutable_item(entry const& data, sha1_hash target); + + void dht_put_mutable_item(std::array key + , std::function& + , std::int64_t&, std::string const&)> cb + , std::string salt = std::string()); + + void dht_get_peers(sha1_hash const& info_hash); + void dht_announce(sha1_hash const& info_hash, int port = 0, dht::announce_flags_t flags = {}); + + void dht_live_nodes(sha1_hash const& nid); + void dht_sample_infohashes(udp::endpoint const& ep, sha1_hash const& target); + + void dht_direct_request(udp::endpoint const& ep, entry& e, client_data_t userdata); + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + entry dht_state() const; + TORRENT_DEPRECATED + void start_dht_deprecated(entry const& startup_state); +#endif + void on_dht_announce(error_code const& e); + void on_dht_name_lookup(error_code const& e + , std::vector
    const& addresses, int port); + void on_dht_router_name_lookup(error_code const& e + , std::vector
    const& addresses, int port); +#endif + +#if !defined TORRENT_DISABLE_ENCRYPTION + torrent const* find_encrypted_torrent( + sha1_hash const& info_hash, sha1_hash const& xor_mask) override; +#endif + + void on_lsd_announce(error_code const& e); + + // called when a port mapping is successful, or a router returns + // a failure to map a port + void on_port_mapping(port_mapping_t mapping, address const& ip, int port + , portmap_protocol proto, error_code const& ec + , portmap_transport transport, listen_socket_handle const&) override; + + bool is_aborted() const override { return m_abort; } + bool is_paused() const { return m_paused; } + + void pause(); + void resume(); + + void set_ip_filter(std::shared_ptr f); + ip_filter const& get_ip_filter(); + + void set_port_filter(port_filter const& f); + port_filter const& get_port_filter() const override; + void ban_ip(address addr) override; + + void queue_tracker_request(tracker_request req + , std::weak_ptr c) override; + + // ==== peer class operations ==== + + // implements session_interface + void set_peer_classes(peer_class_set* s, address const& a, socket_type_t st) override; + peer_class_pool const& peer_classes() const override { return m_classes; } + peer_class_pool& peer_classes() override { return m_classes; } + bool ignore_unchoke_slots_set(peer_class_set const& set) const override; + int copy_pertinent_channels(peer_class_set const& set + , int channel, bandwidth_channel** dst, int m) override; + int use_quota_overhead(peer_class_set& set, int amount_down, int amount_up) override; + bool use_quota_overhead(bandwidth_channel* ch, int amount); + + peer_class_t create_peer_class(char const* name); + void delete_peer_class(peer_class_t cid); + void set_peer_class_filter(ip_filter const& f); + ip_filter const& get_peer_class_filter() const; + + void set_peer_class_type_filter(peer_class_type_filter f); + peer_class_type_filter get_peer_class_type_filter(); + + peer_class_info get_peer_class(peer_class_t cid) const; + void set_peer_class(peer_class_t cid, peer_class_info const& pci); + + bool is_listening() const; + +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extensions_to_torrent( + std::shared_ptr const& torrent_ptr, client_data_t userdata); +#endif + + // the add_torrent_params object must be moved in + torrent_handle add_torrent(add_torrent_params&&, error_code& ec); + + // second return value is true if the torrent was added and false if an + // existing one was found. + std::tuple, info_hash_t, bool> + add_torrent_impl(add_torrent_params&& p, error_code& ec); + std::tuple, info_hash_t, bool> + add_torrent_impl(add_torrent_params const& p, error_code& ec) = delete; + void async_add_torrent(add_torrent_params* params); + + void remove_torrent(torrent_handle const& h, remove_flags_t options) override; + void remove_torrent_impl(std::shared_ptr tptr, remove_flags_t options) override; + + void get_torrent_status(std::vector* ret + , std::function const& pred + , status_flags_t flags) const; + void refresh_torrent_status(std::vector* ret + , status_flags_t flags) const; + void post_torrent_updates(status_flags_t flags); + void post_session_stats(); + void post_dht_stats(); + + std::vector get_torrents() const; + + void pop_alerts(std::vector* alerts); + alert* wait_for_alert(time_duration max_wait); + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED void pop_alerts(); + TORRENT_DEPRECATED alert const* pop_alert(); + TORRENT_DEPRECATED std::size_t set_alert_queue_size_limit(std::size_t queue_size_limit_); + TORRENT_DEPRECATED int upload_rate_limit_depr() const; + TORRENT_DEPRECATED int download_rate_limit_depr() const; + TORRENT_DEPRECATED int local_upload_rate_limit() const; + TORRENT_DEPRECATED int local_download_rate_limit() const; + + TORRENT_DEPRECATED void set_local_download_rate_limit(int bytes_per_second); + TORRENT_DEPRECATED void set_local_upload_rate_limit(int bytes_per_second); + TORRENT_DEPRECATED void set_download_rate_limit_depr(int bytes_per_second); + TORRENT_DEPRECATED void set_upload_rate_limit_depr(int bytes_per_second); + TORRENT_DEPRECATED void set_max_connections(int limit); + TORRENT_DEPRECATED void set_max_uploads(int limit); + + TORRENT_DEPRECATED int max_connections() const; + TORRENT_DEPRECATED int max_uploads() const; +#endif + + aux::bandwidth_manager* get_bandwidth_manager(int channel) override; + + int upload_rate_limit(peer_class_t c) const; + int download_rate_limit(peer_class_t c) const; + void set_upload_rate_limit(peer_class_t c, int limit); + void set_download_rate_limit(peer_class_t c, int limit); + + void set_rate_limit(peer_class_t c, int channel, int limit); + int rate_limit(peer_class_t c, int channel) const; + + bool preemptive_unchoke() const override; + + // deprecated, use stats counters ``num_peers_up_unchoked`` instead + int num_uploads() const override + { return int(m_stats_counters[counters::num_peers_up_unchoked]); } + + // deprecated, use stats counters ``num_peers_connected`` + + // ``num_peers_half_open`` instead. + int num_connections() const override { return int(m_connections.size()); } + + void trigger_unchoke() noexcept override + { + TORRENT_ASSERT(is_single_thread()); + m_unchoke_time_scaler = 0; + } + void trigger_optimistic_unchoke() noexcept override + { + TORRENT_ASSERT(is_single_thread()); + m_optimistic_unchoke_time_scaler = 0; + } + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_push.hpp" + session_status status() const; + peer_id deprecated_get_peer_id() const; +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + + std::uint16_t listen_port() const override; + std::uint16_t listen_port(listen_socket_t* sock) const; + std::uint16_t ssl_listen_port() const override; + std::uint16_t ssl_listen_port(listen_socket_t* sock) const; + + // used by the DHT tracker, returns a UDP listen port + int get_listen_port(transport ssl, aux::listen_socket_handle const& s) override; + // used by peer connections, returns a TCP listen port + // or zero if no matching listen socket is found + int listen_port(transport ssl, address const& local_addr) override; + + void for_each_listen_socket(std::function f) override + { + for (auto& s : m_listen_sockets) + { + f(listen_socket_handle(s)); + } + } + + alert_manager& alerts() override { return m_alerts; } + disk_interface& disk_thread() override { return *m_disk_thread; } + + void abort() noexcept; + void abort_stage2() noexcept; + + torrent_handle find_torrent_handle(sha1_hash const& info_hash); + + void announce_lsd(sha1_hash const& ih, int port) override; + +#if TORRENT_ABI_VERSION <= 2 + void save_state(entry* e, save_state_flags_t flags) const; + void load_state(bdecode_node const* e, save_state_flags_t flags); +#endif + session_params session_state(save_state_flags_t flags) const; + + bool has_connection(peer_connection* p) const override; + void insert_peer(std::shared_ptr const& c) override; + + proxy_settings proxy() const override; + +#ifndef TORRENT_DISABLE_DHT + bool is_dht_running() const { return (m_dht.get() != nullptr); } + int external_udp_port(address const& local_address) const override; +#endif + +#if TORRENT_USE_I2P + char const* i2p_session() const override { return m_i2p_conn.session_id(); } + std::string const& local_i2p_endpoint() const override { return m_i2p_conn.local_endpoint(); } + + void on_i2p_open(error_code const& ec); + void open_new_incoming_i2p_connection(); + void on_i2p_accept(error_code const& e); +#endif + + void start_ip_notifier(); + void start_lsd(); + void start_natpmp(); + void start_upnp(); + + void stop_ip_notifier(); + void stop_lsd(); + void stop_natpmp(); + void stop_upnp(); + + std::vector add_port_mapping(portmap_protocol t, int external_port + , int local_port); + void delete_port_mapping(port_mapping_t handle); + + int next_port() const; + + void deferred_submit_jobs() override; + + // implements dht_observer + void set_external_address(aux::listen_socket_handle const& iface + , address const& ip, address const& source) override; + void get_peers(sha1_hash const& ih) override; + void announce(sha1_hash const& ih, address const& addr, int port) override; + void outgoing_get_peers(sha1_hash const& target + , sha1_hash const& sent_target, udp::endpoint const& ep) override; + +#ifndef TORRENT_DISABLE_LOGGING + bool should_log(module_t m) const override; + void log(module_t m, char const* fmt, ...) + override TORRENT_FORMAT(3,4); + void log_packet(message_direction_t dir, span pkt + , udp::endpoint const& node) override; + + bool should_log_portmap(portmap_transport transport) const override; + void log_portmap(portmap_transport transport, char const* msg + , listen_socket_handle const&) const override; + + bool should_log_lsd() const override; + void log_lsd(char const* msg) const override; +#endif + + bool on_dht_request(string_view query + , dht::msg const& request, entry& response) override; + + void set_external_address(tcp::endpoint const& local_endpoint + , address const& ip + , ip_source_t source_type, address const& source) override; + external_ip external_address() const override; + + // used when posting synchronous function + // calls to session_impl and torrent objects + mutable std::mutex mut; + mutable std::condition_variable cond; + + // implements session_interface + tcp::endpoint bind_outgoing_socket(socket_type& s + , address const& remote_address, error_code& ec) const override; + bool verify_incoming_interface(address const& addr); + bool verify_bound_address(address const& addr, bool utp + , error_code& ec) override; + + bool has_lsd() const override; + + std::vector& block_info_storage() override { return m_block_info_storage; } + + libtorrent::aux::utp_socket_manager* utp_socket_manager() override + { return &m_utp_socket_manager; } +#ifdef TORRENT_SSL_PEERS + libtorrent::aux::utp_socket_manager* ssl_utp_socket_manager() override + { return &m_ssl_utp_socket_manager; } +#endif + + void inc_boost_connections() override + { + ++m_boost_connections; + m_stats_counters.inc_stats_counter(counters::boost_connection_attempts); + } + + // the settings for the client + aux::session_settings m_settings; + +#if TORRENT_ABI_VERSION == 1 + void update_ssl_listen(); + void update_local_download_rate(); + void update_local_upload_rate(); + void update_rate_limit_utp(); + void update_ignore_rate_limits_on_local_network(); +#endif + + void update_dht_upload_rate_limit(); + void update_proxy(); + void update_i2p_bridge(); + void update_peer_dscp(); + void update_user_agent(); + void update_unchoke_limit(); + void update_connection_speed(); + void update_alert_queue_size(); + void update_disk_threads(); + void update_report_web_seed_downloads(); + void update_outgoing_interfaces(); + void update_listen_interfaces(); + void update_privileged_ports(); + void update_auto_sequential(); + void update_max_failcount(); + void update_resolver_cache_timeout(); + + void update_ip_notifier(); + void update_upnp(); + void update_natpmp(); + void update_lsd(); + void update_dht(); + void update_count_slow(); + void update_dht_bootstrap_nodes(); + + void update_socket_buffer_size(); + void update_dht_announce_interval(); + void update_download_rate(); + void update_upload_rate(); + void update_connections_limit(); + void update_alert_mask(); + void update_validate_https(); + + void trigger_auto_manage() override; + + private: + + // return the settings value for int setting "n", if the value is + // negative, return INT_MAX + int get_int_setting(int n) const; + + aux::array, num_torrent_lists, torrent_list_index_t> + m_torrent_lists; + + peer_class_pool m_classes; + + void init(); + + void submit_disk_jobs(); + + void on_trigger_auto_manage(); + + void on_lsd_peer(tcp::endpoint const& peer, sha1_hash const& ih) override; + + void start_natpmp(std::shared_ptr const& s); + void start_upnp(std::shared_ptr const& s); + + void set_external_address(std::shared_ptr const& sock, address const& ip + , ip_source_t source_type, address const& source); + + counters m_stats_counters; + + // this is a pool allocator for torrent_peer objects + // torrents and the disk cache (implicitly by holding references to the + // torrents) depend on this outliving them. + torrent_peer_allocator m_peer_allocator; + + // this vector is used to store the block_info + // objects pointed to by partial_piece_info returned + // by torrent::get_download_queue. + std::vector m_block_info_storage; + + io_context& m_io_context; + +#if TORRENT_USE_SSL + // this is a generic SSL context used when talking to HTTPS servers + ssl::context m_ssl_ctx; +#endif + +#ifdef TORRENT_SSL_PEERS + // this is the SSL context used for SSL listen sockets. It doesn't + // verify peers, but it has the servername callback set on it. Once it + // knows which torrent a peer is connecting to, it will switch the + // socket over to the torrent specific context, which does verify peers + ssl::context m_peer_ssl_ctx; +#endif + + // handles delayed alerts + mutable alert_manager m_alerts; + +#if TORRENT_ABI_VERSION == 1 + // the alert pointers stored in m_alerts + mutable aux::vector m_alert_pointers; + + // if not all the alerts in m_alert_pointers have been delivered to + // the client. This is the offset into m_alert_pointers where the next + // alert is. If this is greater than or equal to m_alert_pointers.size() + // it means we need to request new alerts from the main thread. + mutable int m_alert_pointer_pos = 0; +#endif + + // handles disk io requests asynchronously + // peers have pointers into the disk buffer + // pool, and must be destructed before this + // object. The disk thread relies on the file + // pool object, and must be destructed before + // m_files. The disk io thread posts completion + // events to the io service, and needs to be + // constructed after it. + std::unique_ptr m_disk_thread; + + // the bandwidth manager is responsible for + // handing out bandwidth to connections that + // asks for it, it can also throttle the + // rate. + bandwidth_manager m_download_rate; + bandwidth_manager m_upload_rate; + + // the peer class that all peers belong to by default + peer_class_t m_global_class{0}; + + // the peer class all TCP peers belong to by default + // all tcp peer connections are subject to these + // bandwidth limits. Local peers are exempted + // from this limit. The purpose is to be able to + // throttle TCP that passes over the internet + // bottleneck (i.e. modem) to avoid starving out + // uTP connections. + peer_class_t m_tcp_peer_class{0}; + + // peer class for local peers + peer_class_t m_local_peer_class{0}; + + resolver m_host_resolver; + + tracker_manager m_tracker_manager; + + // the torrents must be destructed after the torrent_peer_allocator, + // since the torrents hold the peer lists that own the torrent_peers + // (which are allocated in the torrent_peer_allocator) + aux::torrent_list m_torrents; + + // all torrents that are downloading or queued, + // ordered by their queue position + aux::vector m_download_queue; + + // peer connections are put here when disconnected to avoid + // race conditions with the disk thread. It's important that + // peer connections are destructed from the network thread, + // once a peer is disconnected, it's put in this list and + // every second their refcount is checked, and if it's 1, + // they are deleted (from the network thread) + std::vector> m_undead_peers; + + // keep the io_context alive until we have posted the job + // to clear the undead peers + executor_work_guard m_work; + + // this maps sockets to their peer_connection + // object. It is the complete list of all connected + // peers. + connection_map m_connections; + +#ifdef TORRENT_SSL_PEERS + // this list holds incoming connections while they + // are performing SSL handshake. When we shut down + // the session, all of these are disconnected, otherwise + // they would linger and stall or hang session shutdown + std::set, unique_ptr_less> m_incoming_sockets; +#endif + + // maps IP ranges to bitfields representing peer class IDs + // to assign peers matching a specific IP range based on its + // remote endpoint + ip_filter m_peer_class_filter; + + // maps socket types to peer classes + peer_class_type_filter m_peer_class_type_filter; + + // filters incoming connections + std::shared_ptr m_ip_filter; + + // filters outgoing connections + port_filter m_port_filter; + + // posts a notification when the set of local IPs changes + std::unique_ptr m_ip_notifier; + + // the addresses or device names of the interfaces we are supposed to + // listen on. if empty, it means that we should let the os decide + // which interface to listen on + std::vector m_listen_interfaces; + + // the network interfaces outgoing connections are opened through. If + // there is more then one, they are used in a round-robin fashion + // each element is a device name or IP address (in string form) and + // a port number. The port determines which port to bind the listen + // socket to, and the device or IP determines which network adapter + // to be used. If no adapter with the specified name exists, the listen + // socket fails. + std::vector m_outgoing_interfaces; + + // since we might be listening on multiple interfaces + // we might need more than one listen socket + std::vector> m_listen_sockets; + +#if TORRENT_USE_I2P + i2p_connection m_i2p_conn; + boost::optional m_i2p_listen_socket; +#endif + +#if TORRENT_USE_SSL + ssl::context* ssl_ctx() override { return &m_ssl_ctx; } +#endif +#ifdef TORRENT_SSL_PEERS + void on_incoming_utp_ssl(socket_type s); + void ssl_handshake(error_code const& ec, socket_type* s); +#endif + + // round-robin index into m_outgoing_interfaces + mutable std::uint8_t m_interface_index = 0; + + std::shared_ptr setup_listener( + listen_endpoint_t const& lep, error_code& ec); + +#ifndef TORRENT_DISABLE_DHT + dht::dht_state m_dht_state; +#endif + + // this is initialized to the unchoke_interval + // session_setting and decreased every second. + // when it reaches zero, it is reset to the + // unchoke_interval and the unchoke set is + // recomputed. + // TODO: replace this by a proper asio timer + int m_unchoke_time_scaler = 0; + + // this is used to decide when to recalculate which + // torrents to keep queued and which to activate + // TODO: replace this by a proper asio timer + int m_auto_manage_time_scaler = 0; + + // works like unchoke_time_scaler but it + // is only decreased when the unchoke set + // is recomputed, and when it reaches zero, + // the optimistic unchoke is moved to another peer. + // TODO: replace this by a proper asio timer + int m_optimistic_unchoke_time_scaler = 0; + + // works like unchoke_time_scaler. Each time + // it reaches 0, and all the connections are + // used, the worst connection will be disconnected + // from the torrent with the most peers + int m_disconnect_time_scaler = 90; + + // when this scaler reaches zero, it will + // scrape one of the auto managed, paused, + // torrents. + int m_auto_scrape_time_scaler = 180; + + // statistics gathered from all torrents. + stat m_stat; + + // implements session_interface + void sent_bytes(int bytes_payload, int bytes_protocol) override; + void received_bytes(int bytes_payload, int bytes_protocol) override; + void trancieve_ip_packet(int bytes, bool ipv6) override; + void sent_syn(bool ipv6) override; + void received_synack(bool ipv6) override; + +#if TORRENT_ABI_VERSION == 1 + int m_peak_up_rate = 0; +#endif + + void on_tick(error_code const& e); + + void try_connect_more_peers(); + void auto_manage_checking_torrents(std::vector& list + , int& limit); + void auto_manage_torrents(std::vector& list + , int& dht_limit, int& tracker_limit + , int& lsd_limit, int& hard_limit, int type_limit); + void recalculate_auto_managed_torrents(); + void recalculate_unchoke_slots(); + void recalculate_optimistic_unchoke_slots(); + + time_point m_created; + std::uint16_t session_time() const override + { + // +1 is here to make it possible to distinguish uninitialized (to + // 0) timestamps and timestamps of things that happened during the + // first second after the session was constructed + std::int64_t const ret = total_seconds(aux::time_now() + - m_created) + 1; + TORRENT_ASSERT(ret >= 0); + if (ret > (std::numeric_limits::max)()) + return (std::numeric_limits::max)(); + return static_cast(ret); + } + time_point session_start_time() const override + { + return m_created; + } + + time_point m_last_tick; + time_point m_last_second_tick; + + // the last time we went through the peers + // to decide which ones to choke/unchoke + time_point m_last_choke; + + // the last time we recalculated which torrents should be started + // and stopped (only the auto managed ones) + time_point m_last_auto_manage; + + // when outgoing_ports is configured, this is the + // port we'll bind the next outgoing socket to + mutable int m_next_port = 0; + +#ifndef TORRENT_DISABLE_DHT + std::unique_ptr m_dht_storage; + std::shared_ptr m_dht; + dht::dht_storage_constructor_type m_dht_storage_constructor + = dht::dht_default_storage_constructor; + + // these are used when starting the DHT + // (and bootstrapping it), and then erased + std::vector m_dht_router_nodes; + + // if a DHT node is added when there's no DHT instance, they're stored + // here until we start the DHT + std::vector m_dht_nodes; + + // this announce timer is used + // by the DHT. + deadline_timer m_dht_announce_timer; + + // the number of torrents there were when the + // update_dht_announce_interval() was last called. + // if the number of torrents changes significantly + // compared to this number, the DHT announce interval + // is updated again. This especially matters for + // small numbers. + int m_dht_interval_update_torrents = 0; + + // the number of DHT router lookups there are currently outstanding. As + // long as this is > 0, we'll postpone starting the DHT + int m_outstanding_router_lookups = 0; +#endif + + void send_udp_packet_hostname(std::weak_ptr sock + , char const* hostname + , int port + , span p + , error_code& ec + , udp_send_flags_t flags); + + void send_udp_packet_hostname_listen(aux::listen_socket_handle const& sock + , char const* hostname + , int port + , span p + , error_code& ec + , udp_send_flags_t const flags) + { + listen_socket_t* s = sock.get(); + if (!s) + { + ec = boost::asio::error::bad_descriptor; + return; + } + send_udp_packet_hostname(sock.get_ptr(), hostname, port, p, ec, flags); + } + + void send_udp_packet(std::weak_ptr sock + , udp::endpoint const& ep + , span p + , error_code& ec + , udp_send_flags_t flags); + + void send_udp_packet_listen(aux::listen_socket_handle const& sock + , udp::endpoint const& ep + , span p + , error_code& ec + , udp_send_flags_t const flags) + { + listen_socket_t* s = sock.get(); + if (!s) + { + ec = boost::asio::error::bad_descriptor; + return; + } + send_udp_packet(sock.get_ptr(), ep, p, ec, flags); + } + + void on_udp_writeable(std::weak_ptr s, error_code const& ec); + + void on_udp_packet(std::weak_ptr s + , std::weak_ptr ls + , transport ssl, error_code const& ec); + + libtorrent::aux::utp_socket_manager m_utp_socket_manager; + +#ifdef TORRENT_SSL_PEERS + // used for uTP connections over SSL + libtorrent::aux::utp_socket_manager m_ssl_utp_socket_manager; +#endif + + // the number of torrent connection boosts + // connections that have been made this second + // this is deducted from the connect speed + int m_boost_connections = 0; + + // mask is a bitmask of which protocols to remap on: + enum remap_port_mask_t + { + remap_natpmp = 1, + remap_upnp = 2, + remap_natpmp_and_upnp = 3 + }; + void remap_ports(remap_port_mask_t mask, listen_socket_t& s); + + // the timer used to fire the tick + deadline_timer m_timer; + aux::handler_storage m_tick_handler_storage; + + // abort may not fail and cannot allocate memory + aux::handler_storage m_abort_handler_storage; + + // submit_deferred may not fail + aux::handler_storage m_submit_jobs_handler_storage; + + // torrents are announced on the local network in a + // round-robin fashion. All torrents are cycled through + // within the LSD announce interval (which defaults to + // 5 minutes) + std::size_t m_next_lsd_torrent; + +#ifndef TORRENT_DISABLE_DHT + // torrents are announced on the DHT in a + // round-robin fashion. All torrents are cycled through + // within the DHT announce interval (which defaults to + // 15 minutes) + std::size_t m_next_dht_torrent; + + // torrents that don't have any peers + // when added should be announced to the DHT + // as soon as possible. Such torrents are put + // in this queue and get announced the next time + // the timer fires, instead of the next one in + // the round-robin sequence. + std::deque> m_dht_torrents; +#endif + + // torrents prioritized to get connection attempts + std::deque, int>> m_prio_torrents; + + // this announce timer is used + // by Local service discovery + deadline_timer m_lsd_announce_timer; + + // this is the timer used to call ``close_oldest`` on the ``file_pool`` + // object. This closes the file that's been opened the longest every + // time it's called, to force the windows disk cache to be flushed + deadline_timer m_close_file_timer; + + // the index of the torrent that will be offered to + // connect to a peer next time on_tick is called. + // This implements a round robin peer connections among + // torrents that want more peers. The index is into + // m_torrent_lists[torrent_want_peers_downloading] + // (which is a list of torrent pointers with all + // torrents that want peers and are downloading) + int m_next_downloading_connect_torrent = 0; + int m_next_finished_connect_torrent = 0; + + // this is the number of attempts of connecting to + // peers we have given to downloading torrents. + // when this gets high enough, we try to connect + // a peer from a finished torrent + int m_download_connect_attempts = 0; + + // index into m_torrent_lists[torrent_want_scrape] referring + // to the next torrent to auto-scrape + int m_next_scrape_torrent = 0; + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + counters& stats_counters() override { return m_stats_counters; } + + void received_buffer(int size) override; + void sent_buffer(int size) override; + +#ifndef TORRENT_DISABLE_LOGGING + bool should_log() const override; + void session_log(char const* fmt, ...) const noexcept override TORRENT_FORMAT(2,3); +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + // this is a list to allow extensions to potentially remove themselves. + std::array>, 5> m_ses_extensions; +#endif + +#if TORRENT_ABI_VERSION == 1 + user_load_function_t m_user_load_torrent; +#endif + + // this is true whenever we have posted a deferred-disk job + // it means we don't need to post another one + bool m_deferred_submit_disk_jobs = false; + + // this is set to true when a torrent auto-manage + // event is triggered, and reset whenever the message + // is delivered and the auto-manage is executed. + // there should never be more than a single pending auto-manage + // message in-flight at any given time. + bool m_pending_auto_manage = false; + + // this is set to true when triggering an auto-manage + // of the torrents. However, if the normal auto-manage + // timer comes along and executes the auto-management, + // this is set to false, which means the triggered event + // no longer needs to execute the auto-management. + bool m_need_auto_manage = false; + + // set to true when the session object + // is being destructed and the thread + // should exit + bool m_abort = false; + + // is true if the session is paused + bool m_paused = false; + + // set to true the first time post_session_stats() is + // called and we post the headers alert + bool m_posted_stats_header = false; + }; + +#ifndef TORRENT_DISABLE_LOGGING + struct tracker_logger : request_callback + { + explicit tracker_logger(session_interface& ses); + void tracker_warning(tracker_request const& req + , std::string const& str) override; + void tracker_response(tracker_request const& + , libtorrent::address const& tracker_ip + , std::list
    const& tracker_ips + , struct tracker_response const& resp) override; + void tracker_request_error(tracker_request const& r + , error_code const& ec, operation_t op, const std::string& str + , seconds32 retry_interval) override; + bool should_log() const override; + void debug_log(const char* fmt, ...) const noexcept override TORRENT_FORMAT(2,3); + session_interface& m_ses; + private: + // explicitly disallow assignment, to silence msvc warning + tracker_logger& operator=(tracker_logger const&); + }; +#endif + + } +} + +#endif diff --git a/include/libtorrent/aux_/session_interface.hpp b/include/libtorrent/aux_/session_interface.hpp new file mode 100644 index 0000000..ea5ccde --- /dev/null +++ b/include/libtorrent/aux_/session_interface.hpp @@ -0,0 +1,316 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016-2017, 2019-2020, Alden Torres +Copyright (c) 2016-2018, Steven Siloti +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_INTERFACE_HPP_INCLUDED +#define TORRENT_SESSION_INTERFACE_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/socket.hpp" // for tcp::endpoint +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/listen_socket_handle.hpp" +#include "libtorrent/aux_/session_udp_sockets.hpp" // for transport +#include "libtorrent/session_types.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/link.hpp" // for torrent_list_index_t +#include "libtorrent/info_hash.hpp" +#include "libtorrent/aux_/socket_type.hpp" +#include "libtorrent/ssl.hpp" + +#include +#include + +namespace libtorrent { + + struct peer_connection; + struct torrent; + struct peer_class_set; + struct peer_class_pool; + struct disk_observer; + struct torrent_peer; + struct disk_interface; + struct tracker_request; + struct request_callback; + struct external_ip; + struct torrent_peer_allocator_interface; + struct counters; + +namespace aux { + struct utp_socket_manager; + struct bandwidth_channel; + struct bandwidth_manager; + struct resolver_interface; + struct alert_manager; +} + + // hidden + using queue_position_t = aux::strong_typedef; + + constexpr queue_position_t no_pos{-1}; + constexpr queue_position_t last_pos{(std::numeric_limits::max)()}; + +#ifndef TORRENT_DISABLE_DHT +namespace dht { + + struct dht_tracker; + } +#endif +} + +namespace libtorrent { +namespace aux { + + struct proxy_settings; + struct session_settings; + + using ip_source_t = flags::bitfield_flag; + +#if !defined TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + // This is the basic logging and debug interface offered by the session. + // a release build with logging disabled (which is the default) will + // not have this class at all + struct TORRENT_EXTRA_EXPORT session_logger + { +#ifndef TORRENT_DISABLE_LOGGING + virtual bool should_log() const = 0; + virtual void session_log(char const* fmt, ...) const TORRENT_FORMAT(2,3) = 0; +#endif + +#if TORRENT_USE_ASSERTS + virtual bool is_single_thread() const = 0; + virtual bool has_peer(peer_connection const* p) const = 0; + virtual bool any_torrent_has_peer(peer_connection const* p) const = 0; + virtual bool is_posting_torrent_updates() const = 0; +#endif + protected: + ~session_logger() {} + }; +#endif // TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + + // TODO: 2 make this interface a lot smaller. It could be split up into + // several smaller interfaces. Each subsystem could then limit the size + // of the mock object to test it. + struct TORRENT_EXTRA_EXPORT session_interface +#if !defined TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + : session_logger +#endif + { + + // TODO: 2 the IP voting mechanism should be factored out + // to its own class, not part of the session + // and these constants should move too + + // the logic in ip_voter relies on more reliable sources are represented + // by more significant bits + static constexpr ip_source_t source_dht = 1_bit; + static constexpr ip_source_t source_peer = 2_bit; + static constexpr ip_source_t source_tracker = 3_bit; + static constexpr ip_source_t source_router = 4_bit; + + virtual void set_external_address(tcp::endpoint const& local_endpoint + , address const& ip + , ip_source_t source_type, address const& source) = 0; + virtual external_ip external_address() const = 0; + + virtual disk_interface& disk_thread() = 0; + + virtual alert_manager& alerts() = 0; + + virtual torrent_peer_allocator_interface& get_peer_allocator() = 0; + virtual io_context& get_context() = 0; + virtual aux::resolver_interface& get_resolver() = 0; + + virtual bool has_connection(peer_connection* p) const = 0; + virtual void insert_peer(std::shared_ptr const& c) = 0; + + virtual void remove_torrent(torrent_handle const& h, remove_flags_t options = {}) = 0; + virtual void remove_torrent_impl(std::shared_ptr tptr, remove_flags_t options) = 0; + + // port filter + virtual port_filter const& get_port_filter() const = 0; + virtual void ban_ip(address addr) = 0; + + virtual std::uint16_t session_time() const = 0; + virtual time_point session_start_time() const = 0; + + virtual bool is_aborted() const = 0; + virtual int num_uploads() const = 0; + virtual bool preemptive_unchoke() const = 0; + virtual void trigger_optimistic_unchoke() noexcept = 0; + virtual void trigger_unchoke() noexcept = 0; + + virtual std::weak_ptr find_torrent(info_hash_t const& info_hash) const = 0; + virtual std::weak_ptr find_disconnect_candidate_torrent() const = 0; + virtual std::shared_ptr delay_load_torrent(info_hash_t const& info_hash + , peer_connection* pc) = 0; + virtual void insert_torrent(info_hash_t const& ih, std::shared_ptr const& t) = 0; + virtual void update_torrent_info_hash(std::shared_ptr const& t + , info_hash_t const& old_ih) = 0; + virtual void set_queue_position(torrent* t, queue_position_t p) = 0; + virtual int num_torrents() const = 0; + + virtual void close_connection(peer_connection* p) noexcept = 0; + virtual int num_connections() const = 0; + + virtual void deferred_submit_jobs() = 0; + + virtual std::uint16_t listen_port() const = 0; + virtual std::uint16_t ssl_listen_port() const = 0; + + virtual int listen_port(aux::transport ssl, address const& local_addr) = 0; + + virtual void for_each_listen_socket(std::function f) = 0; + + // ask for which interface and port to bind outgoing peer connections on + virtual tcp::endpoint bind_outgoing_socket(socket_type& s, address const& + remote_address, error_code& ec) const = 0; + virtual bool verify_bound_address(address const& addr, bool utp + , error_code& ec) = 0; + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + virtual std::vector> find_collection( + std::string const& collection) const = 0; +#endif + + // TODO: it would be nice to not have this be part of session_interface + virtual proxy_settings proxy() const = 0; + +#if TORRENT_USE_I2P + virtual char const* i2p_session() const = 0; + virtual std::string const& local_i2p_endpoint() const = 0; +#endif + + virtual void prioritize_connections(std::weak_ptr t) = 0; + + virtual void trigger_auto_manage() = 0; + + virtual void apply_settings_pack(std::shared_ptr pack) = 0; + virtual session_settings const& settings() const = 0; + + virtual void queue_tracker_request(tracker_request req + , std::weak_ptr c) = 0; + + // peer-classes + virtual void set_peer_classes(peer_class_set* s, address const& a, socket_type_t st) = 0; + virtual peer_class_pool const& peer_classes() const = 0; + virtual peer_class_pool& peer_classes() = 0; + virtual bool ignore_unchoke_slots_set(peer_class_set const& set) const = 0; + virtual int copy_pertinent_channels(peer_class_set const& set + , int channel, bandwidth_channel** dst, int m) = 0; + virtual int use_quota_overhead(peer_class_set& set, int amount_down, int amount_up) = 0; + + virtual bandwidth_manager* get_bandwidth_manager(int channel) = 0; + + virtual void sent_bytes(int bytes_payload, int bytes_protocol) = 0; + virtual void received_bytes(int bytes_payload, int bytes_protocol) = 0; + virtual void trancieve_ip_packet(int bytes, bool ipv6) = 0; + virtual void sent_syn(bool ipv6) = 0; + virtual void received_synack(bool ipv6) = 0; + + // this is the set of (subscribed) torrents that have changed + // their states since the last time the user requested updates. + static constexpr torrent_list_index_t torrent_state_updates{0}; + + // all torrents that want to be ticked every second + static constexpr torrent_list_index_t torrent_want_tick{1}; + + // all torrents that want more peers and are still downloading + // these typically have higher priority when connecting peers + static constexpr torrent_list_index_t torrent_want_peers_download{2}; + + // all torrents that want more peers and are finished downloading + static constexpr torrent_list_index_t torrent_want_peers_finished{3}; + + // torrents that want auto-scrape (only paused auto-managed ones) + static constexpr torrent_list_index_t torrent_want_scrape{4}; + + // auto-managed torrents by state. Only these torrents are considered + // when recalculating auto-managed torrents. started auto managed + // torrents that are inactive are not part of these lists, because they + // are not considered for auto managing (they are left started + // unconditionally) + static constexpr torrent_list_index_t torrent_downloading_auto_managed{5}; + static constexpr torrent_list_index_t torrent_seeding_auto_managed{6}; + static constexpr torrent_list_index_t torrent_checking_auto_managed{7}; + + static constexpr std::size_t num_torrent_lists = 8; + + virtual aux::vector& torrent_list(torrent_list_index_t i) = 0; + + virtual bool has_lsd() const = 0; + virtual void announce_lsd(sha1_hash const& ih, int port) = 0; + virtual libtorrent::aux::utp_socket_manager* utp_socket_manager() = 0; + virtual void inc_boost_connections() = 0; + virtual std::vector& block_info_storage() = 0; + +#ifdef TORRENT_SSL_PEERS + virtual libtorrent::aux::utp_socket_manager* ssl_utp_socket_manager() = 0; +#endif +#if TORRENT_USE_SSL + virtual ssl::context* ssl_ctx() = 0 ; +#endif + +#if !defined TORRENT_DISABLE_ENCRYPTION + virtual torrent const* find_encrypted_torrent( + sha1_hash const& info_hash, sha1_hash const& xor_mask) = 0; +#endif + +#ifndef TORRENT_DISABLE_DHT + virtual bool announce_dht() const = 0; + virtual void add_dht_node(udp::endpoint const& n) = 0; + virtual bool has_dht() const = 0; + virtual int external_udp_port(address const& local_address) const = 0; + virtual dht::dht_tracker* dht() = 0; + virtual void prioritize_dht(std::weak_ptr t) = 0; +#endif + + virtual counters& stats_counters() = 0; + virtual void received_buffer(int size) = 0; + virtual void sent_buffer(int size) = 0; + +#if TORRENT_USE_ASSERTS + virtual bool verify_queue_position(torrent const*, queue_position_t) = 0; +#endif + + virtual ~session_interface() {} + }; +}} + +#endif diff --git a/include/libtorrent/aux_/session_settings.hpp b/include/libtorrent/aux_/session_settings.hpp new file mode 100644 index 0000000..261968f --- /dev/null +++ b/include/libtorrent/aux_/session_settings.hpp @@ -0,0 +1,190 @@ +/* + +Copyright (c) 2014, 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_AUX_SESSION_SETTINGS_HPP_INCLUDED +#define TORRENT_AUX_SESSION_SETTINGS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/assert.hpp" + +#include +#include +#include +#include +#include + +namespace libtorrent { +namespace aux { + + struct TORRENT_EXTRA_EXPORT session_settings_single_thread + { + void set_str(int name, std::string value) + { set(m_strings, name, std::move(value), settings_pack::string_type_base); } + void set_int(int name, int value) + { set(m_ints, name, value, settings_pack::int_type_base); } + void set_bool(int name, bool value) + { set(m_bools, name, value, settings_pack::bool_type_base); } + + std::string const& get_str(int name) const + { return get(m_strings, name, settings_pack::string_type_base); } + int get_int(int name) const + { return get(m_ints, name, settings_pack::int_type_base); } + bool get_bool(int name) const + { return get(m_bools, name, settings_pack::bool_type_base); } + + session_settings_single_thread(); + + private: + + template + void set(Container& c, int const name, T val + , int const type) + { + TORRENT_ASSERT((name & settings_pack::type_mask) == type); + if ((name & settings_pack::type_mask) != type) return; + size_t const index = name & settings_pack::index_mask; + TORRENT_ASSERT(index < c.size()); + c[index] = std::move(val); + } + + template + T get(Container const& c, int const name, int const type) const + { + static typename std::remove_reference::type empty; + TORRENT_ASSERT((name & settings_pack::type_mask) == type); + if ((name & settings_pack::type_mask) != type) return empty; + size_t const index = name & settings_pack::index_mask; + TORRENT_ASSERT(index < c.size()); + return c[index]; + } + + std::array m_strings; + std::array m_ints; + std::bitset m_bools; + }; + + struct TORRENT_EXTRA_EXPORT session_settings final : settings_interface + { + void set_str(int name, std::string value) override + { + std::unique_lock l(m_mutex); + return m_store.set_str(name, std::move(value)); + } + void set_int(int name, int value) override + { + std::unique_lock l(m_mutex); + m_store.set_int(name, value); + } + void set_bool(int name, bool value) override + { + std::unique_lock l(m_mutex); + m_store.set_bool(name, value); + } + + std::string const& get_str(int name) const override + { + std::unique_lock l(m_mutex); + return m_store.get_str(name); + } + int get_int(int name) const override + { + std::unique_lock l(m_mutex); + return m_store.get_int(name); + } + bool get_bool(int name) const override + { + std::unique_lock l(m_mutex); + return m_store.get_bool(name); + } + + bool has_val(int) const override { return true; } + + session_settings(); + explicit session_settings(settings_pack const&); + + void bulk_set(std::function); + void bulk_get(std::function) const; + + // since std::mutex is not copyable, we have to explicitly just copy the + // underlying storage object. Lock the object we're copying from first, + // and forward to a private copy constructor to keep the lock alive + // inspired by https://www.justsoftwaresolutions.co.uk/threading/thread-safe-copy-constructors.html + session_settings(session_settings const& lhs) + : session_settings(lhs, std::unique_lock(lhs.m_mutex)) + {} + session_settings(session_settings&& lhs) + : session_settings(std::move(lhs), std::unique_lock(lhs.m_mutex)) + {} + + session_settings& operator=(session_settings const& rhs) + { + if (this == &rhs) return *this; + // in C++17, use a single std::scoped_lock instead + std::lock(rhs.m_mutex, m_mutex); + std::unique_lock l1(rhs.m_mutex, std::adopt_lock); + std::unique_lock l2(m_mutex, std::adopt_lock); + m_store = rhs.m_store; + return *this; + } + session_settings& operator=(session_settings&& rhs) + { + if (this == &rhs) return *this; + m_store = std::move(rhs.m_store); + return *this; + } + + private: + + session_settings(session_settings const& lhs, std::unique_lock const&) + : settings_interface(lhs) + , m_store(lhs.m_store) + {} + + session_settings(session_settings&& lhs, std::unique_lock const&) + : settings_interface(lhs) + , m_store(std::move(lhs.m_store)) + {} + + session_settings_single_thread m_store; + mutable std::mutex m_mutex; + }; + +} +} + +namespace libtorrent { + TORRENT_EXTRA_EXPORT void initialize_default_settings(aux::session_settings_single_thread& s); +} + +#endif diff --git a/include/libtorrent/aux_/session_udp_sockets.hpp b/include/libtorrent/aux_/session_udp_sockets.hpp new file mode 100644 index 0000000..c3bf189 --- /dev/null +++ b/include/libtorrent/aux_/session_udp_sockets.hpp @@ -0,0 +1,78 @@ +/* + +Copyright (c) 2017, 2019-2020, Arvid Norberg +Copyright (c) 2017, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. +* Neither the name of the author nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_UDP_SOCKETS_HPP_INCLUDED +#define TORRENT_SESSION_UDP_SOCKETS_HPP_INCLUDED + +#include "libtorrent/aux_/utp_socket_manager.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/allocating_handler.hpp" +#include "libtorrent/aux_/listen_socket_handle.hpp" +#include +#include + +namespace libtorrent { + + struct alert_manager; + +namespace aux { + + struct listen_endpoint_t; + struct proxy_settings; + struct listen_socket_t; + + enum class transport : std::uint8_t { plaintext, ssl }; + + struct session_udp_socket + { + explicit session_udp_socket(io_context& ios, listen_socket_handle ls) + : sock(ios, std::move(ls)) {} + + udp::endpoint local_endpoint() { return sock.local_endpoint(); } + + udp_socket sock; + + // since udp packets are expected to be dispatched frequently, this saves + // time on handler allocation every time we read again. + aux::handler_storage udp_handler_storage; + + // this is true when the udp socket send() has failed with EAGAIN or + // EWOULDBLOCK. i.e. we're currently waiting for the socket to become + // writeable again. Once it is, we'll set it to false and notify the utp + // socket manager + bool write_blocked = false; + }; + +} } + +#endif diff --git a/include/libtorrent/aux_/set_socket_buffer.hpp b/include/libtorrent/aux_/set_socket_buffer.hpp new file mode 100644 index 0000000..69f049c --- /dev/null +++ b/include/libtorrent/aux_/set_socket_buffer.hpp @@ -0,0 +1,92 @@ +/* + +Copyright (c) 2018, Arvid Norberg, Magnus Jonsson +Copyright (c) 2018, 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SET_SOCKET_BUFFER_HPP +#define TORRENT_SET_SOCKET_BUFFER_HPP + +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/error_code.hpp" + +namespace libtorrent { +namespace aux { + + template + void set_socket_buffer_size(Socket& s, session_settings const& sett, error_code& ec) + { +#ifdef TCP_NOTSENT_LOWAT + int const not_sent_low_watermark = sett.get_int(settings_pack::send_not_sent_low_watermark); + if (not_sent_low_watermark) + { + error_code ignore; + s.set_option(tcp_notsent_lowat(not_sent_low_watermark), ignore); + } +#endif + int const snd_size = sett.get_int(settings_pack::send_socket_buffer_size); + if (snd_size) + { + typename Socket::send_buffer_size prev_option; + s.get_option(prev_option, ec); + if (!ec && prev_option.value() != snd_size) + { + typename Socket::send_buffer_size option(snd_size); + s.set_option(option, ec); + if (ec) + { + // restore previous value + s.set_option(prev_option, ec); + return; + } + } + } + int const recv_size = sett.get_int(settings_pack::recv_socket_buffer_size); + if (recv_size) + { + typename Socket::receive_buffer_size prev_option; + s.get_option(prev_option, ec); + if (!ec && prev_option.value() != recv_size) + { + typename Socket::receive_buffer_size option(recv_size); + s.set_option(option, ec); + if (ec) + { + // restore previous value + s.set_option(prev_option, ec); + return; + } + } + } + } + +}} + +#endif diff --git a/include/libtorrent/aux_/set_traffic_class.hpp b/include/libtorrent/aux_/set_traffic_class.hpp new file mode 100644 index 0000000..068e7a9 --- /dev/null +++ b/include/libtorrent/aux_/set_traffic_class.hpp @@ -0,0 +1,60 @@ +/* + +Copyright (c) 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SET_TRAFFIC_CLASS_HPP +#define TORRENT_SET_TRAFFIC_CLASS_HPP + +#include "libtorrent/error_code.hpp" +#include "libtorrent/socket.hpp" + +namespace libtorrent { +namespace aux { + + template + void set_traffic_class(Socket& s, int v, error_code& ec) + { +#ifdef IP_DSCP_TRAFFIC_TYPE + s.set_option(dscp_traffic_type((v & 0xff) >> 2), ec); + if (!ec) return; + ec.clear(); +#endif +#if defined IPV6_TCLASS + if (is_v6(s.local_endpoint(ec))) + s.set_option(traffic_class(v & 0xfc), ec); + else if (!ec) +#endif + s.set_option(type_of_service(v & 0xfc), ec); + } + +}} + +#endif diff --git a/include/libtorrent/aux_/sha512.hpp b/include/libtorrent/aux_/sha512.hpp new file mode 100644 index 0000000..4e8ceaf --- /dev/null +++ b/include/libtorrent/aux_/sha512.hpp @@ -0,0 +1,33 @@ +#ifndef TORRENT_SHA512_HPP_INCLUDED +#define TORRENT_SHA512_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if !defined TORRENT_USE_LIBGCRYPT \ + && !TORRENT_USE_COMMONCRYPTO \ + && !TORRENT_USE_CNG \ + && !TORRENT_USE_CRYPTOAPI_SHA_512 \ + && !defined TORRENT_USE_LIBCRYPTO + +#include + +namespace libtorrent { +namespace aux { + + struct sha512_ctx + { + std::uint64_t length; + std::uint64_t state[8]; + std::size_t curlen; + std::uint8_t buf[128]; + }; + + TORRENT_EXTRA_EXPORT int SHA512_init(sha512_ctx* md); + TORRENT_EXTRA_EXPORT int SHA512_update(sha512_ctx* md + , std::uint8_t const* data, std::size_t len); + TORRENT_EXTRA_EXPORT int SHA512_final(std::uint8_t* digest, sha512_ctx* md); +} +} + +#endif +#endif diff --git a/include/libtorrent/aux_/socket_type.hpp b/include/libtorrent/aux_/socket_type.hpp new file mode 100644 index 0000000..3b40919 --- /dev/null +++ b/include/libtorrent/aux_/socket_type.hpp @@ -0,0 +1,100 @@ +/* + +Copyright (c) 2007, 2009-2010, 2012-2013, 2017-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SOCKET_TYPE +#define TORRENT_SOCKET_TYPE + +#include "libtorrent/config.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/socks5_stream.hpp" +#include "libtorrent/http_stream.hpp" +#include "libtorrent/i2p_stream.hpp" +#include "libtorrent/aux_/utp_stream.hpp" +#include "libtorrent/aux_/polymorphic_socket.hpp" +#include "libtorrent/socket_type.hpp" + +#if TORRENT_USE_SSL +#include "libtorrent/ssl_stream.hpp" +#endif + +#include "libtorrent/debug.hpp" + +namespace libtorrent { +namespace aux { + + using socket_type = polymorphic_socket< + tcp::socket + , socks5_stream + , http_stream + , utp_stream +#if TORRENT_USE_I2P + , i2p_stream +#endif +#if TORRENT_USE_SSL + , ssl_stream + , ssl_stream + , ssl_stream + , ssl_stream +#endif + >; + + // returns true if this socket is an SSL socket + bool is_ssl(socket_type const& s); + + // returns true if this is a uTP socket + bool is_utp(socket_type const& s); + + socket_type_t socket_type_idx(socket_type const& s); + + char const* socket_type_name(socket_type const& s); + + // this is only relevant for uTP connections + void set_close_reason(socket_type& s, close_reason_t code); + close_reason_t get_close_reason(socket_type const& s); + +#if TORRENT_USE_I2P + // returns true if this is an i2p socket + bool is_i2p(socket_type const& s); +#endif + + // assuming the socket_type s is an ssl socket, make sure it + // verifies the hostname in its SSL handshake + void setup_ssl_hostname(socket_type& s, std::string const& hostname, error_code& ec); + + // properly shuts down SSL sockets. holder keeps s alive + void async_shutdown(socket_type& s, std::shared_ptr holder); +} +} + +#endif diff --git a/include/libtorrent/aux_/storage_free_list.hpp b/include/libtorrent/aux_/storage_free_list.hpp new file mode 100644 index 0000000..b4b9001 --- /dev/null +++ b/include/libtorrent/aux_/storage_free_list.hpp @@ -0,0 +1,73 @@ +/* + +Copyright (c) 2021-2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STORAGE_FREE_LIST_HPP_INCLUDE +#define TORRENT_STORAGE_FREE_LIST_HPP_INCLUDE + +#include +#include "libtorrent/storage_defs.hpp" + +namespace libtorrent { +namespace aux { + + struct storage_free_list + { + // if we don't already have any free slots, use next + storage_index_t new_index(storage_index_t const next) + { + // make sure we can remove this torrent without causing a memory + // allocation, by triggering the allocation now instead + m_free_slots.reserve(static_cast(next) + 1); + return m_free_slots.empty() ? next : pop(); + } + + void add(storage_index_t const i) { m_free_slots.push_back(i); } + + std::size_t size() const { return m_free_slots.size(); } + + private: + + storage_index_t pop() + { + TORRENT_ASSERT(!m_free_slots.empty()); + storage_index_t const ret = m_free_slots.back(); + m_free_slots.pop_back(); + return ret; + } + + private: + std::vector m_free_slots; + }; +} +} +#endif + diff --git a/include/libtorrent/aux_/storage_utils.hpp b/include/libtorrent/aux_/storage_utils.hpp new file mode 100644 index 0000000..2de14f0 --- /dev/null +++ b/include/libtorrent/aux_/storage_utils.hpp @@ -0,0 +1,130 @@ +/* + +Copyright (c) 2017-2020, 2022, Arvid Norberg +Copyright (c) 2018, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STORAGE_UTILS_HPP_INCLUDE +#define TORRENT_STORAGE_UTILS_HPP_INCLUDE + +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/storage_defs.hpp" // for status_t +#include "libtorrent/session_types.hpp" +#include "libtorrent/error_code.hpp" + +namespace libtorrent { + + struct stat_cache; + + // TODO: 3 remove this typedef, and use span for disk write + // operations + using iovec_t = span; + +namespace aux { + + // this is a read or write operation so that readwrite() knows + // what to do when it's actually touching the file + using fileop = std::function, storage_error&)>; + + // this function is responsible for turning read and write operations in the + // torrent space (pieces) into read and write operations in the filesystem + // space (files on disk). + TORRENT_EXTRA_EXPORT int readwrite(file_storage const& files + , span buf, piece_index_t piece, int offset + , storage_error& ec, fileop op); + + // moves the files in file_storage f from ``save_path`` to + // ``destination_save_path`` according to the rules defined by ``flags``. + // returns the status code and the new save_path. + TORRENT_EXTRA_EXPORT std::pair + move_storage(file_storage const& f + , std::string save_path + , std::string const& destination_save_path + , std::function const& move_partfile + , move_flags_t flags, storage_error& ec); + + // deletes the files on fs from save_path according to options. Options may + // opt to only delete the partfile + TORRENT_EXTRA_EXPORT void + delete_files(file_storage const& fs, std::string const& save_path + , std::string const& part_file_name, remove_flags_t options, storage_error& ec); + + TORRENT_EXTRA_EXPORT bool + verify_resume_data(add_torrent_params const& rd + , aux::vector const& links + , file_storage const& fs + , aux::vector const& file_priority + , stat_cache& stat + , std::string const& save_path + , storage_error& ec); + + // given the save_path, stat all files on file_storage until one exists. If a + // file exists, return true, otherwise return false. + TORRENT_EXTRA_EXPORT bool has_any_file( + file_storage const& fs + , std::string const& save_path + , stat_cache& cache + , storage_error& ec); + + TORRENT_EXTRA_EXPORT int read_zeroes(span bufs); + + TORRENT_EXTRA_EXPORT int hash_zeroes(hasher& ph, std::int64_t size); + + TORRENT_EXTRA_EXPORT void initialize_storage( + file_storage const& fs + , std::string const& save_path + , stat_cache& sc + , aux::vector const& file_priority + , std::function create_file + , std::function create_link + , std::function oversized_file + , storage_error& ec); + + TORRENT_EXTRA_EXPORT void create_symlink( + std::string const& target + , std::string const& link + , storage_error& ec); + + TORRENT_EXTRA_EXPORT void move_file(std::string const& f + , std::string const& newf, storage_error& se); + + TORRENT_EXTRA_EXPORT void copy_file(std::string const& f + , std::string const& newf, storage_error& ec); +}} + +#endif diff --git a/include/libtorrent/aux_/store_buffer.hpp b/include/libtorrent/aux_/store_buffer.hpp new file mode 100644 index 0000000..68213ae --- /dev/null +++ b/include/libtorrent/aux_/store_buffer.hpp @@ -0,0 +1,150 @@ +/* + +Copyright (c) 2016, 2020, 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STORE_BUFFER +#define TORRENT_STORE_BUFFER + +#include +#include + +#include "libtorrent/storage_defs.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + +namespace libtorrent { +namespace aux { + +// uniquely identifies a torrent and offset. It is used as the key in the +// dictionary mapping locations to write jobs +struct torrent_location +{ + torrent_location(storage_index_t const t, piece_index_t const p, int o) + : torrent(t), piece(p), offset(o) {} + storage_index_t torrent; + piece_index_t piece; + int offset; + bool operator==(torrent_location const& rhs) const + { + return std::tie(torrent, piece, offset) + == std::tie(rhs.torrent, rhs.piece, rhs.offset); + } +}; + +} +} + +namespace std { + +template <> +struct hash +{ + using argument_type = libtorrent::aux::torrent_location; + using result_type = std::size_t; + std::size_t operator()(argument_type const& l) const + { + using namespace libtorrent; + std::size_t ret = 0; + boost::hash_combine(ret, std::hash{}(l.torrent)); + boost::hash_combine(ret, std::hash{}(l.piece)); + boost::hash_combine(ret, std::hash{}(l.offset)); + return ret; + } +}; + +} + +namespace libtorrent { +namespace aux { + +struct store_buffer +{ + template + bool get(torrent_location const loc, Fun f) const + { + std::unique_lock l(m_mutex); + auto const it = m_store_buffer.find(loc); + if (it != m_store_buffer.end()) + { + f(it->second); + return true; + } + return false; + } + + template + int get2(torrent_location const loc1, torrent_location const loc2, Fun f) const + { + std::unique_lock l(m_mutex); + auto const it1 = m_store_buffer.find(loc1); + auto const it2 = m_store_buffer.find(loc2); + char const* buf1 = (it1 == m_store_buffer.end()) ? nullptr : it1->second; + char const* buf2 = (it2 == m_store_buffer.end()) ? nullptr : it2->second; + + if (buf1 == nullptr && buf2 == nullptr) + return 0; + + return f(buf1, buf2); + } + + void insert(torrent_location const loc, char const* buf) + { + std::lock_guard l(m_mutex); + m_store_buffer.insert({loc, buf}); + } + + void erase(torrent_location const loc) + { + std::lock_guard l(m_mutex); + auto it = m_store_buffer.find(loc); + TORRENT_ASSERT(it != m_store_buffer.end()); + m_store_buffer.erase(it); + } + + std::size_t size() const + { + return m_store_buffer.size(); + } + +private: + + mutable std::mutex m_mutex; + std::unordered_map m_store_buffer; +}; + +} +} + +#endif + diff --git a/include/libtorrent/aux_/string_ptr.hpp b/include/libtorrent/aux_/string_ptr.hpp new file mode 100644 index 0000000..10da30c --- /dev/null +++ b/include/libtorrent/aux_/string_ptr.hpp @@ -0,0 +1,76 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STRING_PTR_HPP_INCLUDED +#define TORRENT_STRING_PTR_HPP_INCLUDED + +#include "libtorrent/string_view.hpp" + +namespace libtorrent { +namespace aux { + + struct string_ptr + { + explicit string_ptr(string_view str) : m_ptr(new char[str.size() + 1]) + { + std::copy(str.begin(), str.end(), m_ptr); + m_ptr[str.size()] = '\0'; + } + ~string_ptr() + { + delete[] m_ptr; + } + string_ptr(string_ptr&& rhs) + : m_ptr(rhs.m_ptr) + { + rhs.m_ptr = nullptr; + } + string_ptr& operator=(string_ptr&& rhs) + { + if (&rhs == this) return *this; + delete[] m_ptr; + m_ptr = rhs.m_ptr; + rhs.m_ptr = nullptr; + return *this; + } + string_ptr(string_ptr const& rhs) = delete; + string_ptr& operator=(string_ptr const& rhs) = delete; + char const* operator*() const { return m_ptr; } + private: + char* m_ptr; + }; + +} +} + +#endif + diff --git a/include/libtorrent/aux_/strview_less.hpp b/include/libtorrent/aux_/strview_less.hpp new file mode 100644 index 0000000..57d9e75 --- /dev/null +++ b/include/libtorrent/aux_/strview_less.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2016, 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STRLESS_HPP_INCLUDED +#define TORRENT_STRLESS_HPP_INCLUDED + +#include + +namespace libtorrent { +namespace aux { + // this enables us to compare a string_view against the std::string that's + // held by the std::map + struct strview_less + { + using is_transparent = std::true_type; + template + bool operator()(T1 const& rhs, T2 const& lhs) const + { return rhs < lhs; } + }; +} +} + +#endif diff --git a/include/libtorrent/aux_/suggest_piece.hpp b/include/libtorrent/aux_/suggest_piece.hpp new file mode 100644 index 0000000..f0cbe02 --- /dev/null +++ b/include/libtorrent/aux_/suggest_piece.hpp @@ -0,0 +1,128 @@ +/* + +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2016, 2018, 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SUGGEST_PIECE_HPP_INCLUDE +#define TORRENT_SUGGEST_PIECE_HPP_INCLUDE + +#include +#include + +#include "libtorrent/bitfield.hpp" +#include "libtorrent/sliding_average.hpp" +#include "libtorrent/aux_/vector.hpp" + +namespace libtorrent { namespace aux { + +struct suggest_piece +{ + // pick at most n piece indices that are _not_ in p (which represents + // pieces the peer has already sent a suggest for) nor in bits (which are + // pieces the peer already has, and should not be suggested) + int get_pieces(std::vector& p + , typed_bitfield const& bits + , int n) + { + if (m_priority_pieces.empty()) return 0; + + int ret = 0; + + // the highest priority pieces are at the end of m_priority_pieces. + // if we add any piece to the result (p), the farther back the better. + // the prioritization in p is the same, which means we have to first push + // back and then reverse the items we put there. + for (int i = int(m_priority_pieces.size()) - 1; i >= 0; --i) + { + piece_index_t const piece = m_priority_pieces[i]; + if (bits.get_bit(piece)) continue; + if (std::any_of(p.begin(), p.end() - ret + , [piece](piece_index_t pi) { return pi == piece; })) + continue; + + p.push_back(piece); + ++ret; + --n; + if (n == 0) break; + } + + // this it to maintain a strict priority order of pieces. The farther + // back, the higher priority + std::reverse(p.end() - ret, p.end()); + + return ret; + } + + void add_piece(piece_index_t const index, int const availability + , int const max_queue_size) + { + // keep a running average of the availability of pieces, and filter + // anything above average. + int const mean = m_availability.mean(); + m_availability.add_sample(availability); + + if (availability > mean) return; + + auto const it = std::find(m_priority_pieces.begin() + , m_priority_pieces.end(), index); + + if (it != m_priority_pieces.end()) + { + // increase the priority of this piece by moving it to the front + // of the queue + m_priority_pieces.erase(it); + } + + if (int(m_priority_pieces.size()) >= max_queue_size) + { + int const to_remove = int(m_priority_pieces.size()) - max_queue_size + 1; + m_priority_pieces.erase(m_priority_pieces.begin() + , m_priority_pieces.begin() + to_remove); + } + + m_priority_pieces.push_back(index); + } + +private: + + // these are pieces that would be good candidates for suggesting + // to a peer. They represent low availability pieces that we recently + // read from disk (and are likely in our read cache). + // pieces closer to the end were inserted into the cache more recently and + // have higher priority + vector m_priority_pieces; + + sliding_average m_availability; +}; + +}} + +#endif diff --git a/include/libtorrent/aux_/throw.hpp b/include/libtorrent/aux_/throw.hpp new file mode 100644 index 0000000..5d47131 --- /dev/null +++ b/include/libtorrent/aux_/throw.hpp @@ -0,0 +1,54 @@ +/* + +Copyright (c) 2017, 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_THROW_HPP_INCLUDED +#define TORRENT_THROW_HPP_INCLUDED + +#include // for forward() + +#include "libtorrent/config.hpp" + +namespace libtorrent { namespace aux { + + template +#ifdef BOOST_NO_EXCEPTIONS + [[noreturn]] void throw_ex(Args&&...) { + std::terminate(); + } +#else + [[noreturn]] void throw_ex(Args&&... args) { + throw T(std::forward(args)...); + } +#endif +}} + +#endif diff --git a/include/libtorrent/aux_/time.hpp b/include/libtorrent/aux_/time.hpp new file mode 100644 index 0000000..c9d970e --- /dev/null +++ b/include/libtorrent/aux_/time.hpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2015, 2017, 2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_AUX_TIME_HPP +#define TORRENT_AUX_TIME_HPP + +#include "libtorrent/config.hpp" +#include "libtorrent/time.hpp" + +#include // for std::time_t + +namespace libtorrent { namespace aux { + + // returns the current time, as represented by time_point. The + // resolution of this timer is about 100 ms. + TORRENT_EXTRA_EXPORT time_point time_now(); + TORRENT_EXTRA_EXPORT time_point32 time_now32(); + + TORRENT_EXTRA_EXPORT std::time_t to_time_t(time_point32 tp); + TORRENT_EXTRA_EXPORT time_point32 from_time_t(std::time_t t); + + // returns the current posix time (UTC) + TORRENT_EXTRA_EXPORT time_t posix_time(); +} } + +#endif diff --git a/include/libtorrent/aux_/timestamp_history.hpp b/include/libtorrent/aux_/timestamp_history.hpp new file mode 100644 index 0000000..d6d6722 --- /dev/null +++ b/include/libtorrent/aux_/timestamp_history.hpp @@ -0,0 +1,88 @@ +/* + +Copyright (c) 2010-2012, 2014-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TIMESTAMP_HISTORY_HPP +#define TIMESTAMP_HISTORY_HPP + +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent { +namespace aux { + +// timestamp history keeps a history of the lowest timestamps we've +// seen in the last 20 minutes +struct TORRENT_EXTRA_EXPORT timestamp_history +{ + static constexpr int history_size = 20; + + timestamp_history() = default; + bool initialized() const { return m_num_samples != not_initialized; } + + // add a sample to the timestamp history. If step is true, it's been + // a minute since the last step + std::uint32_t add_sample(std::uint32_t sample, bool step); + std::uint32_t base() const { TORRENT_ASSERT(initialized()); return m_base; } + void adjust_base(int change); + +private: + + // this is a circular buffer + std::array m_history; + + // this is the lowest sample seen in the + // last 'history_size' minutes + std::uint32_t m_base = 0; + + // and this is the index we're currently at + // in the circular buffer + std::uint16_t m_index = 0; + + static constexpr std::uint16_t not_initialized = 0xffff; + + // this is the number of samples since the + // last time we stepped one minute. If we + // don't have enough samples, we won't step + // if this is set to 'not_initialized' we + // have bit seen any samples at all yet + // and m_base is not initialized yet + std::uint16_t m_num_samples = not_initialized; +}; + +} +} + +#endif + diff --git a/include/libtorrent/aux_/torrent_impl.hpp b/include/libtorrent/aux_/torrent_impl.hpp new file mode 100644 index 0000000..7f16bc7 --- /dev/null +++ b/include/libtorrent/aux_/torrent_impl.hpp @@ -0,0 +1,80 @@ +/* + +Copyright (c) 2017, 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_IMPL_HPP_INCLUDED +#define TORRENT_TORRENT_IMPL_HPP_INCLUDED + +// this is not a normal header, it's just this template, to be able to call this +// function from more than one translation unit. But it's still internal + +namespace libtorrent { + + template + void torrent::wrap(Fun f, Args&&... a) +#ifndef BOOST_NO_EXCEPTIONS + try +#endif + { + (this->*f)(std::forward(a)...); + } +#ifndef BOOST_NO_EXCEPTIONS + catch (system_error const& e) { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("EXCEPTION: (%d %s) %s" + , e.code().value() + , e.code().message().c_str() + , e.what()); +#endif + alerts().emplace_alert(get_handle() + , e.code(), e.what()); + pause(); + } catch (std::exception const& e) { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("EXCEPTION: %s", e.what()); +#endif + alerts().emplace_alert(get_handle() + , error_code(), e.what()); + pause(); + } catch (...) { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("EXCEPTION: unknown"); +#endif + alerts().emplace_alert(get_handle() + , error_code(), "unknown error"); + pause(); + } +#endif + +} + +#endif + diff --git a/include/libtorrent/aux_/torrent_list.hpp b/include/libtorrent/aux_/torrent_list.hpp new file mode 100644 index 0000000..5dc2573 --- /dev/null +++ b/include/libtorrent/aux_/torrent_list.hpp @@ -0,0 +1,267 @@ +/* + +Copyright (c) 2018, Steven Siloti +Copyright (c) 2019-2022, Arvid Norberg +Copyright (c) 2019, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_LIST_HPP_INCLUDED +#define TORRENT_TORRENT_LIST_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/info_hash.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/aux_/array.hpp" +#include "libtorrent/aux_/invariant_check.hpp" +#include "libtorrent/aux_/scope_end.hpp" + +#include // for shared_ptr +#include +#include + +#if TORRENT_USE_INVARIANT_CHECKS +#include +#endif + +namespace libtorrent { +namespace aux { + +template +struct torrent_list +{ + // These are non-owning pointers. Lifetime is managed by the `torrent_array` + using torrent_map = std::unordered_map; + using torrent_array = std::vector>; + + using iterator = typename torrent_array::iterator; + using const_iterator = typename torrent_array::const_iterator; + + using value_type = typename torrent_array::value_type; + + bool empty() const { return m_array.empty(); } + + iterator begin() { return m_array.begin(); } + iterator end() { return m_array.end(); } + + const_iterator begin() const { return m_array.begin(); } + const_iterator end() const { return m_array.end(); } + + std::size_t size() const { return m_array.size(); } + + T* operator[](std::size_t const idx) + { + TORRENT_ASSERT(idx < m_array.size()); + return m_array[idx].get(); + } + + T const* operator[](std::size_t const idx) const + { + TORRENT_ASSERT(idx < m_array.size()); + return m_array[idx].get(); + } + + bool insert(info_hash_t const& ih, std::shared_ptr t) + { + INVARIANT_CHECK; + TORRENT_ASSERT(t); + + bool duplicate = false; + ih.for_each([&](sha1_hash const& hash, protocol_version) + { + if (m_index.find(hash) != m_index.end()) duplicate = true; + }); + + // if we already have a torrent with this hash, don't do anything + if (duplicate) return false; + + aux::array rollback({ false, false, false, false}); + auto abort_add = aux::scope_end([&] + { + ih.for_each([&](sha1_hash const& hash, protocol_version const v) + { + if (rollback[int(v)]) + m_index.erase(hash); + +#if !defined TORRENT_DISABLE_ENCRYPTION + if (rollback[2 + int(v)]) + { + static char const req2[4] = { 'r', 'e', 'q', '2' }; + hasher h(req2); + h.update(hash); + m_obfuscated_index.erase(h.final()); + } +#endif + }); + }); + + ih.for_each([&](sha1_hash const& hash, protocol_version const v) + { + if (m_index.insert({hash, t.get()}).second) + rollback[int(v)] = true; + +#if !defined TORRENT_DISABLE_ENCRYPTION + static char const req2[4] = { 'r', 'e', 'q', '2' }; + hasher h(req2); + h.update(hash); + // this is SHA1("req2" + info-hash), used for + // encrypted hand shakes + if (m_obfuscated_index.insert({h.final(), t.get()}).second) + rollback[2 + int(v)] = true; +#endif + }); + + m_array.emplace_back(std::move(t)); + + abort_add.disarm(); + + return true; + } + +#if !defined TORRENT_DISABLE_ENCRYPTION + T* find_obfuscated(sha1_hash const& ih) + { + auto const i = m_obfuscated_index.find(ih); + if (i == m_obfuscated_index.end()) return nullptr; + return i->second; + } +#endif + + T* find(sha1_hash const& ih) const + { + auto const i = m_index.find(ih); + if (i == m_index.end()) return nullptr; + return i->second; + } + + bool erase(info_hash_t const& ih) + { + INVARIANT_CHECK; + + T* found = nullptr; + ih.for_each([&](sha1_hash const& hash, protocol_version) + { + auto const i = m_index.find(hash); + if (i != m_index.end()) + { + TORRENT_ASSERT(found == nullptr || found == i->second); + found = i->second; + m_index.erase(i); + } + +#if !defined TORRENT_DISABLE_ENCRYPTION + static char const req2[4] = { 'r', 'e', 'q', '2' }; + hasher h(req2); + h.update(hash); + m_obfuscated_index.erase(h.final()); +#endif + }); + if (!found) return false; + + auto const array_iter = std::find_if(m_array.begin(), m_array.end() + , [&](std::shared_ptr const& p) { return p.get() == found; }); + TORRENT_ASSERT(array_iter != m_array.end()); + + TORRENT_ASSERT(m_index.find(ih.v1) == m_index.end()); + + if (array_iter != m_array.end() - 1) + std::swap(*array_iter, m_array.back()); + + // This is where we, potentially, remove the last reference + m_array.pop_back(); + + return true; + } + + void clear() + { + INVARIANT_CHECK; + + m_array.clear(); + m_index.clear(); +#if !defined TORRENT_DISABLE_ENCRYPTION + m_obfuscated_index.clear(); +#endif + } + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const + { +#ifndef TORRENT_EXPENSIVE_INVARIANT_CHECKS + // if we have many torrents, this would be an expensive + // invariant check, so don't run it in that case (unless we + // enabled expensive invariant checks) + if (m_array.size() > 100) return; +#endif + std::set all_torrents; + std::set all_indexed_torrents; +#if !defined TORRENT_DISABLE_ENCRYPTION + std::set all_obf_indexed_torrents; +#endif + + for (auto const& t : m_array) + all_torrents.insert(t.get()); + + for (auto const& t : m_index) + { + all_indexed_torrents.insert(t.second); + } +#if !defined TORRENT_DISABLE_ENCRYPTION + for (auto const& t : m_obfuscated_index) + { + all_obf_indexed_torrents.insert(t.second); + } +#endif + + TORRENT_ASSERT(all_torrents == all_indexed_torrents); +#if !defined TORRENT_DISABLE_ENCRYPTION + TORRENT_ASSERT(all_torrents == all_obf_indexed_torrents); +#endif + } +#endif + +private: + + torrent_array m_array; + + torrent_map m_index; + +#if !defined TORRENT_DISABLE_ENCRYPTION + // this maps obfuscated hashes to torrents. It's only + // used when encryption is enabled + torrent_map m_obfuscated_index; +#endif +}; + +} +} + +#endif + diff --git a/include/libtorrent/aux_/unique_ptr.hpp b/include/libtorrent/aux_/unique_ptr.hpp new file mode 100644 index 0000000..d7e6cca --- /dev/null +++ b/include/libtorrent/aux_/unique_ptr.hpp @@ -0,0 +1,66 @@ +/* + +Copyright (c) 2017, Alden Torres +Copyright (c) 2018-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UNIQUE_PTR_HPP +#define TORRENT_UNIQUE_PTR_HPP + +#include +#include + +#include "libtorrent/units.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent { namespace aux { + + template + struct unique_ptr; + + template + struct unique_ptr : std::unique_ptr + { + using base = std::unique_ptr; + using underlying_index = typename underlying_index_t::type; + + unique_ptr() = default; + explicit unique_ptr(T* arr) : base(arr) {} + + decltype(auto) operator[](IndexType idx) const + { + TORRENT_ASSERT(idx >= IndexType(0)); + return this->base::operator[](std::size_t(static_cast(idx))); + } + }; + +}} + +#endif diff --git a/include/libtorrent/aux_/utp_socket_manager.hpp b/include/libtorrent/aux_/utp_socket_manager.hpp new file mode 100644 index 0000000..37169f7 --- /dev/null +++ b/include/libtorrent/aux_/utp_socket_manager.hpp @@ -0,0 +1,203 @@ +/* + +Copyright (c) 2010, 2012-2021, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2016-2017, Steven Siloti +Copyright (c) 2017, Andrei Kurushin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UTP_SOCKET_MANAGER_HPP_INCLUDED +#define TORRENT_UTP_SOCKET_MANAGER_HPP_INCLUDED + +#include +#include + +#include "libtorrent/aux_/socket_type.hpp" +#include "libtorrent/session_status.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/aux_/packet_pool.hpp" + +namespace libtorrent { + +struct counters; + +namespace aux { + + struct utp_stream; + struct utp_socket_impl; + + // interface/handle to the underlying udp socket + struct TORRENT_EXTRA_EXPORT utp_socket_interface + { + virtual udp::endpoint get_local_endpoint() = 0; + protected: + virtual ~utp_socket_interface() = default; + }; + + struct utp_socket_manager + { + using send_fun_t = std::function + , udp::endpoint const& + , span + , error_code&, udp_send_flags_t)>; + + using incoming_utp_callback_t = std::function; + + utp_socket_manager(send_fun_t send_fun + , incoming_utp_callback_t cb + , io_context& ios + , aux::session_settings const& sett + , counters& cnt, void* ssl_context); + ~utp_socket_manager(); + + // return false if this is not a uTP packet + bool incoming_packet(std::weak_ptr socket + , udp::endpoint const& ep, span p); + + // if the UDP socket failed with an EAGAIN or EWOULDBLOCK, this will be + // called once the socket is writeable again + void writable(); + + // when the upper layer has drained the underlying UDP socket, this is + // called, and uTP sockets will send their ACKs. This ensures ACKs at + // least coalesce packets returned during the same wakeup + void socket_drained(); + + void tick(time_point now); + + void send_packet(std::weak_ptr sock, udp::endpoint const& ep + , char const* p, int len + , error_code& ec, udp_send_flags_t flags = {}); + void subscribe_writable(utp_socket_impl* s); + + void remove_udp_socket(std::weak_ptr sock); + + // internal, used by utp_stream + void remove_socket(std::uint16_t id); + + utp_socket_impl* new_utp_socket(utp_stream* str); + int gain_factor() const { return m_sett.get_int(settings_pack::utp_gain_factor); } + int target_delay() const { return m_sett.get_int(settings_pack::utp_target_delay) * 1000; } + int syn_resends() const { return m_sett.get_int(settings_pack::utp_syn_resends); } + int fin_resends() const { return m_sett.get_int(settings_pack::utp_fin_resends); } + int num_resends() const { return m_sett.get_int(settings_pack::utp_num_resends); } + int connect_timeout() const { return m_sett.get_int(settings_pack::utp_connect_timeout); } + int min_timeout() const { return m_sett.get_int(settings_pack::utp_min_timeout); } + int loss_multiplier() const { return m_sett.get_int(settings_pack::utp_loss_multiplier); } + int cwnd_reduce_timer() const { return m_sett.get_int(settings_pack::utp_cwnd_reduce_timer); } + + int mtu_for_dest(address const& addr) const; + int num_sockets() const { return int(m_utp_sockets.size()); } + + void defer_ack(utp_socket_impl* s); + void cancel_deferred_ack(utp_socket_impl* s); + void subscribe_drained(utp_socket_impl* s); + + void restrict_mtu(int const mtu) + { + m_restrict_mtu[std::size_t(m_mtu_idx)] = mtu; + m_mtu_idx = (m_mtu_idx + 1) % int(m_restrict_mtu.size()); + } + + int restrict_mtu() const + { + return *std::max_element(m_restrict_mtu.begin(), m_restrict_mtu.end()); + } + + // used to keep stats of uTP events + // the counter is the enum from ``counters``. + void inc_stats_counter(int counter, int delta = 1); + + aux::packet_ptr acquire_packet(int const allocate) { return m_packet_pool.acquire(allocate); } + void release_packet(aux::packet_ptr p) { m_packet_pool.release(std::move(p)); } + void decay() { m_packet_pool.decay(); } + + // explicitly disallow assignment, to silence msvc warning + utp_socket_manager& operator=(utp_socket_manager const&) = delete; + + private: + + send_fun_t m_send_fun; + incoming_utp_callback_t m_cb; + + // replace with a hash-map + using socket_map_t = std::multimap>; + socket_map_t m_utp_sockets; + + using socket_vector_t = std::vector; + + // if this is set, it means this socket still needs to send an ACK. Once + // we exit the loop processing packets, or switch to processing packets + // for a different socket, issue the ACK packet and clear this. + utp_socket_impl* m_deferred_ack = nullptr; + + // storage used for saving cpu time on "push_back" + // by using already pre-allocated vector + socket_vector_t m_temp_sockets; + + // sockets that have received or sent packets this + // round, may subscribe to the event of draining the + // UDP socket. At that point they may call the + // user callback function to indicate bytes have been + // sent or received. + socket_vector_t m_drained_event; + + // list of sockets that received EWOULDBLOCK from the + // underlying socket. They are notified when the socket + // becomes writable again + socket_vector_t m_stalled_sockets; + + // the last socket we received a packet on + utp_socket_impl* m_last_socket = nullptr; + + int m_new_connection = -1; + + aux::session_settings const& m_sett; + + // stats counters + counters& m_counters; + + io_context& m_ios; + + std::array m_restrict_mtu; + int m_mtu_idx = 0; + + // this is passed on to the instantiate connection + // if this is non-nullptr it will create SSL connections over uTP + void* m_ssl_context; + + aux::packet_pool m_packet_pool; + }; +} +} + +#endif diff --git a/include/libtorrent/aux_/utp_stream.hpp b/include/libtorrent/aux_/utp_stream.hpp new file mode 100644 index 0000000..f7ce9c8 --- /dev/null +++ b/include/libtorrent/aux_/utp_stream.hpp @@ -0,0 +1,1063 @@ +/* + +Copyright (c) 2010-2021, Arvid Norberg +Copyright (c) 2015-2018, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UTP_STREAM_HPP_INCLUDED +#define TORRENT_UTP_STREAM_HPP_INCLUDED + +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/aux_/packet_buffer.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/close_reason.hpp" +#include "libtorrent/aux_/timestamp_history.hpp" +#include "libtorrent/sliding_average.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/aux_/invariant_check.hpp" +#include "libtorrent/aux_/storage_utils.hpp" // for iovec_t + +#include + +#ifndef BOOST_NO_EXCEPTIONS +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + +namespace libtorrent { +namespace aux { + +#ifndef TORRENT_UTP_LOG_ENABLE + #define TORRENT_UTP_LOG 0 + #define TORRENT_VERBOSE_UTP_LOG 0 +#else + #define TORRENT_UTP_LOG 1 + #define TORRENT_VERBOSE_UTP_LOG 1 +#endif + +#if TORRENT_UTP_LOG + TORRENT_FORMAT(1, 2) + void utp_log(char const* fmt, ...); + TORRENT_EXPORT bool is_utp_stream_logging(); + + // This function should be used at the very beginning and very end of your program. + TORRENT_EXPORT void set_utp_stream_logging(bool enable); +#endif + + TORRENT_EXTRA_EXPORT bool compare_less_wrap(std::uint32_t lhs + , std::uint32_t rhs, std::uint32_t mask); + + struct utp_socket_manager; + + // internal: the point of the bif_endian_int is two-fold + // one purpose is to not have any alignment requirements + // so that any buffer received from the network can be cast + // to it and read as an integer of various sizes without + // triggering a bus error. The other purpose is to convert + // from network byte order to host byte order when read and + // written, to offer a convenient interface to both interpreting + // and writing network packets + template struct big_endian_int + { + big_endian_int& operator=(T v) & + { + char* p = m_storage; + aux::write_impl(v, p); + return *this; + } + operator T() const + { + const char* p = m_storage; + return aux::read_impl(p, aux::type()); + } + private: + char m_storage[sizeof(T)]; + }; + + using be_uint64 = big_endian_int; + using be_uint32 = big_endian_int; + using be_uint16 = big_endian_int; + using be_int64 = big_endian_int; + using be_int32 = big_endian_int; + using be_int16 = big_endian_int; + +/* + uTP header from BEP 29 + + 0 4 8 16 24 32 + +-------+-------+---------------+---------------+---------------+ + | type | ver | extension | connection_id | + +-------+-------+---------------+---------------+---------------+ + | timestamp_microseconds | + +---------------+---------------+---------------+---------------+ + | timestamp_difference_microseconds | + +---------------+---------------+---------------+---------------+ + | wnd_size | + +---------------+---------------+---------------+---------------+ + | seq_nr | ack_nr | + +---------------+---------------+---------------+---------------+ + +*/ + +// internal: the different kinds of uTP packets +enum utp_socket_state_t : std::uint8_t +{ ST_DATA, ST_FIN, ST_STATE, ST_RESET, ST_SYN, NUM_TYPES }; + +// internal: extension headers. 2 is skipped because there is a deprecated +// extension with that number in the wild +enum utp_extensions_t : std::uint8_t +{ utp_no_extension = 0, utp_sack = 1, utp_close_reason = 3 }; + +struct utp_header +{ + std::uint8_t type_ver; + std::uint8_t extension; + be_uint16 connection_id; + be_uint32 timestamp_microseconds; + be_uint32 timestamp_difference_microseconds; + be_uint32 wnd_size; + be_uint16 seq_nr; + be_uint16 ack_nr; + + int get_type() const { return type_ver >> 4; } + int get_version() const { return type_ver & 0xf; } +}; + +struct utp_socket_impl; +struct utp_socket_interface; +struct utp_stream; + +// this is the user-level stream interface to utp sockets. +// the reason why it's split up in a utp_stream class and +// an implementation class is because the socket state has +// to be able to out-live the user level socket. For instance +// when sending data on a stream and then closing it, the +// state holding the send buffer has to be kept around until +// it has been flushed, which may be longer than the client +// will keep the utp_stream object around for. +// for more details, see utp_socket_impl, which is analogous +// to the kernel state for a socket. It's defined in utp_stream.cpp +struct TORRENT_EXTRA_EXPORT utp_stream +{ + using lowest_layer_type = utp_stream ; + using endpoint_type = tcp::socket::endpoint_type; + using protocol_type = tcp::socket::protocol_type; + + using executor_type = tcp::socket::executor_type; + executor_type get_executor() { return m_io_service.get_executor(); } + + explicit utp_stream(io_context& io_context); + ~utp_stream(); + utp_stream& operator=(utp_stream const&) = delete; + utp_stream(utp_stream const&) = delete; + utp_stream& operator=(utp_stream&&) noexcept = delete; + utp_stream(utp_stream&&) noexcept; + + lowest_layer_type& lowest_layer() { return *this; } + lowest_layer_type const& lowest_layer() const { return *this; } + + // used for incoming connections + void set_impl(utp_socket_impl*); + utp_socket_impl* get_impl(); + +#ifndef BOOST_NO_EXCEPTIONS + template + void io_control(IO_Control_Command&) {} +#endif + + template + void io_control(IO_Control_Command&, error_code&) {} + +#ifndef BOOST_NO_EXCEPTIONS + void non_blocking(bool) {} +#endif + + void non_blocking(bool, error_code&) {} + +#ifndef BOOST_NO_EXCEPTIONS + void bind(endpoint_type const& /*endpoint*/) {} +#endif + + void bind(endpoint_type const&, error_code&); + +#ifndef BOOST_NO_EXCEPTIONS + template + void set_option(SettableSocketOption const&) {} +#endif + + template + void set_option(SettableSocketOption const&, error_code&) { } + +#ifndef BOOST_NO_EXCEPTIONS + template + void get_option(GettableSocketOption&) {} +#endif + + template + void get_option(GettableSocketOption&, error_code&) {} + + void cancel(error_code&) + { + cancel_handlers(boost::asio::error::operation_aborted); + } + + void close(); + void close(error_code const&) { close(); } + + void set_close_reason(close_reason_t code); + close_reason_t get_close_reason() const; + + bool is_open() const { return m_open; } + + int read_buffer_size() const; + static void on_read(utp_stream* self, std::size_t bytes_transferred + , error_code const& ec, bool shutdown); + static void on_write(utp_stream* self, std::size_t bytes_transferred + , error_code const& ec, bool shutdown); + static void on_connect(utp_stream* self, error_code const& ec, bool shutdown); + static void on_close_reason(utp_stream* self, close_reason_t reason); + static void on_writeable(utp_stream* self, error_code const& ec); + + void add_read_buffer(void* buf, int len); + void issue_read(); + void add_write_buffer(void const* buf, int len); + bool check_fin_sent() const; + void issue_write(); + void subscribe_writeable(); + std::size_t read_some(bool clear_buffers, error_code& ec); + std::size_t write_some(bool clear_buffers); + + int send_delay() const; + int recv_delay() const; + + void do_connect(tcp::endpoint const& ep); + + endpoint_type local_endpoint() const + { + error_code ec; + return local_endpoint(ec); + } + + endpoint_type local_endpoint(error_code& ec) const; + + endpoint_type remote_endpoint() const + { + error_code ec; + return remote_endpoint(ec); + } + + endpoint_type remote_endpoint(error_code& ec) const; + + std::size_t available() const; + std::size_t available(error_code& /*ec*/) const { return available(); } + + template + void async_connect(endpoint_type const& endpoint, Handler handler) + { + if (m_impl == nullptr) + { + post(m_io_service, std::bind(std::move(handler), boost::asio::error::not_connected)); + return; + } + + m_connect_handler = std::move(handler); + do_connect(endpoint); + } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler handler) + { + if (m_impl == nullptr) + { + post(m_io_service, std::bind(std::move(handler), boost::asio::error::not_connected, std::size_t(0))); + return; + } + + TORRENT_ASSERT(!m_read_handler); + if (m_read_handler) + { + post(m_io_service, std::bind(std::move(handler), boost::asio::error::operation_not_supported, std::size_t(0))); + return; + } + std::size_t bytes_added = 0; + for (auto i = buffer_sequence_begin(buffers) + , end(buffer_sequence_end(buffers)); i != end; ++i) + { + if (i->size() == 0) continue; + add_read_buffer(i->data(), int(i->size())); + bytes_added += i->size(); + } + if (bytes_added == 0) + { + // if we're reading 0 bytes, post handler immediately + // asio's SSL layer depends on this behavior + post(m_io_service, std::bind(std::move(handler), error_code(), std::size_t(0))); + return; + } + + m_read_handler = std::move(handler); + issue_read(); + } + + template + void async_wait_read(Handler handler) + { + if (m_impl == nullptr) + { + post(m_io_service, std::bind(std::move(handler), boost::asio::error::not_connected, std::size_t(0))); + return; + } + + TORRENT_ASSERT(!m_read_handler); + if (m_read_handler) + { + post(m_io_service, std::bind(std::move(handler), boost::asio::error::operation_not_supported, std::size_t(0))); + return; + } + m_read_handler = std::move(handler); + issue_read(); + } + + template + void open(Protocol const&, error_code&) + { m_open = true; } + + template + void open(Protocol const&) + { m_open = true; } + + template + std::size_t read_some(Mutable_Buffers const& buffers, error_code& ec) + { + TORRENT_ASSERT(!m_read_handler); + if (m_impl == nullptr) + { + ec = boost::asio::error::not_connected; + return 0; + } + + if (read_buffer_size() == 0) + { + ec = boost::asio::error::would_block; + return 0; + } +#if TORRENT_USE_ASSERTS + size_t buf_size = 0; +#endif + + for (auto i = buffer_sequence_begin(buffers) + , end(buffer_sequence_end(buffers)); i != end; ++i) + { + add_read_buffer(i->data(), int(i->size())); +#if TORRENT_USE_ASSERTS + buf_size += i->size(); +#endif + } + std::size_t ret = read_some(true, ec); + TORRENT_ASSERT(ret <= buf_size); + TORRENT_ASSERT(ret > 0); + return ret; + } + + template + std::size_t write_some(Const_Buffers const& buffers, error_code& ec) + { + TORRENT_ASSERT(!m_write_handler); + if (m_impl == nullptr) + { + ec = boost::asio::error::not_connected; + return 0; + } + + if (check_fin_sent()) + { + // we can't send more data after closing the socket + ec = boost::asio::error::broken_pipe; + return 0; + } + +#if TORRENT_USE_ASSERTS + size_t buf_size = 0; +#endif + + for (auto i = buffer_sequence_begin(buffers) + , end(buffer_sequence_end(buffers)); i != end; ++i) + { + add_write_buffer(i->data(), int(i->size())); +#if TORRENT_USE_ASSERTS + buf_size += i->size(); +#endif + } + std::size_t ret = write_some(true); + TORRENT_ASSERT(ret <= buf_size); + if(ret == 0) + { + ec = boost::asio::error::would_block; + return 0; + } + return ret; + } + +#ifndef BOOST_NO_EXCEPTIONS + template + std::size_t read_some(Mutable_Buffers const& buffers) + { + error_code ec; + std::size_t ret = read_some(buffers, ec); + if (ec) + boost::throw_exception(boost::system::system_error(ec)); + return ret; + } + + template + std::size_t write_some(Const_Buffers const& buffers) + { + error_code ec; + std::size_t ret = write_some(buffers, ec); + if (ec) + boost::throw_exception(boost::system::system_error(ec)); + return ret; + } +#endif + + template + void async_write_some(Const_Buffers const& buffers, Handler handler) + { + if (m_impl == nullptr) + { + post(m_io_service, std::bind(std::move(handler) + , boost::asio::error::not_connected, std::size_t(0))); + return; + } + + TORRENT_ASSERT(!m_write_handler); + if (m_write_handler) + { + post(m_io_service, std::bind(std::move(handler) + , boost::asio::error::operation_not_supported, std::size_t(0))); + return; + } + + if (check_fin_sent()) + { + // we can't send more data after closing the socket + post(m_io_service, std::bind(std::move(handler) + , boost::asio::error::broken_pipe, std::size_t(0))); + return; + } + + std::size_t bytes_added = 0; + for (auto i = buffer_sequence_begin(buffers) + , end(buffer_sequence_end(buffers)); i != end; ++i) + { + if (i->size() == 0) continue; + add_write_buffer(i->data(), int(i->size())); + bytes_added += i->size(); + } + if (bytes_added == 0) + { + // if we're writing 0 bytes, post handler immediately + // asio's SSL layer depends on this behavior + post(m_io_service, std::bind(std::move(handler), error_code(), std::size_t(0))); + return; + } + m_write_handler = std::move(handler); + issue_write(); + } + + template + void async_wait_write(Handler handler) + { + if (m_impl == nullptr) + { + post(m_io_service, std::bind(std::move(handler) + , boost::asio::error::not_connected)); + return; + } + + TORRENT_ASSERT(!m_writeable_handler); + if (m_writeable_handler) + { + post(m_io_service, std::bind(std::move(handler) + , boost::asio::error::operation_not_supported)); + return; + } + + if (check_fin_sent()) + { + // we can't send more data after closing the socket + post(m_io_service, std::bind(std::move(handler) + , boost::asio::error::broken_pipe)); + return; + } + m_writeable_handler = std::move(handler); + subscribe_writeable(); + } + +#if BOOST_VERSION >= 106600 + // Compatibility with the async_wait method introduced in boost 1.66 + + enum wait_type { wait_read, wait_write, wait_error }; + + template + void async_wait(wait_type type, Handler handler) { + switch(type) + { + case wait_read: + async_wait_read([handler](error_code ec, size_t) { handler(std::move(ec)); }); + break; + + case wait_write: + async_wait_write(std::move(handler)); + break; + + case wait_error: + post(m_io_service, std::bind(std::move(handler) + , boost::asio::error::operation_not_supported)); + break; + } + } +#endif + +private: + + void cancel_handlers(error_code const&); + + std::function m_connect_handler; + std::function m_read_handler; + std::function m_write_handler; + std::function m_writeable_handler; + + io_context& m_io_service; + utp_socket_impl* m_impl; + + close_reason_t m_incoming_close_reason = close_reason_t::none; + + // this field requires another 8 bytes (including padding) + bool m_open; +}; + +// since the uTP socket state may be needed after the +// utp_stream is closed, it's kept in a separate struct +// whose lifetime is not tied to the lifetime of utp_stream + +// the utp socket is closely modelled after the asio async +// operations and handler model. For writing to the socket, +// the client provides a list of buffers (for gather/writev +// style of I/O) and whenever the socket can write another +// packet to the stream, it picks up data from these buffers. +// When all of the data has been written, or enough time has +// passed since we first started writing, the write handler +// is called and the write buffer is reset. This means that +// we're not writing anything at all while waiting for the +// client to re-issue a write request. + +// reading is a little bit more complicated, since we must +// be able to receive data even when the user doesn't have +// an outstanding read operation on the socket. When the user +// does however, we want to receive data directly into the +// user's buffer instead of first copying it into our receive +// buffer. This is why the receive case is more complicated. +// There are two receive buffers. One provided by the user, +// which when present is always used. The other one is used +// when the user doesn't have an outstanding read request, +// and hence hasn't provided any buffer space to receive into. + +// the user provided read buffer is called "m_read_buffer" and +// its size is "m_read_buffer_size". The buffer we spill over +// into when the user provided buffer is full or when there +// is none, is "m_receive_buffer" and "m_receive_buffer_size" +// respectively. + +// in order to know when to trigger the read and write handlers +// there are two counters, m_read and m_written, which count +// the number of bytes we've stuffed into the user provided +// read buffer or written to the stream from the write buffer. +// These are used to trigger the handlers if we're written a +// large number of bytes. It's also triggered if we're filled +// the whole read buffer, or written the entire write buffer. +// The last way the handlers can be triggered is if we're read +// or written some, and enough time has elapsed since then. + +// when we receive data into m_receive_buffer (i.e. the buffer +// used when there's no user provided one) is stored as a +// number of heap allocated packets. This is just because it's +// simple to reuse the data structured and it provides all the +// functionality needed for this buffer. + +struct utp_socket_impl +{ +#if TORRENT_USE_INVARIANT_CHECKS + friend struct ::libtorrent::invariant_access; +#endif + + utp_socket_impl(std::uint16_t recv_id, std::uint16_t send_id + , utp_stream* userdata, utp_socket_manager& sm); + + ~utp_socket_impl(); + + void tick(time_point now); + void init_mtu(int mtu); + bool incoming_packet(span buf + , udp::endpoint const& ep, time_point receive_time); + void writable(); + + bool should_delete() const; + tcp::endpoint remote_endpoint(error_code& ec) const; + std::size_t available() const; + void close(); + // returns true if there were handlers cancelled + // if it returns false, we can detach immediately + bool destroy(); + void set_close_reason(close_reason_t code); + void detach(); + void send_syn(); + void send_fin(); + + void subscribe_drained(); + void defer_ack(); + void remove_sack_header(packet* p); + + enum packet_flags_t { pkt_ack = 1, pkt_fin = 2 }; + bool send_pkt(int flags = 0); + bool resend_packet(packet* p, bool fast_resend = false); + void send_reset(std::uint16_t ack_nr); + std::pair parse_sack(std::uint16_t packet_ack, std::uint8_t const* ptr + , int size, time_point now); + void parse_close_reason(std::uint8_t const* ptr, int size); + void write_payload(std::uint8_t* ptr, int size); + void maybe_inc_acked_seq_nr(); + std::uint32_t ack_packet(packet_ptr p, time_point receive_time + , std::uint16_t seq_nr); + void write_sack(std::uint8_t* buf, int size) const; + void incoming(std::uint8_t const* buf, int size, packet_ptr p, time_point now); + void do_ledbat(int acked_bytes, int delay, int in_flight); + int packet_timeout() const; + bool test_socket_state(); + void maybe_trigger_receive_callback(error_code const& ec); + void maybe_trigger_send_callback(error_code const& ec); + void maybe_trigger_writeable_callback(error_code const& ec); + bool cancel_handlers(error_code const& ec, bool shutdown); + bool consume_incoming_data( + utp_header const* ph, std::uint8_t const* ptr, int payload_size, time_point now); + void update_mtu_limits(); + void experienced_loss(std::uint32_t seq_nr, time_point now); + + void send_deferred_ack(); + void socket_drained(); + + void set_userdata(utp_stream* s) { m_userdata = s; } + void abort(); + udp::endpoint remote_endpoint() const; + + std::uint16_t receive_id() const { return m_recv_id; } + bool match(udp::endpoint const& ep, std::uint16_t id) const; + + // non-copyable + utp_socket_impl(utp_socket_impl const&) = delete; + utp_socket_impl const& operator=(utp_socket_impl const&) = delete; + + // The underlying UDP socket this uTP socket is bound to + // TODO: it would be nice to make this private + std::weak_ptr m_sock; + + void add_write_buffer(void const* buf, int len); + void add_read_buffer(void* buf, int len); + + int send_delay() const { return m_send_delay; } + int recv_delay() const { return m_recv_delay; } + + void issue_read(); + void issue_write(); + void subscribe_writeable(); + + bool check_fin_sent() const; + + void do_connect(tcp::endpoint const& ep); + + std::size_t read_some(bool const clear_buffers, error_code& ec); + std::size_t write_some(bool const clear_buffers); // Warning: non-blocking + int receive_buffer_size() const { return m_receive_buffer_size; } + + bool null_buffers() const { return m_null_buffers; } + +private: + + // it's important that these match the enums in performance_counters for + // num_utp_idle etc. + enum class state_t { + // not yet connected + none, + // sent a syn packet, not received any acks + syn_sent, + // syn-ack received and in normal operation + // of sending and receiving data + connected, + // fin sent, but all packets up to the fin packet + // have not yet been acked. We might still be waiting + // for a FIN from the other end + fin_sent, + + // ====== states beyond this point ===== + // === are considered closing states === + // === and will cause the socket to ==== + // ============ be deleted ============= + + // the socket has been gracefully disconnected + // and is waiting for the client to make a + // socket call so that we can communicate this + // fact and actually delete all the state, or + // there is an error on this socket and we're + // waiting to communicate this to the client in + // a callback. The error in either case is stored + // in m_error. If the socket has gracefully shut + // down, the error is error::eof. + error_wait, + + // there are no more references to this socket + // and we can delete it + deleting + }; + + packet_ptr acquire_packet(int const allocate); + void release_packet(packet_ptr p); + + void set_state(state_t s); + state_t state() const { return static_cast(m_state); } + +#if TORRENT_USE_INVARIANT_CHECKS + void check_receive_buffers() const; + void check_invariant() const; +#endif + + utp_socket_manager& m_sm; + + // userdata pointer passed along + // with any callback. This is initialized to nullptr + // then set to point to the utp_stream when + // hooked up, and then reset to 0 once the utp_stream + // detaches. This is used to know whether or not + // the socket impl is still attached to a utp_stream + // object. When it isn't, we'll never be able to + // signal anything back to the client, and in case + // of errors, we just have to delete ourselves + // i.e. transition to the state_t::deleting state + utp_stream* m_userdata; + + // if there's currently an async read or write + // operation in progress, these buffers are initialized + // and used, otherwise any bytes received are stuck in + // m_receive_buffer until another read is made + // as we flush from the write buffer, individual iovecs + // are updated to only refer to unflushed portions of the + // buffers. Buffers that empty are erased from the vector. + std::vector> m_write_buffer; + + // if this is non nullptr, it's a packet. This packet was held off because + // of NAGLE. We couldn't send it immediately. It's left + // here to accrue more bytes before we send it. + packet_ptr m_nagle_packet; + + // the user provided read buffer. If this has a size greater + // than 0, we'll always prefer using it over putting received + // data in the m_receive_buffer. As data is stored in the + // read buffer, the iovec_t elements are adjusted to only + // refer to the unwritten portions of the buffers, and the + // ones that fill up are erased from the vector + std::vector m_read_buffer; + + // packets we've received without a read operation + // active. Store them here until the client triggers + // an async_read_some + std::vector m_receive_buffer; + + // this is the error on this socket. If m_state is + // set to state_t::error_wait, this error should be + // forwarded to the client as soon as we have a new + // async operation initiated + error_code m_error; + + // these indicate whether or not there is an outstanding read/write or + // connect operation. i.e. is there upper layer subscribed to these events. + bool m_read_handler = false; + bool m_write_handler = false; + bool m_writeable_handler = false; + bool m_connect_handler = false; + + // the address of the remote endpoint + address m_remote_address; + + // the send and receive buffers + // maps packet sequence numbers + packet_buffer m_inbuf; + packet_buffer m_outbuf; + + // the time when the last packet we sent times out. Including re-sends. + // if we ever end up not having sent anything in one second ( + // or one mean rtt + 2 average deviations, whichever is greater) + // we set our cwnd to 1 MSS. This condition can happen either because + // a packet has timed out and needs to be resent or because our + // cwnd is set to less than one MSS during congestion control. + // it can also happen if the other end sends an advertised window + // size less than one MSS. + time_point m_timeout; + + // the last time we stepped the timestamp history + time_point m_last_history_step = clock_type::now(); + + // the next time we allow a lost packet to halve cwnd. We only do this once every + // 100 ms + time_point m_next_loss; + + // the max number of bytes in-flight. This is a fixed point + // value, to get the true number of bytes, shift right 16 bits + // the value is always >= 0, but the calculations performed on + // it in do_ledbat() are signed. + std::int64_t m_cwnd = TORRENT_ETHERNET_MTU << 16; + + timestamp_history m_delay_hist; + timestamp_history m_their_delay_hist; + + // the slow-start threshold. This is the congestion window size (m_cwnd) + // in bytes the last time we left slow-start mode. This is used as a + // threshold to leave slow-start earlier next time, to avoid packet-loss + std::int32_t m_ssthres = 0; + + // the number of bytes we have buffered in m_inbuf + std::int32_t m_buffered_incoming_bytes = 0; + + // the timestamp diff in the last packet received + // this is what we'll send back + std::uint32_t m_reply_micro = 0; + + // this is the advertised receive window the other end sent + // we'll never have more un-acked bytes in flight + // if this ever gets set to zero, we'll try one packet every + // second until the window opens up again + std::uint32_t m_adv_wnd = TORRENT_ETHERNET_MTU; + + // the number of un-acked bytes we have sent + // This does not include packets that have been created but either failed to + // be sent or were lost. i.e. when a packet is lost, it's no longer + // considered in-flight. + std::int32_t m_bytes_in_flight = 0; + + // the number of bytes read into the user provided + // buffer. If this grows too big, we'll trigger the + // read handler. + std::int32_t m_read = 0; + + // the sum of the lengths of all iovec in m_write_buffer + std::int32_t m_write_buffer_size = 0; + + // the number of bytes already written to packets + // from m_write_buffer + std::int32_t m_written = 0; + + // the sum of all packets stored in m_receive_buffer + std::int32_t m_receive_buffer_size = 0; + + // the sum of all buffers in m_read_buffer + std::int32_t m_read_buffer_size = 0; + + // max number of bytes to allocate for receive buffer + std::int32_t m_receive_buffer_capacity = 1024 * 1024; + + // this holds the 3 last delay measurements, + // these are the actual corrected delay measurements. + // the lowest of the 3 last ones is used in the congestion + // controller. This is to not completely close the cwnd + // by a single outlier. + std::array m_delay_sample_hist; + + // counters + std::uint32_t m_in_packets = 0; + std::uint32_t m_out_packets = 0; + + // the last send delay sample + std::int32_t m_send_delay = 0; + // the last receive delay sample + std::int32_t m_recv_delay = 0; + + // average RTT + sliding_average m_rtt; + + // if this is != 0, it means the upper layer provided a reason for why + // the connection is being closed. The reason is indicated by this + // non-zero value which is included in a packet header extension + close_reason_t m_close_reason = close_reason_t::none; + + // port of destination endpoint + std::uint16_t m_port = 0; + + std::uint16_t m_send_id; + std::uint16_t m_recv_id; + + // this is the ack we're sending back. We have + // received all packets up to this sequence number + std::uint16_t m_ack_nr = 0; + + // the sequence number of the next packet + // we'll send. when we're closing the connection + // this becomes the sequence number of the ST_FIN packet + // and will no longer increase + std::uint16_t m_seq_nr = 0; + + // this is the sequence number of the packet that + // everything has been ACKed up to. Everything we've + // sent up to this point has been received by the other + // end. + std::uint16_t m_acked_seq_nr = 0; + + // each packet gets one chance of "fast resend". i.e. + // if we have multiple duplicate acks, we may send a + // packet immediately, if m_fast_resend_seq_nr is set + // to that packet's sequence number + std::uint16_t m_fast_resend_seq_nr = 0; + + // this is the sequence number of the last undersized + // packet that we sent. this is used to ensure there + // is no more than one undersized packet in flight + // at a time + std::uint16_t m_nagle_seq_nr = 0; + + // this is the sequence number of the FIN packet + // we've received. This sequence number is only + // valid if m_eof is true. We should not accept + // any packets beyond this sequence number from the + // other end + std::uint16_t m_in_eof_seq_nr = 0; + + // this is the lowest sequence number that, when lost, + // will cause the window size to be cut in half + std::uint16_t m_loss_seq_nr = 0; + + // the max number of bytes we can send in a packet + // including the header + std::uint16_t m_mtu = TORRENT_ETHERNET_MTU - TORRENT_IPV4_HEADER - TORRENT_UDP_HEADER - 8 - 24 - 36; + + // the floor is the largest packet that we have + // been able to get through without fragmentation + std::uint16_t m_mtu_floor = TORRENT_INET_MIN_MTU - TORRENT_IPV4_HEADER - TORRENT_UDP_HEADER; + + // the ceiling is the largest packet that we might + // be able to get through without fragmentation. + // i.e. ceiling +1 is very likely to not get through + // or we have in fact experienced a drop or ICMP + // message indicating that it is + std::uint16_t m_mtu_ceiling = TORRENT_ETHERNET_MTU - TORRENT_IPV4_HEADER - TORRENT_UDP_HEADER; + + // the sequence number of the probe in-flight + // this is 0 if there is no probe in flight + std::uint16_t m_mtu_seq = 0; + + // this is a counter of how many times the current m_acked_seq_nr + // has been ACKed. If it's ACKed more than 3 times, we assume the + // packet with the next sequence number has been lost, and we trigger + // a re-send. Obviously an ACK only counts as a duplicate as long as + // we have outstanding packets following it. + std::uint8_t m_duplicate_acks = 0; + + // the number of packet timeouts we've seen in a row + // this affects the packet timeout time + std::uint8_t m_num_timeouts = 0; + + // this is the cursor into m_delay_sample_hist + std::uint8_t m_delay_sample_idx:2; + + // the state the socket is in + std::uint8_t m_state:3; + + // this is set to true when we receive a fin + // The incoming stream is being closed at sequence number + // indicated by m_in_eof_seq_nr + bool m_in_eof:1; + + // this is true when the application has called close() on the socket. + // at this point, we will send a FIN at the current sequence number, + // and will not allow it to be incremented any further + bool m_out_eof:1; + + // is this socket state attached to a user space socket? + bool m_attached:1; + + // this is true if nagle is enabled (which it is by default) + bool m_nagle:1; + + // this is true while the socket is in slow start mode. It's + // only in slow-start during the start-up phase. Slow start + // (contrary to what its name suggest) means that we're growing + // the congestion window (cwnd) exponentially rather than linearly. + // this is done at startup of a socket in order to find its + // link capacity faster. This behaves similar to TCP slow start + bool m_slow_start:1; + + // this is true as long as we have as many packets in + // flight as allowed by the congestion window (cwnd) + bool m_cwnd_full:1; + + // this is set to one if the current read operation + // has a null_buffer. i.e. we're not reading into a user-provided + // buffer, we're just signalling when there's something + // to read from our internal receive buffer + bool m_null_buffers:1; + + // this is set to true when this socket has added itself to + // the utp socket manager's list of deferred acks. Once the + // burst of incoming UDP packets is all drained, the utp socket + // manager will send acks for all sockets on this list. + bool m_deferred_ack:1; + + // this is true if this socket has subscribed to be notified + // when this receive round is done + bool m_subscribe_drained:1; + + // if this socket tries to send a packet via the utp socket + // manager, and it fails with EWOULDBLOCK, the socket + // is stalled and this is set. It's also added to a list + // of sockets in the utp_socket_manager to be notified of + // the socket being writable again + bool m_stalled:1; + + // this is false by default and set to true once we've received a non-SYN + // packet for this connection with a correct ack_nr, confirming that the + // other end is not spoofing its source IP + bool m_confirmed:1; +}; + +} +} + +#endif diff --git a/include/libtorrent/aux_/vector.hpp b/include/libtorrent/aux_/vector.hpp new file mode 100644 index 0000000..2ed7ab0 --- /dev/null +++ b/include/libtorrent/aux_/vector.hpp @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2016, 2018, 2020, Arvid Norberg +Copyright (c) 2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_VECTOR_HPP +#define TORRENT_VECTOR_HPP + +#include + +#include "libtorrent/aux_/container_wrapper.hpp" + +namespace libtorrent { namespace aux { + + template + using vector = container_wrapper>; + +}} + +#endif diff --git a/include/libtorrent/aux_/win_cng.hpp b/include/libtorrent/aux_/win_cng.hpp new file mode 100644 index 0000000..09b36da --- /dev/null +++ b/include/libtorrent/aux_/win_cng.hpp @@ -0,0 +1,208 @@ +/* + +Copyright (c) 2019, Andrei Kurushin +Copyright (c) 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_WIN_CNG_HPP +#define TORRENT_WIN_CNG_HPP + +#include + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_CNG +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/throw.hpp" +#include "libtorrent/aux_/windows.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { namespace aux { + + inline void throw_ntstatus_error(char const* name, NTSTATUS status) { + throw_ex(status, system_category(), name); + } + + inline BCRYPT_ALG_HANDLE cng_open_algorithm_handle(LPCWSTR alg_name) + { + BCRYPT_ALG_HANDLE algorithm_handle{ 0 }; + NTSTATUS status = + BCryptOpenAlgorithmProvider(&algorithm_handle, alg_name, nullptr, 0); + if (status < 0) { + throw_ntstatus_error("BCryptOpenAlgorithmProvider", status); + } + return algorithm_handle; + } + + inline DWORD cng_get_algorithm_object_size( + BCRYPT_ALG_HANDLE algorithm_handle) + { + DWORD object_size{ 0 }; + DWORD data_size{ 0 }; + NTSTATUS status = BCryptGetProperty(algorithm_handle, + BCRYPT_OBJECT_LENGTH, (PBYTE)&object_size, sizeof(DWORD), + &data_size, 0); + if (status < 0) { + throw_ntstatus_error("BCryptGetProperty BCRYPT_OBJECT_LENGTH", + status); + } + + return object_size; + } + + inline void cng_gen_random(span buffer) + { + static BCRYPT_ALG_HANDLE algorithm_handle = + cng_open_algorithm_handle(BCRYPT_RNG_ALGORITHM); + + NTSTATUS status = BCryptGenRandom(algorithm_handle, + reinterpret_cast(buffer.data()), + static_cast(buffer.size()), 0); + if (status < 0) { + throw_ntstatus_error("BCryptGenRandom", status); + } + } + + template + struct cng_hash + { + cng_hash() { create(); } + cng_hash(cng_hash const& h) { duplicate(h); } + ~cng_hash() + { + destroy(); + } + + cng_hash& operator=(cng_hash const& h) & + { + if (this == &h) return *this; + if (m_hash == h.m_hash) return *this; + destroy(); + duplicate(h); + return *this; + } + + void reset() + { + destroy(); + create(); + } + + void update(span data) + { + NTSTATUS status = BCryptHashData( + m_hash, + (PUCHAR)(data.data()), + static_cast(data.size()), 0); + if (status < 0) { + throw_ntstatus_error("BCryptHashData", status); + } + } + + void get_hash(char *digest, std::size_t digest_size) + { + NTSTATUS status = BCryptFinishHash(m_hash, + reinterpret_cast(digest), + static_cast(digest_size), 0); + if (status < 0) { + throw_ntstatus_error("BCryptFinishHash", status); + } + } + private: + void create() + { + NTSTATUS status = BCryptCreateHash(get_algorithm_handle(), + &m_hash, m_hash_object.data(), m_hash_object.size(), + nullptr, 0, 0); + if (status < 0) { + throw_ntstatus_error("BCryptCreateHash", status); + } + } + + void destroy() + { + NTSTATUS status = BCryptDestroyHash(m_hash); + if (status < 0) { + throw_ntstatus_error("BCryptDestroyHash", status); + } + } + + void duplicate(cng_hash const& h) + { + NTSTATUS status = BCryptDuplicateHash(h.m_hash, + &m_hash, m_hash_object.data(), m_hash_object.size(), 0); + if (status < 0) { + throw_ntstatus_error("BCryptDuplicateHash", status); + } + } + + BCRYPT_ALG_HANDLE get_algorithm_handle() + { + static BCRYPT_ALG_HANDLE algorithm_handle = + cng_open_algorithm_handle(AlgId::name); + return algorithm_handle; + } + + std::size_t get_algorithm_object_size() + { + static std::size_t object_size = + static_cast( + cng_get_algorithm_object_size(get_algorithm_handle())); + return object_size; + } + + using hash_object_t = std::vector; + + BCRYPT_HASH_HANDLE m_hash; + hash_object_t m_hash_object + = hash_object_t(get_algorithm_object_size()); + }; + + struct cng_sha1_algorithm { + static constexpr LPCWSTR name = BCRYPT_SHA1_ALGORITHM; + }; + + struct cng_sha256_algorithm { + static constexpr LPCWSTR name = BCRYPT_SHA256_ALGORITHM; + }; + + struct cng_sha512_algorithm { + static constexpr LPCWSTR name = BCRYPT_SHA512_ALGORITHM; + }; + +} // namespace aux +} // namespace libtorrent + +#endif // TORRENT_USE_CNG + +#endif // TORRENT_WIN_CNG_HPP diff --git a/include/libtorrent/aux_/win_crypto_provider.hpp b/include/libtorrent/aux_/win_crypto_provider.hpp new file mode 100644 index 0000000..54055ba --- /dev/null +++ b/include/libtorrent/aux_/win_crypto_provider.hpp @@ -0,0 +1,148 @@ +/* + +Copyright (c) 2017, Alden Torres +Copyright (c) 2017, 2019, Andrei Kurushin +Copyright (c) 2017-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_WIN_CRYPTO_PROVIDER_HPP +#define TORRENT_WIN_CRYPTO_PROVIDER_HPP + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_CRYPTOAPI +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/throw.hpp" +#include "libtorrent/aux_/windows.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { namespace aux { + + inline HCRYPTPROV crypt_acquire_provider(DWORD provider_type) + { + HCRYPTPROV ret; + if (CryptAcquireContext(&ret, nullptr, nullptr, provider_type + , CRYPT_VERIFYCONTEXT) == false) + { + throw_ex(error_code(GetLastError(), system_category())); + } + return ret; + } + + inline void crypt_gen_random(span buffer) + { + static HCRYPTPROV provider = crypt_acquire_provider(PROV_RSA_FULL); + if (!CryptGenRandom(provider, int(buffer.size()) + , reinterpret_cast(buffer.data()))) + { + throw_ex(error_code(GetLastError(), system_category())); + } + } + + template + struct crypt_hash + { + crypt_hash() { m_hash = create(); } + crypt_hash(crypt_hash const& h) { m_hash = duplicate(h); } + ~crypt_hash() { CryptDestroyHash(m_hash); } + + crypt_hash& operator=(crypt_hash const& h) & + { + if (this == &h) return *this; + HCRYPTHASH temp = duplicate(h); + CryptDestroyHash(m_hash); + m_hash = temp; + return *this; + } + + void reset() + { + HCRYPTHASH temp = create(); + CryptDestroyHash(m_hash); + m_hash = temp; + } + + void update(span data) + { + if (CryptHashData(m_hash, reinterpret_cast(data.data()), int(data.size()), 0) == false) + { + throw_ex(error_code(GetLastError(), system_category())); + } + } + + void get_hash(char *digest, std::size_t digest_size) + { + DWORD size = DWORD(digest_size); + if (CryptGetHashParam(m_hash, HP_HASHVAL + , reinterpret_cast(digest), &size, 0) == false) + { + throw_ex(error_code(GetLastError(), system_category())); + } + TORRENT_ASSERT(size == DWORD(digest_size)); + } + private: + HCRYPTHASH create() + { + HCRYPTHASH ret; + if (CryptCreateHash(get_provider(), AlgId, 0, 0, &ret) == false) + { + throw_ex(error_code(GetLastError(), system_category())); + } + return ret; + } + + HCRYPTHASH duplicate(crypt_hash const& h) + { + HCRYPTHASH ret; + if (CryptDuplicateHash(h.m_hash, 0, 0, &ret) == false) + { + throw_ex(error_code(GetLastError(), system_category())); + } + return ret; + } + + HCRYPTPROV get_provider() + { + static HCRYPTPROV provider = crypt_acquire_provider(ProviderType); + return provider; + } + + HCRYPTHASH m_hash; + }; + +} // namespace aux +} // namespace libtorrent + +#endif // TORRENT_USE_CRYPTOAPI + +#endif // TORRENT_WIN_CRYPTO_PROVIDER_HPP diff --git a/include/libtorrent/aux_/win_file_handle.hpp b/include/libtorrent/aux_/win_file_handle.hpp new file mode 100644 index 0000000..3e16477 --- /dev/null +++ b/include/libtorrent/aux_/win_file_handle.hpp @@ -0,0 +1,63 @@ +/* +Copyright (c) 2022, Arvid Norberg +All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef TORRENT_WIN_FILE_HANDLE_HPP_INCLUDED +#define TORRENT_WIN_FILE_HANDLE_HPP_INCLUDED + +#if defined TORRENT_WINDOWS + +#include "libtorrent/aux_/windows.hpp" + +namespace libtorrent { +namespace aux { + +struct win_file_handle +{ + win_file_handle(HANDLE h) : m_h(h) {} + + ~win_file_handle() + { + if (m_h != INVALID_HANDLE_VALUE) ::CloseHandle(m_h); + } + + win_file_handle(win_file_handle const&) = delete; + win_file_handle(win_file_handle&& rhs) + : m_h(rhs.m_h) + { + rhs.m_h = INVALID_HANDLE_VALUE; + } + HANDLE handle() const { return m_h; } +private: + HANDLE m_h; +}; + +} +} + +#endif + +#endif diff --git a/include/libtorrent/aux_/win_util.hpp b/include/libtorrent/aux_/win_util.hpp new file mode 100644 index 0000000..44696ae --- /dev/null +++ b/include/libtorrent/aux_/win_util.hpp @@ -0,0 +1,108 @@ +/* + +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2017, 2019-2020, Arvid Norberg +Copyright (c) 2020, Tiger Wang +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_WIN_UTIL_HPP +#define TORRENT_WIN_UTIL_HPP + +#include "libtorrent/config.hpp" + +namespace libtorrent { namespace aux { + +#ifdef TORRENT_WINRT + using LoadLibraryASignature = HMODULE WINAPI(_In_ LPCSTR lpLibFileName); +#endif + + template + HMODULE get_library_handle() + { + static bool handle_checked = false; + static HMODULE handle = nullptr; + + if (!handle_checked) + { + handle_checked = true; + +#ifdef TORRENT_WINRT + MEMORY_BASIC_INFORMATION Information; + + if (::VirtualQuery(&VirtualQuery, &Information, sizeof(Information)) == 0) + { + return nullptr; + } + + const auto SyscallBegin = static_cast(Information.AllocationBase); + const auto LoadLibraryA = reinterpret_cast(::GetProcAddress(SyscallBegin, "LoadLibraryA")); + + if (LoadLibraryA == nullptr) + { + return nullptr; + } +#endif + + handle = LoadLibraryA(Library::library_name); + } + return handle; + } + + template + Signature get_library_procedure(LPCSTR name) + { + static Signature proc = nullptr; + static bool failed_proc = false; + + if ((proc == nullptr) && !failed_proc) + { + HMODULE const handle = get_library_handle(); + if (handle) proc = reinterpret_cast(reinterpret_cast(GetProcAddress(handle, name))); + failed_proc = (proc == nullptr); + } + return proc; + } + + struct iphlpapi { + static constexpr char const* library_name = "iphlpapi.dll"; + }; + + struct kernel32 { + static constexpr char const* library_name = "kernel32.dll"; + }; + + struct advapi32 { + static constexpr char const* library_name = "advapi32.dll"; + }; + +} // namespace aux +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/aux_/windows.hpp b/include/libtorrent/aux_/windows.hpp new file mode 100644 index 0000000..9c5dec3 --- /dev/null +++ b/include/libtorrent/aux_/windows.hpp @@ -0,0 +1,51 @@ +/* + +Copyright (c) 2018, Arvid Norberg, Steven Siloti +Copyright (c) 2018, 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_WINDOWS_HPP_INCLUDED +#define TORRENT_WINDOWS_HPP_INCLUDED + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef VC_EXTRALEAN +#define VC_EXTRALEAN +#endif +#ifndef STRICT +#define STRICT +#endif +#include + +#endif // TORRENT_WINDOWS_HPP_INCLUDED + + + diff --git a/include/libtorrent/bdecode.hpp b/include/libtorrent/bdecode.hpp new file mode 100644 index 0000000..66d382b --- /dev/null +++ b/include/libtorrent/bdecode.hpp @@ -0,0 +1,483 @@ +/* + +Copyright (c) 2015-2016, Alden Torres +Copyright (c) 2015-2020, 2022, Arvid Norberg +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2017, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BDECODE_HPP +#define TORRENT_BDECODE_HPP + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/assert.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" + +/* + +This is an efficient bdecoder. It decodes into a flat memory buffer of tokens. + +Each token has an offset into the bencoded buffer where the token came from +and a next pointer, which is a relative number of tokens to skip forward to +get to the logical next item in a container. + +strings and ints offset pointers point to the first character of the length +prefix or the 'i' character. This is to maintain uniformity with other types +and to allow easily calculating the span of a node by subtracting its offset +by the offset of the next node. + +example layout: + +{ + "a": { "b": 1, "c": "abcd" }, + "d": 3 +} + + /---------------------------------------------------------------------------------------\ + | | + | | + | /--------------------------------------------\ | + | | | | + | | | | + | /-----\ | /----\ /----\ /----\ /----\ | /----\ /----\ | + | next | | | | | | | | | | | | | | | | | + | pointers | v | | v | v | v | v v | v | v v ++-+-----+----+--+----+--+----+--+----+--+----+--+----+--+-------+----+--+----+--+------+ X +| dict | str | dict | str | int | str | str | end | str | int | end | +| | | | | | | | | | | | +| | | | | | | | | | | | ++-+-----+-+-----+-+-----+-+-----+-+-----+-+-----+-+-----+-+-----+-+-----+-+-----+-+----+ + | offset| | | | | | | | | | + | | | | | | | | | | | + |/------/ | | | | | | | | | + || /-----------/ | | | | | | | | + || |/------------------/ | | | | | | | + || || /-----------------------/ | | | | | | + || || | /----------------------------/ | | | | | + || || | | /---------------------------------/ | | | | + || || | | | /-----------------------------------/ | | | + || || | | | |/------------------------------------------/ | | + || || | | | || /-----------------------------------------------/ | + || || | | | || | /----------------------------------------------------/ + || || | | | || | | + vv vv v v v vv v v +``d1:ad1:bi1e1:c4:abcde1:di3ee`` + +*/ + +namespace libtorrent { + +TORRENT_EXPORT boost::system::error_category& bdecode_category(); + +#if TORRENT_ABI_VERSION == 1 +TORRENT_DEPRECATED +inline boost::system::error_category& get_bdecode_category() +{ return bdecode_category(); } +#endif + +namespace bdecode_errors { + // libtorrent uses boost.system's ``error_code`` class to represent + // errors. libtorrent has its own error category bdecode_category() + // with the error codes defined by error_code_enum. + enum error_code_enum + { + // Not an error + no_error = 0, + // expected digit in bencoded string + expected_digit, + // expected colon in bencoded string + expected_colon, + // unexpected end of file in bencoded string + unexpected_eof, + // expected value (list, dict, int or string) in bencoded string + expected_value, + // bencoded recursion depth limit exceeded + depth_exceeded, + // bencoded item count limit exceeded + limit_exceeded, + // integer overflow + overflow, + + // the number of error codes + error_code_max + }; + + // hidden + TORRENT_EXPORT boost::system::error_code make_error_code(error_code_enum e); +} +} // namespace libtorrent + +namespace boost { +namespace system { + + template<> struct is_error_code_enum + { static const bool value = true; }; + +} +} + +namespace libtorrent { + + using error_code = boost::system::error_code; + +TORRENT_EXTRA_EXPORT char const* parse_int(char const* start + , char const* end, char delimiter, std::int64_t& val + , bdecode_errors::error_code_enum& ec); + +namespace aux { + +// internal +void escape_string(std::string& ret, char const* str, int len); + +// internal +struct bdecode_token +{ + // the node with type 'end' is a logical node, pointing to the end + // of the bencoded buffer. The `long_string` type is for strings that are so + // long they need a length prefix that's longer than 8 decimal digits. + // these enum values need to be compatible with bdecode_node::type_t + enum type_t : std::uint8_t + { none, dict, list, string, integer, long_string, end }; + + enum limits_t + { + max_offset = (1 << 29) - 1, + max_next_item = (1 << 29) - 1, + short_string_max_header = (1 << 3) - 1 + 2, + long_string_max_header = 8 + (1 << 3) - 1 + 2 + }; + + bdecode_token(std::ptrdiff_t off, bdecode_token::type_t t) + : offset(std::uint32_t(off)) + , type(t) + , next_item(0) + , header(0) + { + TORRENT_ASSERT(off >= 0); + TORRENT_ASSERT(off <= max_offset); + TORRENT_ASSERT(t <= end); + static_assert(std::is_unsigned::type>::value + , "we need to assert t >= 0 here"); + } + + bdecode_token(std::ptrdiff_t const off, std::uint32_t const next + , bdecode_token::type_t const t, std::uint32_t const header_size = 0) + : offset(std::uint32_t(off)) + , type(t == string && header_size > aux::bdecode_token::short_string_max_header ? long_string : t) + , next_item(next) + , header(type == string ? std::uint32_t(header_size - 2) + : type == long_string ? std::uint32_t(header_size - 8 - 2) : 0) + { + TORRENT_ASSERT((type != string && type != long_string) || header_size >= 2); + TORRENT_ASSERT(off >= 0); + TORRENT_ASSERT(off <= max_offset); + TORRENT_ASSERT(next <= max_next_item); + // the string has 2 implied header bytes, to allow for longer prefixes + TORRENT_ASSERT(header_size < 8 + || (type == string && header_size < 10) + || (type == long_string && header_size < 18)); + TORRENT_ASSERT(t <= end); + static_assert(std::is_unsigned::type>::value + , "we need to assert t >= 0 here"); + } + + int start_offset() const + { + TORRENT_ASSERT(type == string || type == long_string); + if (type == string) + return int(header) + 2; + else + return int(header) + 8 + 2; + } + + // offset into the bdecoded buffer where this node is + std::uint32_t offset:29; + + // one of type_t enums + std::uint32_t type:3; + + // if this node is a member of a list, 'next_item' is the number of nodes + // to jump forward in th node array to get to the next item in the list. + // if it's a key in a dictionary, it's the number of step forwards to get + // to its corresponding value. If it's a value in a dictionary, it's the + // number of steps to the next key, or to the end node. + // this is the _relative_ offset to the next node + std::uint32_t next_item:29; + + // This field is only used for ``string`` and ``long_string`` type tokens. + // It is the number of bytes to skip forward from the offset to get to the + // first character of the string. This is essentially the length of the + // length prefix and the colon. Since a string always has at least one + // character of length prefix and always a colon, those 2 characters are + // implied. 3 bits gives us a maximum length of 7, plus one implied digit. + // if the string is 100'000'000 bytes long (100 megabytes), we need more + // digits. That's what the ``long_string`` type is used for. It has 8 + // implied digits in the length prefix (+ the colon). + std::uint32_t header:3; +}; +} + +// a ``bdecode_node`` is used to traverse and hold the tree structure defined +// by bencoded data after it has been parse by bdecode(). +// +// There are primarily two kinds of bdecode_nodes. The ones that own the tree +// structure, and defines its lifetime, and nodes that are child nodes in the +// tree, pointing back into the root's tree. +// +// The ``bdecode_node`` passed in to ``bdecode()`` becomes the one owning the +// tree structure. Make sure not to destruct that object for as long as you +// use any of its child nodes. Also, keep in mind that the buffer originally +// parsed also must remain valid while using it. (see switch_underlying_buffer()). +// +// Copying an owning node will create a copy of the whole tree, but will still +// point into the same parsed bencoded buffer as the first one. + +// Sometimes it's important to get a non-owning reference to the root node ( +// to be able to copy it as a reference for instance). For that, use the +// non_owning() member function. +// +// There are 5 different types of nodes, see type_t. +struct TORRENT_EXPORT bdecode_node +{ +#if TORRENT_ABI_VERSION == 1 + TORRENT_EXPORT friend int bdecode(char const* start, char const* end, bdecode_node& ret + , error_code& ec, int* error_pos, int depth_limit + , int token_limit); +#endif + + // hidden + TORRENT_EXPORT friend bdecode_node bdecode(span buffer + , error_code& ec, int* error_pos, int depth_limit, int token_limit); + + // creates a default constructed node, it will have the type ``none_t``. + bdecode_node() = default; + + // For owning nodes, the copy will create a copy of the tree, but the + // underlying buffer remains the same. + bdecode_node(bdecode_node const&); + bdecode_node& operator=(bdecode_node const&) &; + bdecode_node(bdecode_node&&) noexcept; + bdecode_node& operator=(bdecode_node&&) & = default; + + // the types of bdecoded nodes + enum type_t + { + // uninitialized or default constructed. This is also used + // to indicate that a node was not found in some cases. + none_t, + // a dictionary node. The ``dict_find_`` functions are valid. + dict_t, + // a list node. The ``list_`` functions are valid. + list_t, + // a string node, the ``string_`` functions are valid. + string_t, + // an integer node. The ``int_`` functions are valid. + int_t + }; + + // the type of this node. See type_t. + type_t type() const noexcept; + + // returns true if type() != none_t. + explicit operator bool() const noexcept; + + // return a non-owning reference to this node. This is useful to refer to + // the root node without copying it in assignments. + bdecode_node non_owning() const; + + // returns the buffer and length of the section in the original bencoded + // buffer where this node is defined. For a dictionary for instance, this + // starts with ``d`` and ends with ``e``, and has all the content of the + // dictionary in between. + // the ``data_offset()`` function returns the byte-offset to this node in, + // starting from the beginning of the buffer that was parsed. + span data_section() const noexcept; + std::ptrdiff_t data_offset() const noexcept; + + // functions with the ``list_`` prefix operate on lists. These functions are + // only valid if ``type()`` == ``list_t``. ``list_at()`` returns the item + // in the list at index ``i``. ``i`` may not be greater than or equal to the + // size of the list. ``size()`` returns the size of the list. + bdecode_node list_at(int i) const; + string_view list_string_value_at(int i + , string_view default_val = string_view()) const; + std::int64_t list_int_value_at(int i + , std::int64_t default_val = 0) const; + int list_size() const; + + // Functions with the ``dict_`` prefix operates on dictionaries. They are + // only valid if ``type()`` == ``dict_t``. In case a key you're looking up + // contains a 0 byte, you cannot use the 0-terminated string overloads, + // but have to use ``string_view`` instead. ``dict_find_list`` will return a + // valid ``bdecode_node`` if the key is found _and_ it is a list. Otherwise + // it will return a default-constructed bdecode_node. + // + // Functions with the ``_value`` suffix return the value of the node + // directly, rather than the nodes. In case the node is not found, or it has + // a different type, a default value is returned (which can be specified). + // + // ``dict_at()`` returns the (key, value)-pair at the specified index in a + // dictionary. Keys are only allowed to be strings. ``dict_at_node()`` also + // returns the (key, value)-pair, but the key is returned as a + // ``bdecode_node`` (and it will always be a string). + bdecode_node dict_find(string_view key) const; + std::pair dict_at(int i) const; + std::pair dict_at_node(int i) const; + bdecode_node dict_find_dict(string_view key) const; + bdecode_node dict_find_list(string_view key) const; + bdecode_node dict_find_string(string_view key) const; + bdecode_node dict_find_int(string_view key) const; + string_view dict_find_string_value(string_view key + , string_view default_value = string_view()) const; + std::int64_t dict_find_int_value(string_view key + , std::int64_t default_val = 0) const; + int dict_size() const; + + // this function is only valid if ``type()`` == ``int_t``. It returns the + // value of the integer. + std::int64_t int_value() const; + + // these functions are only valid if ``type()`` == ``string_t``. They return + // the string values. Note that ``string_ptr()`` is *not* 0-terminated. + // ``string_length()`` returns the number of bytes in the string. + // ``string_offset()`` returns the byte offset from the start of the parsed + // bencoded buffer this string can be found. + string_view string_value() const; + char const* string_ptr() const; + int string_length() const; + std::ptrdiff_t string_offset() const; + + // resets the ``bdecoded_node`` to a default constructed state. If this is + // an owning node, the tree is freed and all child nodes are invalidated. + void clear(); + + // Swap contents. + void swap(bdecode_node& n); + + // preallocate memory for the specified numbers of tokens. This is + // useful if you know approximately how many tokens are in the file + // you are about to parse. Doing so will save realloc operations + // while parsing. You should only call this on the root node, before + // passing it in to bdecode(). + void reserve(int tokens); + + // this buffer *MUST* be identical to the one originally parsed. This + // operation is only defined on owning root nodes, i.e. the one passed in to + // decode(). + void switch_underlying_buffer(char const* buf) noexcept; + + // returns true if there is a non-fatal error in the bencoding of this node + // or its children + bool has_soft_error(span error) const; + +private: + bdecode_node(aux::bdecode_token const* tokens, char const* buf + , int len, int idx); + + // if this is the root node, that owns all the tokens, they live in this + // vector. If this is a sub-node, this field is not used, instead the + // m_root_tokens pointer points to the root node's token. + aux::noexcept_movable> m_tokens; + + // this points to the root nodes token vector + // for the root node, this points to its own m_tokens member + aux::bdecode_token const* m_root_tokens = nullptr; + + // this points to the original buffer that was parsed + char const* m_buffer = nullptr; + int m_buffer_size = 0; + + // this is the index into m_root_tokens that this node refers to + // for the root node, it's 0. -1 means uninitialized. + int m_token_idx = -1; + + // this is a cache of the last element index looked up. This only applies + // to lists and dictionaries. If the next lookup is at m_last_index or + // greater, we can start iterating the tokens at m_last_token. + mutable int m_last_index = -1; + mutable int m_last_token = -1; + + // the number of elements in this list or dict (computed on the first + // call to dict_size() or list_size()) + mutable int m_size = -1; +}; + +// print the bencoded structure in a human-readable format to a string +// that's returned. +TORRENT_EXPORT std::string print_entry(bdecode_node const& e + , bool single_line = false, int indent = 0); + +// This function decodes/parses bdecoded data (for example a .torrent file). +// The data structure is returned in the ``ret`` argument. the buffer to parse +// is specified by the ``start`` of the buffer as well as the ``end``, i.e. one +// byte past the end. If the buffer fails to parse, the function returns a +// non-zero value and fills in ``ec`` with the error code. The optional +// argument ``error_pos``, if set to non-nullptr, will be set to the byte offset +// into the buffer where the parse failure occurred. +// +// ``depth_limit`` specifies the max number of nested lists or dictionaries are +// allowed in the data structure. (This affects the stack usage of the +// function, be careful not to set it too high). +// +// ``token_limit`` is the max number of tokens allowed to be parsed from the +// buffer. This is simply a sanity check to not have unbounded memory usage. +// +// The resulting ``bdecode_node`` is an *owning* node. That means it will +// be holding the whole parsed tree. When iterating lists and dictionaries, +// those ``bdecode_node`` objects will simply have references to the root or +// owning ``bdecode_node``. If the root node is destructed, all other nodes +// that refer to anything in that tree become invalid. +// +// However, the underlying buffer passed in to this function (``start``, ``end``) +// must also remain valid while the bdecoded tree is used. The parsed tree +// produced by this function does not copy any data out of the buffer, but +// simply produces references back into it. +TORRENT_EXPORT int bdecode(char const* start, char const* end, bdecode_node& ret + , error_code& ec, int* error_pos = nullptr, int depth_limit = 100 + , int token_limit = 2000000); +TORRENT_EXPORT bdecode_node bdecode(span buffer + , error_code& ec, int* error_pos = nullptr, int depth_limit = 100 + , int token_limit = 2000000); +TORRENT_EXPORT bdecode_node bdecode(span buffer + , int depth_limit = 100, int token_limit = 2000000); + +} + +#endif // TORRENT_BDECODE_HPP diff --git a/include/libtorrent/bencode.hpp b/include/libtorrent/bencode.hpp new file mode 100644 index 0000000..ceb93b4 --- /dev/null +++ b/include/libtorrent/bencode.hpp @@ -0,0 +1,408 @@ +/* + +Copyright (c) 2003-2005, 2007-2009, 2012-2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2019, Amir Abrams +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BENCODE_HPP_INCLUDED +#define TORRENT_BENCODE_HPP_INCLUDED + +// OVERVIEW +// +// Bencoding is a common representation in bittorrent used for dictionary, +// list, int and string hierarchies. It's used to encode .torrent files and +// some messages in the network protocol. libtorrent also uses it to store +// settings, resume data and other session state. +// +// Strings in bencoded structures do not necessarily represent text. +// Strings are raw byte buffers of a certain length. If a string is meant to be +// interpreted as text, it is required to be UTF-8 encoded. See `BEP 3`_. +// +// The function for decoding bencoded data bdecode(), returning a bdecode_node. +// This function builds a tree that points back into the original buffer. The +// returned bdecode_node will not be valid once the buffer it was parsed out of +// is discarded. +// +// It's possible to construct an entry from a bdecode_node, if a structure needs +// to be altered and re-encoded. + +#include +#include // for distance + +#include "libtorrent/config.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/io.hpp" // for write_string +#include "libtorrent/string_util.hpp" // for is_digit + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 + using invalid_encoding = system_error; +#endif + +namespace aux { + + template ::value>::type> + int write_integer(OutIt& out, In data) + { + entry::integer_type const val = entry::integer_type(data); + TORRENT_ASSERT(data == In(val)); + // the stack allocated buffer for keeping the + // decimal representation of the number can + // not hold number bigger than this: + static_assert(sizeof(entry::integer_type) <= 8, "64 bit integers required"); + static_assert(sizeof(data) <= sizeof(entry::integer_type), "input data too big, see entry::integer_type"); + std::array buf; + auto const str = integer_to_str(buf, val); + for (char const c : str) + { + *out = c; + ++out; + } + return static_cast(str.size()); + } + + template + void write_char(OutIt& out, char c) + { + *out = c; + ++out; + } + + template + std::string read_until(InIt& in, InIt end, char end_token, bool& err) + { + std::string ret; + if (in == end) + { + err = true; + return ret; + } + while (*in != end_token) + { + ret += *in; + ++in; + if (in == end) + { + err = true; + return ret; + } + } + return ret; + } + + template + void read_string(InIt& in, InIt end, int len, std::string& str, bool& err) + { + TORRENT_ASSERT(len >= 0); + for (int i = 0; i < len; ++i) + { + if (in == end) + { + err = true; + return; + } + str += *in; + ++in; + } + } + + template + int bencode_recursive(OutIt& out, const entry& e) + { + int ret = 0; + switch(e.type()) + { + case entry::int_t: + write_char(out, 'i'); + ret += write_integer(out, e.integer()); + write_char(out, 'e'); + ret += 2; + break; + case entry::string_t: + ret += write_integer(out, e.string().length()); + write_char(out, ':'); + ret += write_string(e.string(), out); + ret += 1; + break; + case entry::list_t: + write_char(out, 'l'); + for (auto const& i : e.list()) + ret += bencode_recursive(out, i); + write_char(out, 'e'); + ret += 2; + break; + case entry::dictionary_t: + write_char(out, 'd'); + for (auto const& i : e.dict()) + { + // write key + ret += write_integer(out, i.first.length()); + write_char(out, ':'); + ret += write_string(i.first, out); + // write value + ret += bencode_recursive(out, i.second); + ret += 1; + } + write_char(out, 'e'); + ret += 2; + break; + case entry::preformatted_t: + std::copy(e.preformatted().begin(), e.preformatted().end(), out); + ret += static_cast(e.preformatted().size()); + break; + case entry::undefined_t: + + // empty string + write_char(out, '0'); + write_char(out, ':'); + + ret += 2; + break; + } + return ret; + } +#if TORRENT_ABI_VERSION == 1 + template + void bdecode_recursive(InIt& in, InIt end, entry& ret, bool& err, int depth) + { + if (depth >= 100) + { + err = true; + return; + } + + if (in == end) + { + err = true; +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + switch (*in) + { + + // ---------------------------------------------- + // integer + case 'i': + { + ++in; // 'i' + std::string const val = read_until(in, end, 'e', err); + if (err) return; + TORRENT_ASSERT(*in == 'e'); + ++in; // 'e' + ret = entry(entry::int_t); + char* end_pointer; + ret.integer() = std::strtoll(val.c_str(), &end_pointer, 10); +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + if (end_pointer == val.c_str()) + { + err = true; + return; + } + } + break; + + // ---------------------------------------------- + // list + case 'l': + ret = entry(entry::list_t); + ++in; // 'l' + while (*in != 'e') + { + ret.list().emplace_back(); + entry& e = ret.list().back(); + bdecode_recursive(in, end, e, err, depth + 1); + if (err) + { +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + if (in == end) + { + err = true; +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + } +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + TORRENT_ASSERT(*in == 'e'); + ++in; // 'e' + break; + + // ---------------------------------------------- + // dictionary + case 'd': + ret = entry(entry::dictionary_t); + ++in; // 'd' + while (*in != 'e') + { + entry key; + bdecode_recursive(in, end, key, err, depth + 1); + if (err || key.type() != entry::string_t) + { +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + entry& e = ret[key.string()]; + bdecode_recursive(in, end, e, err, depth + 1); + if (err) + { +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + if (in == end) + { + err = true; +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + } +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + TORRENT_ASSERT(*in == 'e'); + ++in; // 'e' + break; + + // ---------------------------------------------- + // string + default: + static_assert(sizeof(*in) == 1, "Input iterator to 8 bit data required"); + if (is_digit(char(*in))) + { + std::string len_s = read_until(in, end, ':', err); + if (err) + { +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + TORRENT_ASSERT(*in == ':'); + ++in; // ':' + int len = atoi(len_s.c_str()); + ret = entry(entry::string_t); + read_string(in, end, len, ret.string(), err); + if (err) + { +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + } + else + { + err = true; +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + } + } +#endif // TORRENT_ABI_VERSION +} + + // This function will encode data to bencoded form. + // + // The entry_ class is the internal representation of the bencoded data + // and it can be used to retrieve information, an entry_ can also be build by + // the program and given to ``bencode()`` to encode it into the ``OutIt`` + // iterator. + // + // ``OutIt`` is an OutputIterator_. It's a template and usually + // instantiated as ostream_iterator_ or back_insert_iterator_. This + // function assumes the value_type of the iterator is a ``char``. + // In order to encode entry ``e`` into a buffer, do:: + // + // std::vector buf; + // bencode(std::back_inserter(buf), e); + // + // .. _OutputIterator: https://en.cppreference.com/w/cpp/named_req/OutputIterator + // .. _ostream_iterator: https://en.cppreference.com/w/cpp/iterator/ostream_iterator + // .. _back_insert_iterator: https://en.cppreference.com/w/cpp/iterator/back_insert_iterator + template int bencode(OutIt out, const entry& e) + { + return aux::bencode_recursive(out, e); + } + +#if TORRENT_ABI_VERSION == 1 + template + TORRENT_DEPRECATED + entry bdecode(InIt start, InIt end) + { + entry e; + bool err = false; + aux::bdecode_recursive(start, end, e, err, 0); + TORRENT_ASSERT(e.m_type_queried == false); + if (err) return entry(); + return e; + } + template + TORRENT_DEPRECATED + entry bdecode(InIt start, InIt end + , typename std::iterator_traits::difference_type& len) + { + entry e; + bool err = false; + InIt s = start; + aux::bdecode_recursive(start, end, e, err, 0); + len = std::distance(s, start); + TORRENT_ASSERT(len >= 0); + if (err) return entry(); + return e; + } +#endif +} + +#endif // TORRENT_BENCODE_HPP_INCLUDED diff --git a/include/libtorrent/bitfield.hpp b/include/libtorrent/bitfield.hpp new file mode 100644 index 0000000..44a0829 --- /dev/null +++ b/include/libtorrent/bitfield.hpp @@ -0,0 +1,335 @@ +/* + +Copyright (c) 2008-2009, 2012-2019, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Falcosc +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BITFIELD_HPP_INCLUDED +#define TORRENT_BITFIELD_HPP_INCLUDED + +#include "libtorrent/assert.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/index_range.hpp" +#include "libtorrent/aux_/unique_ptr.hpp" +#include "libtorrent/aux_/byteswap.hpp" +#include "libtorrent/aux_/ffs.hpp" + +#include // for memset and memcpy +#include // uint32_t + +namespace libtorrent { + + // The bitfield type stores any number of bits as a bitfield + // in a heap allocated array. + struct TORRENT_EXPORT bitfield + { + // constructs a new bitfield. The default constructor creates an empty + // bitfield. ``bits`` is the size of the bitfield (specified in bits). + // ``val`` is the value to initialize the bits to. If not specified + // all bits are initialized to 0. + // + // The constructor taking a pointer ``b`` and ``bits`` copies a bitfield + // from the specified buffer, and ``bits`` number of bits (rounded up to + // the nearest byte boundary). + bitfield() noexcept = default; + explicit bitfield(int bits) { resize(bits); } + bitfield(int bits, bool val) { resize(bits, val); } + bitfield(char const* b, int bits) { assign(b, bits); } + bitfield(bitfield const& rhs) { assign(rhs.data(), rhs.size()); } + bitfield(bitfield&& rhs) noexcept = default; + + // copy bitfield from buffer ``b`` of ``bits`` number of bits, rounded up to + // the nearest byte boundary. + void assign(char const* b, int const bits) + { + resize(bits); + if (bits > 0) + { + std::memcpy(buf(), b, std::size_t((bits + 7) / 8)); + clear_trailing_bits(); + } + } + + // query bit at ``index``. Returns true if bit is 1, otherwise false. + bool operator[](int index) const noexcept + { return get_bit(index); } + bool get_bit(int index) const noexcept + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < size()); + return (buf()[index / 32] & aux::host_to_network(0x80000000 >> (index & 31))) != 0; + } + + // set bit at ``index`` to 0 (clear_bit) or 1 (set_bit). + void clear_bit(int index) noexcept + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < size()); + buf()[index / 32] &= aux::host_to_network(~(0x80000000 >> (index & 31))); + } + void set_bit(int index) noexcept + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < size()); + buf()[index / 32] |= aux::host_to_network(0x80000000 >> (index & 31)); + } + + // returns true if all bits in the bitfield are set + bool all_set() const noexcept; + + // returns true if no bit in the bitfield is set + bool none_set() const noexcept + { + if(size() == 0) return true; + + const int words = num_words(); + std::uint32_t const* b = buf(); + for (int i = 0; i < words; ++i) + { + if (b[i] != 0) return false; + } + return true; + } + + // returns the size of the bitfield in bits. + int size() const noexcept + { + int const bits = m_buf == nullptr ? 0 : int(m_buf[0]); + TORRENT_ASSERT(bits >= 0); + return bits; + } + + // returns the number of 32 bit words are needed to represent all bits in + // this bitfield. + int num_words() const noexcept + { + return (size() + 31) / 32; + } + + // returns the number of bytes needed to represent all bits in this + // bitfield + int num_bytes() const noexcept + { + return (size() + 7) / 8; + } + + // returns true if the bitfield has zero size. + bool empty() const noexcept { return size() == 0; } + + // returns a pointer to the internal buffer of the bitfield, or + // ``nullptr`` if it's empty. + char const* data() const noexcept { return m_buf ? reinterpret_cast(&m_buf[1]) : nullptr; } + char* data() noexcept { return m_buf ? reinterpret_cast(&m_buf[1]) : nullptr; } + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + char const* bytes() const { return data(); } +#endif + + // hidden + bitfield& operator=(bitfield const& rhs) & + { + if (&rhs == this) return *this; + assign(rhs.data(), rhs.size()); + return *this; + } + bitfield& operator=(bitfield&& rhs) & noexcept = default; + + // swaps the bit-fields two variables refer to + void swap(bitfield& rhs) noexcept + { + std::swap(m_buf, rhs.m_buf); + } + + // count the number of bits in the bitfield that are set to 1. + int count() const noexcept; + + // returns the index of the first set bit in the bitfield, i.e. 1 bit. + int find_first_set() const noexcept; + + // returns the index to the last cleared bit in the bitfield, i.e. 0 bit. + int find_last_clear() const noexcept; + + bool operator==(lt::bitfield const& rhs) const; + + // internal + struct const_iterator + { + friend struct bitfield; + + using value_type = bool; + using difference_type = ptrdiff_t; + using pointer = bool const*; + using reference = bool&; + using iterator_category = std::forward_iterator_tag; + + bool operator*() noexcept { return (*buf & aux::host_to_network(bit)) != 0; } + const_iterator& operator++() noexcept { inc(); return *this; } + const_iterator operator++(int) noexcept + { const_iterator ret(*this); inc(); return ret; } + const_iterator& operator--() noexcept { dec(); return *this; } + const_iterator operator--(int) noexcept + { const_iterator ret(*this); dec(); return ret; } + + const_iterator() noexcept {} + bool operator==(const_iterator const& rhs) const noexcept + { return buf == rhs.buf && bit == rhs.bit; } + + bool operator!=(const_iterator const& rhs) const noexcept + { return buf != rhs.buf || bit != rhs.bit; } + + private: + void inc() + { + TORRENT_ASSERT(buf); + if (bit == 0x01) + { + bit = 0x80000000; + ++buf; + } + else + { + bit >>= 1; + } + } + void dec() + { + TORRENT_ASSERT(buf); + if (bit == 0x80000000) + { + bit = 0x01; + --buf; + } + else + { + bit <<= 1; + } + } + const_iterator(std::uint32_t const* ptr, int offset) + : buf(ptr), bit(0x80000000 >> offset) {} + std::uint32_t const* buf = nullptr; + std::uint32_t bit = 0x80000000; + }; + + // internal + const_iterator begin() const noexcept { return const_iterator(m_buf ? buf() : nullptr, 0); } + const_iterator end() const noexcept + { + if (m_buf) + return const_iterator(buf() + num_words() - (((size() & 31) == 0) ? 0 : 1), size() & 31); + else + return const_iterator(nullptr, size() & 31); + } + + // set the size of the bitfield to ``bits`` length. If the bitfield is extended, + // the new bits are initialized to ``val``. + void resize(int bits, bool val); + void resize(int bits); + + // set all bits in the bitfield to 1 (set_all) or 0 (clear_all). + void set_all() noexcept + { + if (size() == 0) return; + std::memset(buf(), 0xff, std::size_t(num_words()) * 4); + clear_trailing_bits(); + } + void clear_all() noexcept + { + if (size() == 0) return; + std::memset(buf(), 0x00, std::size_t(num_words()) * 4); + } + + // make the bitfield empty, of zero size. + void clear() noexcept { m_buf.reset(); } + + private: + + std::uint32_t const* buf() const noexcept { TORRENT_ASSERT(m_buf); return &m_buf[1]; } + std::uint32_t* buf() noexcept { TORRENT_ASSERT(m_buf); return &m_buf[1]; } + void clear_trailing_bits() noexcept + { + // clear the tail bits in the last byte + if (size() & 31) buf()[num_words() - 1] &= aux::host_to_network(0xffffffff << (32 - (size() & 31))); + } + + // the first element is not part of the bitfield, it's the + // number of bits. + aux::unique_ptr m_buf; + }; + + template + struct typed_bitfield : bitfield + { + typed_bitfield() noexcept {} + typed_bitfield(typed_bitfield&& rhs) noexcept + : bitfield(std::forward(rhs)) + {} + typed_bitfield(typed_bitfield const& rhs) + : bitfield(static_cast(rhs)) + {} + typed_bitfield(bitfield&& rhs) noexcept : bitfield(std::forward(rhs)) {} // NOLINT + typed_bitfield(bitfield const& rhs) : bitfield(rhs) {} // NOLINT + typed_bitfield& operator=(typed_bitfield&& rhs) & noexcept + { + this->bitfield::operator=(std::forward(rhs)); + return *this; + } + typed_bitfield& operator=(typed_bitfield const& rhs) & + { + this->bitfield::operator=(rhs); + return *this; + } + using bitfield::bitfield; + + // returns an object that can be used in a range-for to iterate over all + // indices in the bitfield + index_range range() const noexcept + { + return {IndexType{0}, end_index()}; + } + + bool operator[](IndexType const index) const + { return this->bitfield::get_bit(static_cast(index)); } + + bool get_bit(IndexType const index) const + { return this->bitfield::get_bit(static_cast(index)); } + + void clear_bit(IndexType const index) + { this->bitfield::clear_bit(static_cast(index)); } + + void set_bit(IndexType const index) + { this->bitfield::set_bit(static_cast(index)); } + + IndexType end_index() const noexcept { return IndexType(this->size()); } + }; +} + +#endif // TORRENT_BITFIELD_HPP_INCLUDED diff --git a/include/libtorrent/bloom_filter.hpp b/include/libtorrent/bloom_filter.hpp new file mode 100644 index 0000000..ce062a7 --- /dev/null +++ b/include/libtorrent/bloom_filter.hpp @@ -0,0 +1,80 @@ +/* + +Copyright (c) 2010-2011, 2015-2017, 2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BLOOM_FILTER_HPP_INCLUDED +#define TORRENT_BLOOM_FILTER_HPP_INCLUDED + +#include "libtorrent/sha1_hash.hpp" + +#include // for log() +#include + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT void set_bits(std::uint8_t const* k, std::uint8_t* bits, int len); + TORRENT_EXTRA_EXPORT bool has_bits(std::uint8_t const* k, std::uint8_t const* bits, int len); + TORRENT_EXTRA_EXPORT int count_zero_bits(std::uint8_t const* bits, int len); + + template + struct bloom_filter + { + bool find(sha1_hash const& k) const + { return has_bits(&k[0], bits, N); } + + void set(sha1_hash const& k) + { set_bits(&k[0], bits, N); } + + std::string to_string() const + { return std::string(reinterpret_cast(&bits[0]), N); } + + void from_string(char const* str) + { std::memcpy(bits, str, N); } + + void clear() { std::memset(bits, 0, N); } + + float size() const + { + int const c = (std::min)(count_zero_bits(bits, N), (N * 8) - 1); + int const m = N * 8; + return std::log(c / float(m)) / (2.f * std::log(1.f - 1.f/m)); + } + + bloom_filter() { clear(); } + + private: + std::uint8_t bits[N]; + }; + +} + +#endif // TORRENT_BLOOM_FILTER_HPP_INCLUDED diff --git a/include/libtorrent/bt_peer_connection.hpp b/include/libtorrent/bt_peer_connection.hpp new file mode 100644 index 0000000..f5f87c3 --- /dev/null +++ b/include/libtorrent/bt_peer_connection.hpp @@ -0,0 +1,519 @@ +/* + +Copyright (c) 2007, Un Shyam +Copyright (c) 2006-2020, 2022, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2018, Greg Hazel +Copyright (c) 2018-2019, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BT_PEER_CONNECTION_HPP_INCLUDED +#define TORRENT_BT_PEER_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/debug.hpp" +#include "libtorrent/aux_/buffer.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/pe_crypto.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/hash_picker.hpp" + +namespace libtorrent { + +#ifndef TORRENT_DISABLE_EXTENSIONS + struct TORRENT_EXTRA_EXPORT ut_pex_peer_store + { + // stores all peers this peer is connected to. These lists + // are updated with each pex message and are limited in size + // to protect against malicious clients. These lists are also + // used for looking up which peer a peer that supports holepunch + // came from. + // these are vectors to save memory and keep the items close + // together for performance. Inserting and removing is relatively + // cheap since the lists' size is limited + using peers4_t = std::vector>; + peers4_t m_peers; + using peers6_t = std::vector>; + peers6_t m_peers6; + + bool was_introduced_by(tcp::endpoint const& ep); + + virtual ~ut_pex_peer_store() {} + }; +#endif + + struct TORRENT_EXTRA_EXPORT bt_peer_connection + : peer_connection + { + friend struct invariant_access; + + // this is the constructor where the we are the active part. + // The peer_connection should handshake and verify that the + // other end has the correct id + explicit bt_peer_connection(peer_connection_args& pack); + + void start() override; + + enum + { + // pex_msg = 1, + // metadata_msg = 2, + upload_only_msg = 3, + holepunch_msg = 4, + // recommend_msg = 5, + // comment_msg = 6, + dont_have_msg = 7, + share_mode_msg = 8 + }; + + ~bt_peer_connection() override; + + peer_id our_pid() const override { return m_our_peer_id; } + +#if !defined TORRENT_DISABLE_ENCRYPTION + bool supports_encryption() const + { return m_encrypted; } + bool rc4_encrypted() const + { return m_rc4_encrypted; } + + void switch_send_crypto(std::shared_ptr crypto); + void switch_recv_crypto(std::shared_ptr crypto); +#endif + + connection_type type() const override + { return connection_type::bittorrent; } + + enum message_type + { + // standard messages + msg_choke = 0, + msg_unchoke, + msg_interested, + msg_not_interested, + msg_have, + msg_bitfield, + msg_request, + msg_piece, + msg_cancel, + // DHT extension + msg_dht_port, + // FAST extension + msg_suggest_piece = 0xd, + msg_have_all, + msg_have_none, + msg_reject_request, + msg_allowed_fast, + + // extension protocol message + msg_extended = 20, + + msg_hash_request = 21, + msg_hashes, + msg_hash_reject, + + num_supported_messages + }; + + enum class hp_message : std::uint8_t + { + // msg_types + rendezvous = 0, + connect = 1, + failed = 2 + }; + + enum class hp_error + { + // error codes + no_error = 0, + no_such_peer = 1, + not_connected = 2, + no_support = 3, + no_self = 4 + }; + + // called from the main loop when this connection has any + // work to do. + + void on_sent(error_code const& error + , std::size_t bytes_transferred) override; + void on_receive(error_code const& error + , std::size_t bytes_transferred) override; + void on_receive_impl(std::size_t bytes_transferred); + +#if !defined TORRENT_DISABLE_ENCRYPTION + // next_barrier, buffers-to-prepend + std::tuple>> + hit_send_barrier(span> iovec) override; +#endif + + void get_specific_peer_info(peer_info& p) const override; + bool in_handshake() const override; + bool packet_finished() const { return m_recv_buffer.packet_finished(); } + + bool supports_holepunch() const { return m_holepunch_id != 0; } +#ifndef TORRENT_DISABLE_EXTENSIONS + void set_ut_pex(std::weak_ptr ut_pex) + { m_ut_pex = std::move(ut_pex); } + bool was_introduced_by(tcp::endpoint const& ep) const + { auto p = m_ut_pex.lock(); return p && p->was_introduced_by(ep); } +#endif + + bool support_extensions() const { return m_supports_extensions; } + + // the message handlers are called + // each time a recv() returns some new + // data, the last time it will be called + // is when the entire packet has been + // received, then it will no longer + // be called. i.e. most handlers need + // to check how much of the packet they + // have received before any processing + void on_choke(int received); + void on_unchoke(int received); + void on_interested(int received); + void on_not_interested(int received); + void on_have(int received); + void on_bitfield(int received); + void on_request(int received); + void on_piece(int received); + void on_cancel(int received); + void on_hash_request(int received); + void on_hashes(int received); + void on_hash_reject(int received); + + // DHT extension + void on_dht_port(int received); + + // FAST extension + void on_suggest_piece(int received); + void on_have_all(int received); + void on_have_none(int received); + void on_reject_request(int received); + void on_allowed_fast(int received); + void on_holepunch(); + + void on_extended(int received); + + void on_extended_handshake(); + + template + void extension_notify(F message, Args... args); + + // the following functions appends messages + // to the send buffer + void write_choke() override; + void write_unchoke() override; + void write_interested() override; + void write_not_interested() override; + void write_request(peer_request const& r) override; + void write_cancel(peer_request const& r) override; + void write_bitfield() override; + void write_have(piece_index_t index) override; + void write_dont_have(piece_index_t index) override; + void write_piece(peer_request const& r, disk_buffer_holder buffer) override; + void write_keepalive() override; + void write_handshake(); + void write_upload_only(bool enabled) override; + void write_extensions(); + void write_share_mode(); + void write_holepunch_msg(hp_message type, tcp::endpoint const& ep + , hp_error error = hp_error::no_error); + void write_hash_request(hash_request const& req); + void write_hashes(hash_request const& req, span hashes); + void write_hash_reject(hash_request const& req, sha256_hash const& file_root); + + void maybe_send_hash_request(); + + // DHT extension + void write_dht_port(int listen_port); + + // FAST extension + void write_have_all(); + void write_have_none(); + void write_reject_request(peer_request const&) override; + void write_allow_fast(piece_index_t piece) override; + void write_suggest(piece_index_t piece) override; + + void on_connected() override; + void on_metadata() override; + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; + time_point m_last_choke; +#endif + + private: + + template + void send_message(message_type const type + , counters::stats_counter_t const counter + , Args... args) + { + TORRENT_ASSERT(m_sent_handshake); + TORRENT_ASSERT(m_sent_bitfield); + + char msg[5 + sizeof...(Args) * 4] + = { 0,0,0,1 + sizeof...(Args) * 4, static_cast(type) }; + char* ptr = msg + 5; + TORRENT_UNUSED(ptr); + + int tmp[] = {0, (aux::write_int32(args, ptr), 0)...}; + TORRENT_UNUSED(tmp); + + send_buffer(msg); + + stats_counters().inc_stats_counter(counter); + } + + void write_dht_port(); + + bool dispatch_message(int received); + // returns the block currently being + // downloaded. And the progress of that + // block. If the peer isn't downloading + // a piece for the moment, the boost::optional + // will be invalid. + piece_block_progress downloading_piece_progress() const override; + +#if !defined TORRENT_DISABLE_ENCRYPTION + + // if (is_local()), we are 'a' otherwise 'b' + // + // 1. a -> b dhkey, pad + // 2. b -> a dhkey, pad + // 3. a -> b sync, payload + // 4. b -> a sync, payload + // 5. a -> b payload + + void write_pe1_2_dhkey(); + void write_pe3_sync(); + void write_pe4_sync(int crypto_select); + + void write_pe_vc_cryptofield(span write_buf + , int crypto_field, int pad_size); + + // helper to cut down on boilerplate + void rc4_decrypt(span buf); +#endif + + public: + + // these functions encrypt the send buffer if m_rc4_encrypted + // is true, otherwise it passes the call to the + // peer_connection functions of the same names + template + void append_const_send_buffer(Holder holder, int size) + { +#if !defined TORRENT_DISABLE_ENCRYPTION + if (!m_enc_handler.is_send_plaintext()) + { + // if we're encrypting this buffer, we need to make a copy + // since we'll mutate it + aux::buffer buf(size, {holder.data(), size}); + append_send_buffer(std::move(buf), size); + } + else +#endif + { + append_send_buffer(std::move(holder), size); + } + } + + private: + +#if !defined TORRENT_DISABLE_ENCRYPTION + void init_bt_handshake(); +#endif + + enum class state_t : std::uint8_t + { +#if !defined TORRENT_DISABLE_ENCRYPTION + read_pe_dhkey, + read_pe_syncvc, + read_pe_synchash, + read_pe_skey_vc, + read_pe_cryptofield, + read_pe_pad, + read_pe_ia, +#endif + read_protocol_identifier, + read_info_hash, + read_peer_id, + + // handshake complete + read_packet_size, + read_packet + }; + + // state of on_receive. one of the enums in state_t + state_t m_state = state_t::read_protocol_identifier; + + // this is set to true if the handshake from + // the peer indicated that it supports the + // extension protocol + bool m_supports_extensions:1; + bool m_supports_dht_port:1; + bool m_supports_fast:1; + + // this is set to true when we send the bitfield message. + // for magnet links we can't do that right away, + // since we don't know how many pieces there are in + // the torrent. + bool m_sent_bitfield:1; + + // true if we're done sending the bittorrent handshake, + // and can send bittorrent messages + bool m_sent_handshake:1; + + // set to true once we send the allowed-fast messages. This is + // only done once per connection + bool m_sent_allowed_fast:1; + +#if !defined TORRENT_DISABLE_ENCRYPTION + // this is set to true after the encryption method has been + // successfully negotiated (either plaintext or rc4), to signal + // automatic encryption/decryption. + bool m_encrypted:1; + + // true if rc4, false if plaintext + bool m_rc4_encrypted:1; + +// this is a legitimate use of a shadow field +#ifdef __clang__ +#pragma clang diagnostic push +// macOS clang doesn't have -Wshadow-field +#pragma clang diagnostic ignored "-Wunknown-pragmas" +// Xcode 9 needs this +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wshadow-field" +#endif + aux::crypto_receive_buffer m_recv_buffer; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif + + std::string m_client_version; + + // the peer ID we advertise for ourself + peer_id const m_our_peer_id; + + // this is a queue of ranges that describes + // where in the send buffer actual payload + // data is located. This is currently + // only used to be able to gather statistics + // separately on payload and protocol data. + struct range + { + range(int s, int l) + : start(s) + , length(l) + { + TORRENT_ASSERT(s >= 0); + TORRENT_ASSERT(l > 0); + } + int start; + int length; + }; + + std::vector m_payloads; + + std::vector m_hash_requests; + +#if !defined TORRENT_DISABLE_ENCRYPTION + // initialized during write_pe1_2_dhkey, and destroyed on + // creation of m_enc_handler. Cannot reinitialize once + // initialized. + std::unique_ptr m_dh_key_exchange; + + // used during an encrypted handshake then moved + // into m_enc_handler if rc4 encryption is negotiated + // otherwise it is destroyed when the handshake completes + std::shared_ptr m_rc4; + + // if encryption is negotiated, this is used for + // encryption/decryption during the entire session. + encryption_handler m_enc_handler; + + // (outgoing only) synchronize verification constant with + // remote peer, this will hold rc4_decrypt(vc). Destroyed + // after the sync step. + std::unique_ptr m_sync_vc; + + // (incoming only) synchronize hash with remote peer, holds + // the sync hash (hash("req1",secret)). Destroyed after the + // sync step. + std::unique_ptr m_sync_hash; + + // used to disconnect peer if sync points are not found within + // the maximum number of bytes + int m_sync_bytes_read = 0; +#endif + + // the message ID for upload only message + // 0 if not supported + std::uint8_t m_upload_only_id = 0; + + // the message ID for holepunch messages + std::uint8_t m_holepunch_id = 0; + + // the message ID for don't-have message + std::uint8_t m_dont_have_id = 0; + + // the message ID for share mode message + // 0 if not supported + std::uint8_t m_share_mode_id = 0; + +#ifndef TORRENT_DISABLE_EXTENSIONS + std::weak_ptr m_ut_pex; +#endif + + std::array m_reserved_bits; + }; +} + +#endif // TORRENT_BT_PEER_CONNECTION_HPP_INCLUDED diff --git a/include/libtorrent/choker.hpp b/include/libtorrent/choker.hpp new file mode 100644 index 0000000..e593ca0 --- /dev/null +++ b/include/libtorrent/choker.hpp @@ -0,0 +1,60 @@ +/* + +Copyright (c) 2014, 2017-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CHOKER_HPP_INCLUDED +#define TORRENT_CHOKER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/time.hpp" // for time_duration +#include + +namespace libtorrent { + +namespace aux { + struct session_settings; +} + struct peer_connection; + + // sorts the vector of peers in-place. When returning, the top unchoke slots + // elements are the peers we should unchoke. This is similar to a partial + // sort. Only the unchoke slots first elements are sorted. + // the return value are the number of peers that should be unchoked. This + // is also the number of elements that are valid at the beginning of the + // peer list. Peers beyond this initial range are not sorted. + TORRENT_EXTRA_EXPORT int unchoke_sort(std::vector& peers + , time_duration unchoke_interval + , aux::session_settings const& sett); + +} + +#endif // TORRENT_CHOKER_INCLUDED diff --git a/include/libtorrent/client_data.hpp b/include/libtorrent/client_data.hpp new file mode 100644 index 0000000..885fdd4 --- /dev/null +++ b/include/libtorrent/client_data.hpp @@ -0,0 +1,104 @@ +/* + +Copyright (c) 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CLIENT_DATA_HPP_INCLUDED +#define TORRENT_CLIENT_DATA_HPP_INCLUDED + +#include "libtorrent/aux_/export.hpp" +#include + +namespace libtorrent { + +// A thin wrapper around a void pointer used as "user data". i.e. an opaque +// cookie passed in to libtorrent and returned on demand. It adds type-safety by +// requiring the same type be requested out of it as was assigned to it. +struct TORRENT_EXPORT client_data_t +{ + // construct a nullptr client data + client_data_t() = default; + + // initialize the client data with the specified pointer + template + explicit client_data_t(T* v) + : m_type_ptr(type()) + , m_client_ptr(v) + {} + + // assigns a new pointer to the client data + template + client_data_t& operator=(T* v) + { + m_type_ptr = type(); + m_client_ptr = v; + return *this; + } + + // request to retrieve the pointer back again. The type ``T`` must be + // identical to the type of the pointer assigned earlier, including + // cv-qualifiers. + template + T* get() const + { + if (m_type_ptr != type()) return nullptr; + return static_cast(m_client_ptr); + } + template ::value>::type> + explicit operator T() const + { + if (m_type_ptr != type::type>()) return nullptr; + return static_cast(m_client_ptr); + } + +#if TORRENT_ABI_VERSION > 2 + // we don't allow type-unsafe operations + operator void*() const = delete; + operator void const*() const = delete; + client_data_t& operator=(void*) = delete; + client_data_t& operator=(void const*) = delete; +#endif + +private: + template + char const* type() const + { + // each unique T will instantiate a unique "instance", and have a unique + // address + static const char instance = 0; + return &instance; + } + char const* m_type_ptr = nullptr; + void* m_client_ptr = nullptr; +}; + +} + +#endif diff --git a/include/libtorrent/close_reason.hpp b/include/libtorrent/close_reason.hpp new file mode 100644 index 0000000..8a22b29 --- /dev/null +++ b/include/libtorrent/close_reason.hpp @@ -0,0 +1,156 @@ +/* + +Copyright (c) 2015, 2017, 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CLOSE_REASON_HPP +#define TORRENT_CLOSE_REASON_HPP + +#include "libtorrent/error_code.hpp" + +namespace libtorrent { + + // internal: these are all the reasons to disconnect a peer + // all reasons caused by the peer sending unexpected data + // are 256 and up. + enum class close_reason_t : std::uint16_t + { + // no reason specified. Generic close. + none = 0, + + // we're already connected to + duplicate_peer_id, + + // this torrent has been removed, paused or stopped from this client. + torrent_removed, + + // client failed to allocate necessary memory for this peer connection + no_memory, + + // the source port of this peer is blocked + port_blocked, + + // the source IP has been blocked + blocked, + + // both ends of the connection are upload-only. staying connected would + // be redundant + upload_to_upload, + + // connection was closed because the other end is upload only and does + // not have any pieces we're interested in + not_interested_upload_only, + + // peer connection timed out (generic timeout) + timeout, + + // the peers have not been interested in each other for a very long time. + // disconnect + timed_out_interest, + + // the peer has not sent any message in a long time. + timed_out_activity, + + // the peer did not complete the handshake in too long + timed_out_handshake, + + // the peer sent an interested message, but did not send a request + // after a very long time after being unchoked. + timed_out_request, + + // the encryption mode is blocked + protocol_blocked, + + // the peer was disconnected in the hopes of finding a better peer + // in the swarm + peer_churn, + + // we have too many peers connected + too_many_connections, + + // we have too many file-descriptors open + too_many_files, + + // the encryption handshake failed + encryption_error = 256, + + // the info hash sent as part of the handshake was not what we expected + invalid_info_hash, + + self_connection, + + // the metadata received matched the info-hash, but failed to parse. + // this is either someone finding a SHA1 collision, or the author of + // the magnet link creating it from an invalid torrent + invalid_metadata, + + // the advertised metadata size + metadata_too_big, + + // invalid bittorrent messages + message_too_big, + invalid_message_id, + invalid_message, + invalid_piece_message, + invalid_have_message, + invalid_bitfield_message, + invalid_choke_message, + invalid_unchoke_message, + invalid_interested_message, + invalid_not_interested_message, + invalid_request_message, + invalid_reject_message, + invalid_allow_fast_message, + invalid_extended_message, + invalid_cancel_message, + invalid_dht_port_message, + invalid_suggest_message, + invalid_have_all_message, + invalid_dont_have_message, + invalid_have_none_message, + invalid_pex_message, + invalid_metadata_request_message, + invalid_metadata_message, + invalid_metadata_offset, + + // the peer sent a request while being choked + request_when_choked, + + // the peer sent corrupt data + corrupt_pieces, + + pex_message_too_big, + pex_too_frequent + }; + + close_reason_t error_to_close_reason(error_code const& ec); +} + +#endif diff --git a/include/libtorrent/config.hpp b/include/libtorrent/config.hpp new file mode 100644 index 0000000..6752a5e --- /dev/null +++ b/include/libtorrent/config.hpp @@ -0,0 +1,682 @@ +/* + +Copyright (c) 2005, 2008-2022, Arvid Norberg +Copyright (c) 2009, Daniel Wallin +Copyright (c) 2015, John Sebastian Peterson +Copyright (c) 2016-2017, 2019, 2021, Alden Torres +Copyright (c) 2016, 2019, Andrei Kurushin +Copyright (c) 2016, terry zhao +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, Pavel Pimenov +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CONFIG_HPP_INCLUDED +#define TORRENT_CONFIG_HPP_INCLUDED + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#if !defined __MINGW64__ && !defined __MINGW32__ +#define _FILE_OFFSET_BITS 64 +#endif + +#include + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#ifdef __linux__ +#include // for LINUX_VERSION_CODE and KERNEL_VERSION +#endif // __linux + +#if defined __MINGW64__ || defined __MINGW32__ +// GCC warns on format codes that are incompatible with glibc, which the windows +// format codes are. So we need to disable those for mingw targets +#pragma GCC diagnostic ignored "-Wformat" +#pragma GCC diagnostic ignored "-Wformat-extra-args" +// Mingw does not like friend declarations of dllexport functions. This +// suppresses those warnings +#pragma GCC diagnostic ignored "-Wattributes" +#endif + +// This is the GCC indication of building with address sanitizer +#if defined __SANITIZE_ADDRESS__ && __SANITIZE_ADDRESS__ +#define TORRENT_ADDRESS_SANITIZER 1 +#endif + +// This is the clang indication of building with address sanitizer +#if defined(__has_feature) +#if __has_feature(address_sanitizer) +#define TORRENT_ADDRESS_SANITIZER 1 +#endif +#endif + +#if defined __GNUC__ + +#ifdef _GLIBCXX_CONCEPT_CHECKS +#define TORRENT_COMPLETE_TYPES_REQUIRED 1 +#endif + +// ======= SUNPRO ========= + +#elif defined __SUNPRO_CC + +#define TORRENT_COMPLETE_TYPES_REQUIRED 1 + +// ======= MSVC ========= + +#elif defined BOOST_MSVC + +// class X needs to have dll-interface to be used by clients of class Y +#pragma warning(disable:4251) + +#endif + + +// ======= PLATFORMS ========= + + +// set up defines for target environments +// ==== AMIGA === +#if defined __AMIGA__ || defined __amigaos__ || defined __AROS__ +#define TORRENT_AMIGA +#define TORRENT_USE_IOSTREAM 0 +// set this to 1 to disable all floating point operations +// (disables some float-dependent APIs) +#define TORRENT_NO_FPU 1 +#define TORRENT_USE_I2P 0 + +// ==== Darwin/BSD === +#elif (defined __APPLE__ && defined __MACH__) || defined __FreeBSD__ || defined __NetBSD__ \ + || defined __OpenBSD__ || defined __bsdi__ || defined __DragonFly__ \ + || defined __FreeBSD_kernel__ +#define TORRENT_BSD + +#if defined __APPLE__ + +#include +#include + +#if defined __MACH__ && MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 +#define TORRENT_HAS_COPYFILE 1 +#endif + +#define TORRENT_NATIVE_UTF8 1 + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 +// on OSX, use the built-in common crypto for built-in +# if !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT +# define TORRENT_USE_COMMONCRYPTO 1 +# endif +#endif // MAC_OS_X_VERSION_MIN_REQUIRED + +// execinfo.h is available in the MacOS X 10.5 SDK. +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 +#define TORRENT_USE_EXECINFO 1 +#endif + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 +// this is used for the ip_change_notifier on macOS, which isn't supported on +// 10.6 and earlier +#define TORRENT_USE_SYSTEMCONFIGURATION 1 +#endif + +#if TARGET_OS_IPHONE +#define TORRENT_USE_SC_NETWORK_REACHABILITY 1 +#endif + +#define TORRENT_USE_DEV_RANDOM 1 + +#else + +// non-Apple BSD +#define TORRENT_USE_GETRANDOM 1 +#define TORRENT_HAS_PTHREAD_SET_NAME 1 + +#endif // __APPLE__ + +#define TORRENT_HAS_SYMLINK 1 + +#ifndef TORRENT_HAVE_MMAP +#define TORRENT_HAVE_MMAP 1 +#endif +#define TORRENT_USE_MADVISE 1 + +#define TORRENT_HAS_FALLOCATE 0 + +#define TORRENT_USE_IFADDRS 1 +#define TORRENT_USE_SYSCTL 1 +#define TORRENT_USE_IFCONF 1 + + +// ==== LINUX === +#elif defined __linux__ +#define TORRENT_LINUX + +#ifndef TORRENT_HAVE_MMAP +#define TORRENT_HAVE_MMAP 1 +#endif + +#if defined __GLIBC__ && (__GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 27)) +#define TORRENT_HAS_COPY_FILE_RANGE 0 +#elif defined __ANDROID__ +#define TORRENT_HAS_COPY_FILE_RANGE 0 +#else +#define TORRENT_HAS_COPY_FILE_RANGE 1 +#endif + +#define TORRENT_HAS_PTHREAD_SET_NAME 1 +#define TORRENT_HAS_SYMLINK 1 +#define TORRENT_USE_MADVISE 1 +#define TORRENT_USE_NETLINK 1 +#define TORRENT_USE_IFADDRS 0 +#define TORRENT_USE_IFCONF 1 +#define TORRENT_HAS_SALEN 0 +#define TORRENT_USE_FDATASYNC 1 + +#if defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ > 24)) +#define TORRENT_USE_GETRANDOM 1 +#endif + +// ===== ANDROID ===== (almost linux, sort of) +#if defined __ANDROID__ +#define TORRENT_ANDROID +#define TORRENT_HAS_PTHREAD_SET_NAME 1 + +#if __ANDROID_API__ < 21 +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_HAS_FADVISE 0 +#endif // API < 21 + +// android 32 bits has real problems with fseeko +#if (__ANDROID_API__ < 24) || defined __arm__ || defined __i386__ +#define TORRENT_HAS_FSEEKO 0 +#endif + +#if __ANDROID_API__ < 24 +#define TORRENT_HAS_FTELLO 0 +#endif // API < 24 + +// Starting Android 11 (API >= 30), the enum_routes using NETLINK +// is not possible anymore. For other functions, it's not clear +// that IFADDRS is working as expected for API >= 30, but at least +// it is supported. +// See https://developer.android.com/training/articles/user-data-ids#mac-11-plus +#if __ANDROID_API__ >= 24 +#undef TORRENT_USE_NETLINK +#undef TORRENT_USE_IFADDRS +#define TORRENT_USE_NETLINK 0 +#define TORRENT_USE_IFADDRS 1 +#endif // API >= 24 + +#else // ANDROID + +// posix_fallocate() is not available in glibc under these condition +#if defined _XOPEN_SOURCE && _XOPEN_SOURCE < 600 +#define TORRENT_HAS_FALLOCATE 0 +#elif defined _POSIX_C_SOURCE && _POSIX_C_SOURCE < 200112L +#define TORRENT_HAS_FALLOCATE 0 +#endif + +#define TORRENT_USE_SYNC_FILE_RANGE 1 + +#endif // ANDROID + +#if defined __GLIBC__ && ( defined __x86_64__ || defined __i386 \ + || defined _M_X64 || defined _M_IX86 ) +#define TORRENT_USE_EXECINFO 1 +#endif + +// ==== MINGW === +#elif defined __MINGW32__ || defined __MINGW64__ +#define TORRENT_MINGW +#define TORRENT_WINDOWS +#ifndef TORRENT_HAVE_MAP_VIEW_OF_FILE +#define TORRENT_HAVE_MAP_VIEW_OF_FILE 1 +#endif +#define TORRENT_USE_RLIMIT 0 +#define TORRENT_USE_NETLINK 0 +#define TORRENT_USE_GETADAPTERSADDRESSES 1 +#define TORRENT_HAS_SALEN 0 +#define TORRENT_USE_GETIPFORWARDTABLE 1 +#define TORRENT_USE_UNC_PATHS 1 + +// mingw doesn't implement random_device. +#define TORRENT_BROKEN_RANDOM_DEVICE 1 + +# if !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT + +#ifdef NTDDI_VERSION +# if (NTDDI_VERSION >= NTDDI_VISTA) +# define TORRENT_USE_CNG 1 +# endif +#else // NTDDI_VERSION not defined so use simple _WIN32_WINNT check +# if _WIN32_WINNT >= 0x0600 +# define TORRENT_USE_CNG 1 +# endif +#endif + +# if !defined TORRENT_USE_CNG +// unless some other crypto library has been specified, default to the native +// windows CryptoAPI +#define TORRENT_USE_CRYPTOAPI 1 +#define TORRENT_USE_DEV_RANDOM 0 + +#ifdef NTDDI_VERSION +# if (NTDDI_VERSION > NTDDI_WINXPSP2) +# define TORRENT_USE_CRYPTOAPI_SHA_512 1 +# endif +#else // NTDDI_VERSION not defined so use simple _WIN32_WINNT check +# if _WIN32_WINNT >= 0x0600 +# define TORRENT_USE_CRYPTOAPI_SHA_512 1 +# endif +#endif + +#endif // !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT + +#endif +// ==== WINDOWS === +#elif defined _WIN32 +#define TORRENT_WINDOWS +#ifndef TORRENT_USE_GETIPFORWARDTABLE +# define TORRENT_USE_GETIPFORWARDTABLE 1 +#endif + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#ifndef TORRENT_HAVE_MAP_VIEW_OF_FILE +#define TORRENT_HAVE_MAP_VIEW_OF_FILE 1 +#endif + +# if !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT + +#ifdef NTDDI_VERSION +# if (NTDDI_VERSION >= NTDDI_VISTA) +# define TORRENT_USE_CNG 1 +# endif +#else // NTDDI_VERSION not defined so use simple _WIN32_WINNT check +# if _WIN32_WINNT >= 0x0600 +# define TORRENT_USE_CNG 1 +# endif +#endif + +# if !defined TORRENT_USE_CNG +// unless some other crypto library has been specified, default to the native +// windows CryptoAPI +#define TORRENT_USE_CRYPTOAPI 1 +#define TORRENT_USE_DEV_RANDOM 0 + +#ifdef NTDDI_VERSION +# if (NTDDI_VERSION > NTDDI_WINXPSP2) +# define TORRENT_USE_CRYPTOAPI_SHA_512 1 +# endif +#else // NTDDI_VERSION not defined so use simple _WIN32_WINNT check +# if _WIN32_WINNT >= 0x0600 +# define TORRENT_USE_CRYPTOAPI_SHA_512 1 +# endif +#endif + +#endif // !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT + +#endif + +#define TORRENT_USE_GETADAPTERSADDRESSES 1 +#define TORRENT_HAS_SALEN 0 +#define TORRENT_USE_RLIMIT 0 +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_USE_UNC_PATHS 1 + +// ==== WINRT === +#if defined(WINAPI_FAMILY_PARTITION) +# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) \ + && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +# define TORRENT_WINRT +# endif +#endif + +// ==== SOLARIS === +#elif defined sun || defined __sun +#define TORRENT_SOLARIS +#define TORRENT_USE_IFCONF 1 +#define TORRENT_HAS_SALEN 0 +#ifndef TORRENT_HAVE_MMAP +#define TORRENT_HAVE_MMAP 1 +#endif +#define TORRENT_USE_MADVISE 1 +#define TORRENT_HAS_SYMLINK 1 +#define TORRENT_USE_GETRANDOM 1 + +// ==== BEOS === +#elif defined __BEOS__ || defined __HAIKU__ +#define TORRENT_BEOS +#include // B_PATH_NAME_LENGTH +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_NATIVE_UTF8 1 +#define TORRENT_USE_IFCONF 1 +#define TORRENT_USE_GRTTABLE 1 + +// ==== GNU/Hurd === +#elif defined __GNU__ +#define TORRENT_HURD +#define TORRENT_USE_IFADDRS 1 +#define TORRENT_USE_IFCONF 1 +#define TORRENT_HAS_SYMLINK 1 +#define TORRENT_USE_GETRANDOM 1 + +// ==== eCS(OS/2) === +#elif defined __OS2__ +#define TORRENT_OS2 +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_USE_IFCONF 1 +#define TORRENT_USE_SYSCTL 1 + +#else + +#ifdef _MSC_VER +#pragma message ( "unknown OS, assuming BSD" ) +#else +#warning "unknown OS, assuming BSD" +#endif + +#define TORRENT_BSD +#endif + +#define TORRENT_UNUSED(x) (void)(x) + +#if defined __GNUC__ || defined __clang__ +#define TORRENT_FORMAT(fmt, ellipsis) __attribute__((__format__(__printf__, fmt, ellipsis))) +#else +#define TORRENT_FORMAT(fmt, ellipsis) +#endif + +#ifndef TORRENT_BROKEN_RANDOM_DEVICE +#define TORRENT_BROKEN_RANDOM_DEVICE 0 +#endif + +#ifndef TORRENT_HAS_SALEN +#define TORRENT_HAS_SALEN 1 +#endif + +#ifndef TORRENT_USE_GETADAPTERSADDRESSES +#define TORRENT_USE_GETADAPTERSADDRESSES 0 +#endif + +#ifndef TORRENT_USE_NETLINK +#define TORRENT_USE_NETLINK 0 +#endif + +#ifndef TORRENT_USE_EXECINFO +#define TORRENT_USE_EXECINFO 0 +#endif + +#ifndef TORRENT_USE_SYSCTL +#define TORRENT_USE_SYSCTL 0 +#endif + +#ifndef TORRENT_USE_GETIPFORWARDTABLE +#define TORRENT_USE_GETIPFORWARDTABLE 0 +#endif + +#if defined BOOST_NO_STD_WSTRING +#error your C++ standard library appears to be missing std::wstring. This type is required on windows +#endif + +#ifndef TORRENT_HAS_FALLOCATE +#define TORRENT_HAS_FALLOCATE 1 +#endif + +#ifndef TORRENT_HAS_FADVISE +#define TORRENT_HAS_FADVISE 1 +#endif + +#ifndef TORRENT_HAS_FSEEKO +#define TORRENT_HAS_FSEEKO 1 +#endif + +#ifndef TORRENT_HAS_FTELLO +#define TORRENT_HAS_FTELLO 1 +#endif + +#ifndef TORRENT_USE_COMMONCRYPTO +#define TORRENT_USE_COMMONCRYPTO 0 +#endif + +#ifndef TORRENT_USE_SYSTEMCONFIGURATION +#define TORRENT_USE_SYSTEMCONFIGURATION 0 +#endif + +#ifndef TORRENT_USE_SC_NETWORK_REACHABILITY +#define TORRENT_USE_SC_NETWORK_REACHABILITY 0 +#endif + +#ifndef TORRENT_USE_CRYPTOAPI +#define TORRENT_USE_CRYPTOAPI 0 +#endif + +#ifndef TORRENT_USE_CRYPTOAPI_SHA_512 +#define TORRENT_USE_CRYPTOAPI_SHA_512 0 +#endif + +#ifndef TORRENT_USE_CNG +#define TORRENT_USE_CNG 0 +#endif + +#ifndef TORRENT_USE_DEV_RANDOM +#define TORRENT_USE_DEV_RANDOM 1 +#endif + +#ifndef TORRENT_HAVE_MMAP +#define TORRENT_HAVE_MMAP 0 +#endif + +#ifndef TORRENT_HAVE_MAP_VIEW_OF_FILE +#define TORRENT_HAVE_MAP_VIEW_OF_FILE 0 +#endif + +#ifndef TORRENT_USE_MADVISE +#define TORRENT_USE_MADVISE 0 +#endif + +#ifndef TORRENT_USE_SYNC_FILE_RANGE +#define TORRENT_USE_SYNC_FILE_RANGE 0 +#endif + + +#ifndef TORRENT_COMPLETE_TYPES_REQUIRED +#define TORRENT_COMPLETE_TYPES_REQUIRED 0 +#endif + +#ifndef TORRENT_USE_FDATASYNC +#define TORRENT_USE_FDATASYNC 0 +#endif + +#ifndef TORRENT_USE_UNC_PATHS +#define TORRENT_USE_UNC_PATHS 0 +#endif + +#ifndef TORRENT_USE_RLIMIT +#define TORRENT_USE_RLIMIT 1 +#endif + +#ifndef TORRENT_USE_IFADDRS +#define TORRENT_USE_IFADDRS 0 +#endif + +#ifndef TORRENT_NO_FPU +#define TORRENT_NO_FPU 0 +#endif + +#ifndef TORRENT_USE_IOSTREAM +#ifndef BOOST_NO_IOSTREAM +#define TORRENT_USE_IOSTREAM 1 +#else +#define TORRENT_USE_IOSTREAM 0 +#endif +#endif + +#ifndef TORRENT_USE_I2P +#define TORRENT_USE_I2P 1 +#endif + +#ifndef TORRENT_HAS_SYMLINK +#define TORRENT_HAS_SYMLINK 0 +#endif + +#ifndef TORRENT_USE_IFCONF +#define TORRENT_USE_IFCONF 0 +#endif + +#ifndef TORRENT_USE_GETRANDOM +#define TORRENT_USE_GETRANDOM 0 +#endif + +#ifndef TORRENT_NATIVE_UTF8 +#define TORRENT_NATIVE_UTF8 0 +#endif + +#ifndef TORRENT_HAS_PTHREAD_SET_NAME +#define TORRENT_HAS_PTHREAD_SET_NAME 0 +#endif + +#ifndef TORRENT_HAS_COPY_FILE_RANGE +#define TORRENT_HAS_COPY_FILE_RANGE 0 +#endif + +#ifndef TORRENT_HAS_COPYFILE +#define TORRENT_HAS_COPYFILE 0 +#endif + +// debug builds have asserts enabled by default, release +// builds have asserts if they are explicitly enabled by +// the release_asserts macro. +#ifndef TORRENT_USE_ASSERTS +#define TORRENT_USE_ASSERTS 0 +#endif // TORRENT_USE_ASSERTS + +#ifndef TORRENT_USE_INVARIANT_CHECKS +#define TORRENT_USE_INVARIANT_CHECKS 0 +#endif + +#if TORRENT_USE_INVARIANT_CHECKS && !TORRENT_USE_ASSERTS +#error "invariant checks cannot be enabled without asserts" +#endif + +// SSE is x86 / amd64 specific. On top of that, we only +// know how to access it on msvc and gcc (and gcc compatibles). +// GCC requires the user to enable SSE support in order for +// the program to have access to the intrinsics, this is +// indicated by the __SSE4_1__ macro +#ifndef TORRENT_HAS_SSE + +#if (defined _M_AMD64 || defined _M_IX86 || defined _M_X64 \ + || defined __amd64__ || defined __i386 || defined __i386__ \ + || defined __x86_64__ || defined __x86_64) \ + && (defined __GNUC__ || (defined _MSC_VER && _MSC_VER >= 1600)) +#define TORRENT_HAS_SSE 1 +#else +#define TORRENT_HAS_SSE 0 +#endif + +#endif // TORRENT_HAS_SSE + +#if (defined __arm__ || defined __aarch64__ || defined _M_ARM || defined _M_ARM64) +#define TORRENT_HAS_ARM 1 +#else +#define TORRENT_HAS_ARM 0 +#endif // TORRENT_HAS_ARM + +#ifndef __has_builtin +#define __has_builtin(x) 0 // for non-clang compilers +#endif + +#ifdef __cpp_guaranteed_copy_elision +#define TORRENT_RVO(x) x +#else +#define TORRENT_RVO(x) std::move(x) +#endif + +#if (TORRENT_HAS_SSE && defined __GNUC__) +# define TORRENT_HAS_BUILTIN_CLZ 1 +#elif (TORRENT_HAS_ARM && defined __GNUC__ && !defined __clang__) +# define TORRENT_HAS_BUILTIN_CLZ 1 +#elif (defined __clang__ && __has_builtin(__builtin_clz)) +# define TORRENT_HAS_BUILTIN_CLZ 1 +#else +# define TORRENT_HAS_BUILTIN_CLZ 0 +#endif // TORRENT_HAS_BUILTIN_CLZ + +#if (TORRENT_HAS_SSE && defined __GNUC__) +# define TORRENT_HAS_BUILTIN_CTZ 1 +#elif (TORRENT_HAS_ARM && defined __GNUC__ && !defined __clang__) +# define TORRENT_HAS_BUILTIN_CTZ 1 +#elif (defined __clang__ && __has_builtin(__builtin_ctz)) +# define TORRENT_HAS_BUILTIN_CTZ 1 +#else +# define TORRENT_HAS_BUILTIN_CTZ 0 +#endif // TORRENT_HAS_BUILTIN_CTZ + +#if TORRENT_HAS_ARM && defined __ARM_NEON +# define TORRENT_HAS_ARM_NEON 1 +#else +# define TORRENT_HAS_ARM_NEON 0 +#endif // TORRENT_HAS_ARM_NEON + +#if TORRENT_HAS_ARM && defined __ARM_FEATURE_CRC32 +# define TORRENT_HAS_ARM_CRC32 1 +#else +#if defined TORRENT_FORCE_ARM_CRC32 +# define TORRENT_HAS_ARM_CRC32 1 +#else +# define TORRENT_HAS_ARM_CRC32 0 +#endif +#endif // TORRENT_HAS_ARM_CRC32 + +#if defined TORRENT_USE_OPENSSL || defined TORRENT_USE_GNUTLS +#define TORRENT_USE_SSL 1 +#else +#define TORRENT_USE_SSL 0 +#endif + +#if defined TORRENT_SSL_PEERS && !TORRENT_USE_SSL +#error compiling with TORRENT_SSL_PEERS requires TORRENT_USE_OPENSSL or TORRENT_USE_GNUTLS +#endif + +#include "libtorrent/aux_/export.hpp" + +namespace libtorrent {} + +// create alias +namespace lt = libtorrent; + +#endif // TORRENT_CONFIG_HPP_INCLUDED diff --git a/include/libtorrent/copy_ptr.hpp b/include/libtorrent/copy_ptr.hpp new file mode 100644 index 0000000..c2cec73 --- /dev/null +++ b/include/libtorrent/copy_ptr.hpp @@ -0,0 +1,68 @@ +/* + +Copyright (c) 2010, 2016-2019, Arvid Norberg +Copyright (c) 2017, Matthew Fioravante +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_COPY_PTR +#define TORRENT_COPY_PTR + +#include + +namespace libtorrent { + + template + struct copy_ptr + { + copy_ptr() = default; + explicit copy_ptr(T* t): m_ptr(t) {} + copy_ptr(copy_ptr const& p): m_ptr(p.m_ptr ? new T(*p.m_ptr) : nullptr) {} + copy_ptr(copy_ptr&& p) noexcept = default; + + void reset(T* t = nullptr) { m_ptr.reset(t); } + copy_ptr& operator=(copy_ptr const& p) & + { + if (m_ptr == p.m_ptr) return *this; + m_ptr.reset(p.m_ptr ? new T(*p.m_ptr) : nullptr); + return *this; + } + copy_ptr& operator=(copy_ptr&& p) & noexcept = default; + T* operator->() { return m_ptr.get(); } + T const* operator->() const { return m_ptr.get(); } + T& operator*() { return *m_ptr; } + T const& operator*() const { return *m_ptr; } + void swap(copy_ptr& p) { std::swap(*this, p); } + explicit operator bool() const { return m_ptr.get() != nullptr; } + private: + std::unique_ptr m_ptr; + }; +} + +#endif // TORRENT_COPY_PTR diff --git a/include/libtorrent/crc32c.hpp b/include/libtorrent/crc32c.hpp new file mode 100644 index 0000000..300fb8e --- /dev/null +++ b/include/libtorrent/crc32c.hpp @@ -0,0 +1,46 @@ +/* + +Copyright (c) 2010, 2014, 2016-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CRC32C_HPP_INCLUDE +#define TORRENT_CRC32C_HPP_INCLUDE + +#include +#include "libtorrent/aux_/export.hpp" + +namespace libtorrent { + + // this is the crc32c (Castagnoli) polynomial + TORRENT_EXTRA_EXPORT std::uint32_t crc32c_32(std::uint32_t); + TORRENT_EXTRA_EXPORT std::uint32_t crc32c(std::uint64_t const*, int); +} + +#endif diff --git a/include/libtorrent/create_torrent.hpp b/include/libtorrent/create_torrent.hpp new file mode 100644 index 0000000..d9232c5 --- /dev/null +++ b/include/libtorrent/create_torrent.hpp @@ -0,0 +1,577 @@ +/* + +Copyright (c) 2008-2022, Arvid Norberg +Copyright (c) 2016-2017, 2019, Alden Torres +Copyright (c) 2016, Markus +Copyright (c) 2017, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CREATE_TORRENT_HPP_INCLUDED +#define TORRENT_CREATE_TORRENT_HPP_INCLUDED + +#include "libtorrent/bencode.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/session_params.hpp" // for disk_io_constructor_type +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/path.hpp" // for combine_path etc. +#include "libtorrent/fwd.hpp" +#include "libtorrent/aux_/throw.hpp" +#include "libtorrent/index_range.hpp" + +#include +#include +#include + +// OVERVIEW +// +// This section describes the functions and classes that are used +// to create torrent files. It is a layered API with low level classes +// and higher level convenience functions. A torrent is created in 4 +// steps: +// +// 1. first the files that will be part of the torrent are determined. +// 2. the torrent properties are set, such as tracker url, web seeds, +// DHT nodes etc. +// 3. Read through all the files in the torrent, SHA-1 all the data +// and set the piece hashes. +// 4. The torrent is bencoded into a file or buffer. +// +// If there are a lot of files and or deep directory hierarchies to +// traverse, step one can be time consuming. +// +// Typically step 3 is by far the most time consuming step, since it +// requires to read all the bytes from all the files in the torrent. +// +// All of these classes and functions are declared by including +// ``libtorrent/create_torrent.hpp``. +// +// example: +// +// .. code:: c++ +// +// file_storage fs; +// +// // recursively adds files in directories +// add_files(fs, "./my_torrent"); +// +// create_torrent t(fs); +// t.add_tracker("http://my.tracker.com/announce"); +// t.set_creator("libtorrent example"); +// +// // reads the files and calculates the hashes +// set_piece_hashes(t, "."); +// +// ofstream out("my_torrent.torrent", std::ios_base::binary); +// std::vector buf = t.generate_buf(); +// out.write(buf.data(), buf.size()); +// +// // alternatively, generate an entry and encode it directly to an ostream +// // iterator +// bencode(std::ostream_iterator(out), t.generate()); +// +namespace libtorrent { + + // hidden + using create_flags_t = flags::bitfield_flag; + + // This class holds state for creating a torrent. After having added + // all information to it, call create_torrent::generate() to generate + // the torrent. The entry that's returned can then be bencoded into a + // .torrent file using bencode(). + struct TORRENT_EXPORT create_torrent + { +#if TORRENT_ABI_VERSION == 1 + using flags_t = create_flags_t; +#endif + +#if TORRENT_ABI_VERSION <= 2 + // This will insert pad files to align the files to piece boundaries, for + // optimized disk-I/O. This will minimize the number of bytes of pad- + // files, to keep the impact down for clients that don't support + // them. + // incompatible with v2 metadata, ignored + TORRENT_DEPRECATED static constexpr create_flags_t optimize_alignment = 0_bit; +#endif +#if TORRENT_ABI_VERSION == 1 + // same as optimize_alignment, for backwards compatibility + TORRENT_DEPRECATED static constexpr create_flags_t optimize = 0_bit; +#endif + +#if TORRENT_ABI_VERSION <= 2 + // This will create a merkle hash tree torrent. A merkle torrent cannot + // be opened in clients that don't specifically support merkle torrents. + // The benefit is that the resulting torrent file will be much smaller and + // not grow with more pieces. When this option is specified, it is + // recommended to have a fairly small piece size, say 64 kiB. + // When creating merkle torrents, the full hash tree is also generated + // and should be saved off separately. It is accessed through the + // create_torrent::merkle_tree() function. + // support for BEP 30 merkle torrents has been removed + TORRENT_DEPRECATED static constexpr create_flags_t merkle = 1_bit; +#endif + + // This will include the file modification time as part of the torrent. + // This is not enabled by default, as it might cause problems when you + // create a torrent from separate files with the same content, hoping to + // yield the same info-hash. If the files have different modification times, + // with this option enabled, you would get different info-hashes for the + // files. + static constexpr create_flags_t modification_time = 2_bit; + + // If this flag is set, files that are symlinks get a symlink attribute + // set on them and their data will not be included in the torrent. This + // is useful if you need to reconstruct a file hierarchy which contains + // symlinks. + static constexpr create_flags_t symlinks = 3_bit; + + // to create a torrent that can be updated via a *mutable torrent* + // (see `BEP 38`_). This also needs to be enabled for torrents that update + // another torrent. +#if TORRENT_ABI_VERSION <= 2 + // BEP 52 requires files to be piece aligned so all torrents are now compatible + // with BEP 38 + TORRENT_DEPRECATED static constexpr create_flags_t mutable_torrent_support = 4_bit; +#endif + + // Do not generate v1 metadata. The resulting torrent will only be usable by + // clients which support v2. This requires setting all v2 hashes, with + // set_hash2() before calling generate(). Setting v1 hashes (with + // set_hash()) is an error with this flag set. + static constexpr create_flags_t v2_only = 5_bit; + + // do not generate v2 metadata or enforce v2 alignment and padding rules + // this is mainly for tests, not recommended for production use. This + // requires setting all v1 hashes, with set_hash(), before calling + // generate(). Setting v2 hashes (with set_hash2()) is an error with + // this flag set. + static constexpr create_flags_t v1_only = 6_bit; + + // This flag only affects v1-only torrents, and is only relevant + // together with the v1_only_flag. This flag will force the + // same file order and padding as a v2 (or hybrid) torrent would have. + // It has the effect of ordering files and inserting pad files to align + // them with piece boundaries. + static constexpr create_flags_t canonical_files = 7_bit; + + // passing this flag to add_files() will ignore file attributes (such as + // executable or hidden) when adding the files to the file storage. + // Since not all filesystems and operating systems support all file + // attributes the resulting torrent may differ depending on where it's + // created. If it's important for torrents to be created consistently + // across systems, this flag should be set. + static constexpr create_flags_t no_attributes = 8_bit; + + // this flag enforces the file layout to be canonical according to the + // bittorrent v2 specification (just like the ``canonical_files`` flag) + // with the one exception that tail padding is not added to the last + // file. + // This behavior deviates from the specification but was the way + // libtorrent created torrents in version up to and including 2.0.7. + // This flag is here for backwards compatibility. + static constexpr create_flags_t canonical_files_no_tail_padding = 9_bit; + + // hidden + static constexpr create_flags_t allow_odd_piece_size = 31_bit; + + // The ``piece_size`` is the size of each piece in bytes. It must be a + // power of 2 and a minimum of 16 kiB. If a piece size of 0 is + // specified, a piece_size will be set automatically. + // Piece sizes greater than 128 MiB are considered unreasonable and will + // be rejected (with an lt::system_error exception). + // + // The ``flags`` arguments specifies options for the torrent creation. It can + // be any combination of the flags defined by create_flags_t. + // + // The file_storage (``fs``) parameter defines the files, sizes and + // their properties for the torrent to be created. Set this up first, + // before passing it to the create_torrent constructor. + // + // The overload that takes a ``torrent_info`` object will make a verbatim + // copy of its info dictionary (to preserve the info-hash). The copy of + // the info dictionary will be used by create_torrent::generate(). This means + // that none of the member functions of create_torrent that affects + // the content of the info dictionary (such as set_hash()), will + // have any affect. Instead of using this overload, consider using + // write_torrent_file() instead. + // + // .. warning:: + // The file_storage and torrent_info objects must stay alive for the + // entire duration of the create_torrent object. + // + explicit create_torrent(file_storage& fs, int piece_size = 0 + , create_flags_t flags = {}); + explicit create_torrent(torrent_info const& ti); + +#if TORRENT_ABI_VERSION <= 2 + TORRENT_DEPRECATED + explicit create_torrent(file_storage& fs, int piece_size + , int, create_flags_t flags = {}, int = -1) + : create_torrent(fs, piece_size, flags) {} +#endif + + // internal + ~create_torrent(); + + // This function will generate the .torrent file as a bencode tree, or a + // bencoded into a buffer. + // In order to encode the entry into a flat file, use the bencode() function. + // + // The function returning an entry may be useful to add custom entries + // to the torrent file before bencoding it and saving it to disk. + // + // Whether the resulting torrent object is v1, v2 or hybrid depends on + // whether any of the v1_only or v2_only flags were set on the + // constructor. If neither were set, the resulting torrent depends on + // which hashes were set. If both v1 and v2 hashes were set, a hybrid + // torrent is created. + // + // Any failure will cause this function to throw system_error, with an + // appropriate error message. These are the reasons this call may throw: + // + // * the file storage has 0 files + // * the total size of the file storage is 0 bytes (i.e. it only has + // empty files) + // * not all v1 hashes (set_hash()) and not all v2 hashes (set_hash2()) + // were set + // * for v2 torrents, you may not have a directory with the same name as + // a file. If that's encountered in the file storage, generate() + // fails. + entry generate() const; + std::vector generate_buf() const; + + // returns an immutable reference to the file_storage used to create + // the torrent from. + file_storage const& files() const { return m_files; } + + // Sets the comment for the torrent. The string ``str`` should be utf-8 encoded. + // The comment in a torrent file is optional. + void set_comment(char const* str); + + // Sets the creator of the torrent. The string ``str`` should be utf-8 encoded. + // This is optional. + void set_creator(char const* str); + + // sets the "creation time" field. Defaults to the system clock at the + // time of construction of the create_torrent object. The timestamp is + // specified in seconds, posix time. If the creation date is set to 0, + // the "creation date" field will be omitted from the generated torrent. + void set_creation_date(std::time_t timestamp); + + // This sets the SHA-1 hash for the specified piece (``index``). You are required + // to set the hash for every piece in the torrent before generating it. If you have + // the files on disk, you can use the high level convenience function to do this. + // See set_piece_hashes(). + // A SHA-1 hash of all zeros is internally used to indicate a hash that + // has not been set. Setting such hash will not be considered set when + // calling generate(). + // This function will throw ``std::system_error`` if it is called on an + // object constructed with the v2_only flag. + void set_hash(piece_index_t index, sha1_hash const& h); + + // sets the bittorrent v2 hash for file `file` of the piece `piece`. + // `piece` is relative to the first piece of the file, starting at 0. The + // first piece in the file can be computed with + // file_storage::file_index_at_piece(). + // The hash, `h`, is the root of the merkle tree formed by the piece's + // 16 kiB blocks. Note that piece sizes must be powers-of-2, so all + // per-piece merkle trees are complete. + // A SHA-256 hash of all zeros is internally used to indicate a hash + // that has not been set. Setting such hash will not be considered set + // when calling generate(). + // This function will throw ``std::system_error`` if it is called on an + // object constructed with the v1_only flag. + void set_hash2(file_index_t file, piece_index_t::diff_type piece, sha256_hash const& h); + +#if TORRENT_ABI_VERSION < 3 + // This sets the sha1 hash for this file. This hash will end up under the key ``sha1`` + // associated with this file (for multi-file torrents) or in the root info dictionary + // for single-file torrents. + // .. note:: + // + // with bittorrent v2, this feature is obsolete + TORRENT_DEPRECATED + void set_file_hash(file_index_t index, sha1_hash const& h); +#endif + + // This adds a url seed to the torrent. You can have any number of url seeds. For a + // single file torrent, this should be an HTTP url, pointing to a file with identical + // content as the file of the torrent. For a multi-file torrent, it should point to + // a directory containing a directory with the same name as this torrent, and all the + // files of the torrent in it. + // + // The second function, ``add_http_seed()`` adds an HTTP seed instead. + void add_url_seed(string_view url); + void add_http_seed(string_view url); + + // This adds a DHT node to the torrent. This especially useful if you're creating a + // tracker less torrent. It can be used by clients to bootstrap their DHT node from. + // The node is a hostname and a port number where there is a DHT node running. + // You can have any number of DHT nodes in a torrent. + void add_node(std::pair node); + + // Adds a tracker to the torrent. This is not strictly required, but most torrents + // use a tracker as their main source of peers. The url should be an http:// or udp:// + // url to a machine running a bittorrent tracker that accepts announces for this torrent's + // info-hash. The tier is the fallback priority of the tracker. All trackers with tier 0 are + // tried first (in any order). If all fail, trackers with tier 1 are tried. If all of those + // fail, trackers with tier 2 are tried, and so on. + void add_tracker(string_view url, int tier = 0); + + // This function sets an X.509 certificate in PEM format to the torrent. This makes the + // torrent an *SSL torrent*. An SSL torrent requires that each peer has a valid certificate + // signed by this root certificate. For SSL torrents, all peers are connecting over SSL + // connections. For more information, see the section on ssl-torrents_. + // + // The string is not the path to the cert, it's the actual content of the + // certificate. + void set_root_cert(string_view cert); + + // Sets and queries the private flag of the torrent. + // Torrents with the private flag set ask the client to not use any other + // sources than the tracker for peers, and to not use DHT to advertise itself publicly, + // only the tracker. + void set_priv(bool p) { m_private = p; } + bool priv() const { return m_private; } + + bool is_v2_only() const { return m_v2_only; } + bool is_v1_only() const { return m_v1_only; } + + // returns the number of pieces in the associated file_storage object. + int num_pieces() const { return m_files.num_pieces(); } + + piece_index_t end_piece() const { return m_files.end_piece(); } + + // all piece indices in the torrent to be created + index_range piece_range() const noexcept + { return {piece_index_t{0}, end_piece()}; } + + file_index_t end_file() const { return m_files.end_file(); } + + // all file indices in the torrent to be created + index_range file_range() const noexcept + { return m_files.file_range(); } + + // for v2 and hybrid torrents only, the pieces in the + // specified file, specified as delta from the first piece in the file. + // i.e. the first index is 0. + index_range file_piece_range(file_index_t f) + { return m_files.file_piece_range(f); } + + // the total number of bytes of all files and pad files + std::int64_t total_size() const { return m_files.total_size(); } + + // ``piece_length()`` returns the piece size of all pieces but the + // last one. ``piece_size()`` returns the size of the specified piece. + // these functions are just forwarding to the associated file_storage. + int piece_length() const { return m_files.piece_length(); } + int piece_size(piece_index_t i) const { return m_files.piece_size(i); } + +#if TORRENT_ABI_VERSION <= 2 + // support for BEP 30 merkle torrents has been removed + + // This function returns the merkle hash tree, if the torrent was created as a merkle + // torrent. The tree is created by ``generate()`` and won't be valid until that function + // has been called. When creating a merkle tree torrent, the actual tree itself has to + // be saved off separately and fed into libtorrent the first time you start seeding it, + // through the ``torrent_info::set_merkle_tree()`` function. From that point onwards, the + // tree will be saved in the resume data. + TORRENT_DEPRECATED + std::vector merkle_tree() const { return std::vector(); } +#endif + + // Add similar torrents (by info-hash) or collections of similar torrents. + // Similar torrents are expected to share some files with this torrent. + // Torrents sharing a collection name with this torrent are also expected + // to share files with this torrent. A torrent may have more than one + // collection and more than one similar torrents. For more information, + // see `BEP 38`_. + void add_similar_torrent(sha1_hash ih); + void add_collection(string_view c); + + private: + + file_storage const& m_files; + // if m_info_dict is initialized, it is + // used instead of m_files to generate + // the info dictionary + entry m_info_dict; + + // the URLs to the trackers + std::vector> m_urls; + + std::vector m_url_seeds; + std::vector m_http_seeds; + + aux::vector m_piece_hash; + + // leave this here for now, to preserve ABI between building with + // deprecated functions and without + aux::vector m_filehashes; + + mutable aux::vector m_fileroots; + aux::vector, file_index_t> m_file_piece_hash; + + std::vector m_similar; + std::vector m_collections; + + // dht nodes to add to the routing table/bootstrap from + std::vector> m_nodes; + + // if a creation date is found in the torrent file + // this will be set to that, otherwise it'll be + // 1970, Jan 1 + time_t m_creation_date; + + // if a comment is found in the torrent file + // this will be set to that comment + std::string m_comment; + + // an optional string naming the software used + // to create the torrent file + std::string m_created_by; + + // this is the root cert for SSL torrents + std::string m_root_cert; + + // this is used when creating a torrent. If there's + // only one file there are cases where it's impossible + // to know if it should be written as a multi-file torrent + // or not. e.g. test/test there's one file and one directory + // and they have the same name. + bool m_multifile:1; + + // this is true if the torrent is private. i.e., the client should not + // advertise itself on the DHT for this torrent + bool m_private:1; + + // if set, include the 'mtime' modification time in the + // torrent file + bool m_include_mtime:1; + + // if set, symbolic links are declared as such in + // the torrent file. The full data of the pointed-to + // file is still included + bool m_include_symlinks:1; + + bool m_v2_only:1; + + // only generate v1 metadata and do not enforce v2 padding rules + bool m_v1_only:1; + }; + +namespace aux { + inline void nop(piece_index_t) {} +} + + // Adds the file specified by ``path`` to the file_storage object. In case ``path`` + // refers to a directory, files will be added recursively from the directory. + // + // If specified, the predicate ``p`` is called once for every file and directory that + // is encountered. Files for which ``p`` returns true are added, and directories for + // which ``p`` returns true are traversed. ``p`` must have the following signature: + // + // .. code:: c++ + // + // bool Pred(std::string const& p); + // + // The path that is passed in to the predicate is the full path of the file or + // directory. If no predicate is specified, all files are added, and all directories + // are traversed. + // + // The ".." directory is never traversed. + // + // The ``flags`` argument should be the same as the flags passed to the `create_torrent`_ + // constructor. + TORRENT_EXPORT void add_files(file_storage& fs, std::string const& file + , std::function p, create_flags_t flags = {}); + TORRENT_EXPORT void add_files(file_storage& fs, std::string const& file + , create_flags_t flags = {}); + + // This function will assume that the files added to the torrent file exists at path + // ``p``, read those files and hash the content and set the hashes in the ``create_torrent`` + // object. The optional function ``f`` is called in between every hash that is set. ``f`` + // must have the following signature: + // + // .. code:: c++ + // + // void Fun(piece_index_t); + // + // The overloads taking a settings_pack may be used to configure the + // underlying disk access. Such as ``settings_pack::aio_threads``. + // + // The overloads that don't take an ``error_code&`` may throw an exception in case of a + // file error, the other overloads sets the error code to reflect the error, if any. + TORRENT_EXPORT void set_piece_hashes(create_torrent& t, std::string const& p + , std::function const& f, error_code& ec); + TORRENT_EXPORT void set_piece_hashes(create_torrent& t, std::string const& p + , settings_interface const& settings + , std::function const& f, error_code& ec); + TORRENT_EXPORT void set_piece_hashes(create_torrent& t, std::string const& p + , settings_interface const& settings, disk_io_constructor_type disk_io + , std::function const& f, error_code& ec); + inline void set_piece_hashes(create_torrent& t, std::string const& p, error_code& ec) + { + set_piece_hashes(t, p, aux::nop, ec); + } +#ifndef BOOST_NO_EXCEPTIONS + inline void set_piece_hashes(create_torrent& t, std::string const& p) + { + error_code ec; + set_piece_hashes(t, p, aux::nop, ec); + if (ec) aux::throw_ex(ec); + } + inline void set_piece_hashes(create_torrent& t, std::string const& p + , std::function const& f) + { + error_code ec; + set_piece_hashes(t, p, f, ec); + if (ec) aux::throw_ex(ec); + } + inline void set_piece_hashes(create_torrent& t, std::string const& p + , settings_interface const& settings + , std::function const& f) + { + error_code ec; + set_piece_hashes(t, p, settings, f, ec); + if (ec) aux::throw_ex(ec); + } +#endif + +namespace aux { + TORRENT_EXTRA_EXPORT file_flags_t get_file_attributes(std::string const& p); + TORRENT_EXTRA_EXPORT std::string get_symlink_path(std::string const& p); +} + +} + +#endif diff --git a/include/libtorrent/deadline_timer.hpp b/include/libtorrent/deadline_timer.hpp new file mode 100644 index 0000000..723ed9c --- /dev/null +++ b/include/libtorrent/deadline_timer.hpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2009, 2015, 2017-2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DEADLINE_TIMER_HPP_INCLUDED +#define TORRENT_DEADLINE_TIMER_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#else +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // SIMULATOR + +namespace libtorrent { + +#if defined TORRENT_BUILD_SIMULATOR + using deadline_timer = sim::asio::high_resolution_timer; +#else + using deadline_timer = boost::asio::high_resolution_timer; +#endif +} + +#endif // TORRENT_DEADLINE_TIMER_HPP_INCLUDED diff --git a/include/libtorrent/debug.hpp b/include/libtorrent/debug.hpp new file mode 100644 index 0000000..0211d98 --- /dev/null +++ b/include/libtorrent/debug.hpp @@ -0,0 +1,288 @@ +/* + +Copyright (c) 2003, 2010, 2012-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DEBUG_HPP_INCLUDED +#define TORRENT_DEBUG_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" + +#if TORRENT_USE_ASSERTS +#include +#endif + +#if defined TORRENT_ASIO_DEBUGGING + +#include "libtorrent/time.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include +#include +#include + +#ifdef __MACH__ +#include +#include +#include + +const mach_msg_type_number_t task_events_info_count = TASK_EVENTS_INFO_COUNT; +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +std::string demangle(char const* name); + +namespace libtorrent { + + struct async_t + { + async_t() : refs(0) {} + std::string stack; + int refs; + }; + + // defined in session_impl.cpp + TORRENT_EXTRA_EXPORT extern std::map _async_ops; + TORRENT_EXTRA_EXPORT extern int _async_ops_nthreads; + TORRENT_EXTRA_EXPORT extern std::mutex _async_ops_mutex; + + // timestamp -> operation + struct wakeup_t + { + time_point timestamp; + std::uint64_t context_switches; + char const* operation; + }; + TORRENT_EXTRA_EXPORT extern std::deque _wakeups; + + inline bool has_outstanding_async(char const* name) + { + std::lock_guard l(_async_ops_mutex); + std::map::iterator i = _async_ops.find(name); + return i != _async_ops.end(); + } + + inline void add_outstanding_async(char const* name) + { + std::lock_guard l(_async_ops_mutex); + async_t& a = _async_ops[name]; + if (a.stack.empty()) + { + char stack_text[10000]; + print_backtrace(stack_text, sizeof(stack_text), 9); + + // skip the stack frame of 'add_outstanding_async' + char* ptr = strchr(stack_text, '\n'); + if (ptr != nullptr) ++ptr; + else ptr = stack_text; + a.stack = ptr; + } + ++a.refs; + } + + inline void complete_async(char const* name) + { + std::lock_guard l(_async_ops_mutex); + async_t& a = _async_ops[name]; + TORRENT_ASSERT(a.refs > 0); + --a.refs; + + // don't let this grow indefinitely + if (_wakeups.size() < 100000) + { + _wakeups.push_back(wakeup_t()); + wakeup_t& w = _wakeups.back(); + w.timestamp = clock_type::now(); +#ifdef __MACH__ + task_events_info teinfo; + mach_msg_type_number_t t_info_count = task_events_info_count; + task_info(mach_task_self(), TASK_EVENTS_INFO, + reinterpret_cast(&teinfo), &t_info_count); + w.context_switches = static_cast(teinfo.csw); +#else + w.context_switches = 0; +#endif + w.operation = name; + } + } + + inline void async_inc_threads() + { + std::lock_guard l(_async_ops_mutex); + ++_async_ops_nthreads; + } + + inline void async_dec_threads() + { + std::lock_guard l(_async_ops_mutex); + --_async_ops_nthreads; + } + + inline int log_async() + { + std::lock_guard l(_async_ops_mutex); + int ret = 0; + for (auto const& op : _async_ops) + { + if (op.second.refs <= _async_ops_nthreads - 1) continue; + ret += op.second.refs; + std::printf("%s: (%d)\n%s\n", op.first.c_str(), op.second.refs, op.second.stack.c_str()); + } + return ret; + } + + struct handler_alloc_t + { + std::size_t capacity; + std::set> allocations; + }; + // defined in session_impl.cpp + extern std::map _handler_storage; + extern std::mutex _handler_storage_mutex; + extern bool _handler_logger_registered; + + inline void log_handler_allocators() noexcept + { + static char const* const handler_names[] = { + "write_handler", + "read_handler", + "udp_handler", + "tick_handler", + "abort_handler", + "defer_handler", + "utp_handler", + "submit_handler", + }; + std::lock_guard l(_handler_storage_mutex); + std::printf("handler allocator storage:\n\n"); + for (auto const& e : _handler_storage) + { + std::size_t allocated = 0; + std::string handler_name; + // pick the largest allocation, in case the storage was used for + // different handlers + for (auto const& a : e.second.allocations) + { + if (allocated >= a.second) continue; + allocated = a.second; + handler_name = demangle(e.second.allocations.begin()->first->name()); + } + + std::printf("%15s: capacity: %-3d allocated: %-3d handler: %s\n" + , handler_names[e.first], int(e.second.capacity), int(allocated), handler_name.c_str()); + } + } + + template + void record_handler_allocation(int const type, std::size_t const capacity) + { + std::lock_guard l(_handler_storage_mutex); + auto& e = _handler_storage[type]; + e.capacity = capacity; + e.allocations.emplace(&typeid(Handler), sizeof(Handler)); + if (!_handler_logger_registered) + { + std::atexit(&log_handler_allocators); + _handler_logger_registered = true; + } + } +} + +#define ADD_OUTSTANDING_ASYNC(x) add_outstanding_async(x) +#define COMPLETE_ASYNC(x) complete_async(x) + +#else + +#define ADD_OUTSTANDING_ASYNC(x) do {} TORRENT_WHILE_0 +#define COMPLETE_ASYNC(x) do {} TORRENT_WHILE_0 + +#endif // TORRENT_ASIO_DEBUGGING + +namespace libtorrent { + +#if TORRENT_USE_ASSERTS + struct TORRENT_EXTRA_EXPORT single_threaded + { + single_threaded(): m_id() {} + ~single_threaded() { m_id = std::thread::id(); } + bool is_single_thread() const + { + if (m_id == std::thread::id()) + { + m_id = std::this_thread::get_id(); + return true; + } + return m_id == std::this_thread::get_id(); + } + bool is_not_thread() const + { + if (m_id == std::thread::id()) return true; + return m_id != std::this_thread::get_id(); + } + + void thread_started() + { m_id = std::this_thread::get_id(); } + + private: + mutable std::thread::id m_id; + }; +#else + struct single_threaded { + bool is_single_thread() const { return true; } + void thread_started() {} + bool is_not_thread() const {return true; } + }; +#endif + +#if TORRENT_USE_ASSERTS + struct increment_guard + { + int& m_cnt; + explicit increment_guard(int& c) : m_cnt(c) { TORRENT_ASSERT(m_cnt >= 0); ++m_cnt; } + ~increment_guard() { --m_cnt; TORRENT_ASSERT(m_cnt >= 0); } + private: + increment_guard(increment_guard const&); + increment_guard operator=(increment_guard const&); + }; +#define TORRENT_INCREMENT(x) increment_guard inc_(x) +#else +#define TORRENT_INCREMENT(x) do {} TORRENT_WHILE_0 +#endif +} + +#endif // TORRENT_DEBUG_HPP_INCLUDED diff --git a/include/libtorrent/disabled_disk_io.hpp b/include/libtorrent/disabled_disk_io.hpp new file mode 100644 index 0000000..4487b92 --- /dev/null +++ b/include/libtorrent/disabled_disk_io.hpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2016, 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISABLED_DISK_IO +#define TORRENT_DISABLED_DISK_IO + +#include "libtorrent/config.hpp" +#include "libtorrent/io_context.hpp" + +#include + +namespace libtorrent { + + struct counters; + struct disk_interface; + struct settings_interface; + + // creates a disk io object that discards all data written to it, and only + // returns zero-buffers when read from. May be useful for testing and + // benchmarking. + TORRENT_EXPORT std::unique_ptr disabled_disk_io_constructor( + io_context& ios, settings_interface const&, counters& cnt); +} + +#endif + diff --git a/include/libtorrent/disk_buffer_holder.hpp b/include/libtorrent/disk_buffer_holder.hpp new file mode 100644 index 0000000..b88de7a --- /dev/null +++ b/include/libtorrent/disk_buffer_holder.hpp @@ -0,0 +1,112 @@ +/* + +Copyright (c) 2008-2009, 2013-2019, Arvid Norberg +Copyright (c) 2018, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_BUFFER_HOLDER_HPP_INCLUDED +#define TORRENT_DISK_BUFFER_HOLDER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include + +namespace libtorrent { + + // the interface for freeing disk buffers, used by the disk_buffer_holder. + // when implementing disk_interface, this must also be implemented in order + // to return disk buffers back to libtorrent + struct TORRENT_EXPORT buffer_allocator_interface + { + virtual void free_disk_buffer(char* b) = 0; + protected: + ~buffer_allocator_interface() = default; + }; + + // The disk buffer holder acts like a ``unique_ptr`` that frees a disk buffer + // when it's destructed + // + // If this buffer holder is moved-from, default constructed or reset, + // ``data()`` will return nullptr. + struct TORRENT_EXPORT disk_buffer_holder + { + disk_buffer_holder& operator=(disk_buffer_holder&&) & noexcept; + disk_buffer_holder(disk_buffer_holder&&) noexcept; + + disk_buffer_holder& operator=(disk_buffer_holder const&) = delete; + disk_buffer_holder(disk_buffer_holder const&) = delete; + + // construct a buffer holder that will free the held buffer + // using a disk buffer pool directly (there's only one + // disk_buffer_pool per session) + disk_buffer_holder(buffer_allocator_interface& alloc + , char* buf, int sz) noexcept; + + // default construct a holder that does not own any buffer + disk_buffer_holder() noexcept = default; + + // frees disk buffer held by this object + ~disk_buffer_holder(); + + // return a pointer to the held buffer, if any. Otherwise returns nullptr. + char* data() const noexcept { return m_buf; } + + // free the held disk buffer, if any, and clear the holder. This sets the + // holder object to a default-constructed state + void reset(); + + // swap pointers of two disk buffer holders. + void swap(disk_buffer_holder& h) noexcept + { + using std::swap; + swap(h.m_allocator, m_allocator); + swap(h.m_buf, m_buf); + swap(h.m_size, m_size); + } + + // if this returns true, the buffer may not be modified in place + bool is_mutable() const noexcept { return false; } + + // implicitly convertible to true if the object is currently holding a + // buffer + explicit operator bool() const noexcept { return m_buf != nullptr; } + + std::ptrdiff_t size() const { return m_size; } + + private: + + buffer_allocator_interface* m_allocator = nullptr; + char* m_buf = nullptr; + int m_size = 0; + }; + +} + +#endif diff --git a/include/libtorrent/disk_interface.hpp b/include/libtorrent/disk_interface.hpp new file mode 100644 index 0000000..d8fdb76 --- /dev/null +++ b/include/libtorrent/disk_interface.hpp @@ -0,0 +1,428 @@ +/* + +Copyright (c) 2014-2022, Arvid Norberg +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2018, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_INTERFACE_HPP +#define TORRENT_DISK_INTERFACE_HPP + +#include "libtorrent/bdecode.hpp" + +#include +#include + +#include "libtorrent/fwd.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/session_types.hpp" + +// OVERVIEW +// +// The disk I/O can be customized in libtorrent. In previous versions, the +// customization was at the level of each torrent. Now, the customization point +// is at the session level. All torrents added to a session will use the same +// disk I/O subsystem, as determined by the disk_io_constructor (in +// session_params). +// +// This allows the disk subsystem to also customize threading and disk job +// management. +// +// To customize the disk subsystem, implement disk_interface and provide a +// factory function to the session constructor (via session_params). +// +// Example use: +// +// .. include:: ../examples/custom_storage.cpp +// :code: c++ +// :tab-width: 2 +// :start-after: -- example begin +// :end-before: // -- example end +namespace libtorrent { + + struct disk_observer; + struct counters; + + struct storage_holder; + + using file_open_mode_t = flags::bitfield_flag; + + // internal + // this is a bittorrent constant + constexpr int default_block_size = 0x4000; + +namespace file_open_mode { + // open the file for reading only + constexpr file_open_mode_t read_only{}; + + // open the file for writing only + constexpr file_open_mode_t write_only = 0_bit; + + // open the file for reading and writing + constexpr file_open_mode_t read_write = 1_bit; + + // the mask for the bits determining read or write mode + constexpr file_open_mode_t rw_mask = read_only | write_only | read_write; + + // open the file in sparse mode (if supported by the + // filesystem). + constexpr file_open_mode_t sparse = 2_bit; + + // don't update the access timestamps on the file (if + // supported by the operating system and filesystem). + // this generally improves disk performance. + constexpr file_open_mode_t no_atime = 3_bit; + + // When this is not set, the kernel is hinted that access to this file will + // be made sequentially. + constexpr file_open_mode_t random_access = 5_bit; + +#if TORRENT_ABI_VERSION == 1 + // prevent the file from being opened by another process + // while it's still being held open by this handle + constexpr file_open_mode_t locked TORRENT_DEPRECATED = 6_bit; +#endif + + // the file is memory mapped + constexpr file_open_mode_t mmapped = 7_bit; +} + + // this contains information about a file that's currently open by the + // libtorrent disk I/O subsystem. It's associated with a single torrent. + struct TORRENT_EXPORT open_file_state + { + // the index of the file this entry refers to into the ``file_storage`` + // file list of this torrent. This starts indexing at 0. + file_index_t file_index; + + // ``open_mode`` is a bitmask of the file flags this file is currently + // opened with. For possible flags, see file_open_mode_t. + // + // Note that the read/write mode is not a bitmask. The two least significant bits are used + // to represent the read/write mode. Those bits can be masked out using the ``rw_mask`` constant. + file_open_mode_t open_mode; + + // a (high precision) timestamp of when the file was last used. + time_point last_use; + }; + + using disk_job_flags_t = flags::bitfield_flag; + + // The disk_interface is the customization point for disk I/O in libtorrent. + // implement this interface and provide a factory function to the session constructor + // use custom disk I/O. All functions on the disk subsystem (implementing + // disk_interface) are called from within libtorrent's network thread. For + // disk I/O to be performed in a separate thread, the disk subsystem has to + // manage that itself. + // + // Although the functions are called ``async_*``, they do not technically + // *have* to be asynchronous, but they support being asynchronous, by + // expecting the result passed back into a callback. The callbacks must be + // posted back onto the network thread via the io_context object passed into + // the constructor. The callbacks will be run in the network thread. + struct TORRENT_EXPORT disk_interface + { + // force making a copy of the cached block, rather than getting a + // reference to a block already in the cache. This is used the block is + // expected to be overwritten very soon, by async_write()`, and we need + // access to the previous content. + static constexpr disk_job_flags_t force_copy = 0_bit; + + // hint that there may be more disk operations with sequential access to + // the file + static constexpr disk_job_flags_t sequential_access = 3_bit; + + // don't keep the read block in cache. This is a hint that this block is + // unlikely to be read again anytime soon, and caching it would be + // wasteful. + static constexpr disk_job_flags_t volatile_read = 4_bit; + + // compute a v1 piece hash. This is only used by the async_hash() call. + // If this flag is not set in the async_hash() call, the SHA-1 piece + // hash does not need to be computed. + static constexpr disk_job_flags_t v1_hash = 5_bit; + + // this flag instructs a hash job that we just completed this piece, and + // it should be flushed to disk + static constexpr disk_job_flags_t flush_piece = 7_bit; + + // this is called when a new torrent is added. The shared_ptr can be + // used to hold the internal torrent object alive as long as there are + // outstanding disk operations on the storage. + // The returned storage_holder is an owning reference to the underlying + // storage that was just created. It is fundamentally a storage_index_t + virtual storage_holder new_torrent(storage_params const& p + , std::shared_ptr const& torrent) = 0; + + // remove the storage with the specified index. This is not expected to + // delete any files from disk, just to clean up any resources associated + // with the specified storage. + virtual void remove_torrent(storage_index_t) = 0; + + // perform a read or write operation from/to the specified storage + // index and the specified request. When the operation completes, call + // handler possibly with a disk_buffer_holder, holding the buffer with + // the result. Flags may be set to affect the read operation. See + // disk_job_flags_t. + // + // The disk_observer is a callback to indicate that + // the store buffer/disk write queue is below the watermark to let peers + // start writing buffers to disk again. When ``async_write()`` returns + // ``true``, indicating the write queue is full, the peer will stop + // further writes and wait for the passed-in ``disk_observer`` to be + // notified before resuming. + // + // Note that for ``async_read``, the peer_request (``r``) is not + // necessarily aligned to blocks (but it is most of the time). However, + // all writes (passed to ``async_write``) are guaranteed to be block + // aligned. + virtual void async_read(storage_index_t storage, peer_request const& r + , std::function handler + , disk_job_flags_t flags = {}) = 0; + virtual bool async_write(storage_index_t storage, peer_request const& r + , char const* buf, std::shared_ptr o + , std::function handler + , disk_job_flags_t flags = {}) = 0; + + // Compute hash(es) for the specified piece. Unless the v1_hash flag is + // set (in ``flags``), the SHA-1 hash of the whole piece does not need + // to be computed. + // + // The `v2` span is optional and can be empty, which means v2 hashes + // should not be computed. If v2 is non-empty it must be at least large + // enough to hold all v2 blocks in the piece, and this function will + // fill in the span with the SHA-256 block hashes of the piece. + virtual void async_hash(storage_index_t storage, piece_index_t piece, span v2 + , disk_job_flags_t flags + , std::function handler) = 0; + + // computes the v2 hash (SHA-256) of a single block. The block at + // ``offset`` in piece ``piece``. + virtual void async_hash2(storage_index_t storage, piece_index_t piece, int offset, disk_job_flags_t flags + , std::function handler) = 0; + + // called to request the files for the specified storage/torrent be + // moved to a new location. It is the disk I/O object's responsibility + // to synchronize this with any currently outstanding disk operations to + // the storage. Whether files are replaced at the destination path or + // not is controlled by ``flags`` (see move_flags_t). + virtual void async_move_storage(storage_index_t storage, std::string p, move_flags_t flags + , std::function handler) = 0; + + // This is called on disk I/O objects to request they close all open + // files for the specified storage/torrent. If file handles are not + // pooled/cached, it can be a no-op. For truly asynchronous disk I/O, + // this should provide at least one point in time when all files are + // closed. It is possible that later asynchronous operations will + // re-open some of the files, by the time this completion handler is + // called, that's fine. + virtual void async_release_files(storage_index_t storage + , std::function handler = std::function()) = 0; + + // this is called when torrents are added to validate their resume data + // against the files on disk. This function is expected to do a few things: + // + // if ``links`` is non-empty, it contains a string for each file in the + // torrent. The string being a path to an existing identical file. The + // default behavior is to create hard links of those files into the + // storage of the new torrent (specified by ``storage``). An empty + // string indicates that there is no known identical file. This is part + // of the "mutable torrent" feature, where files can be reused from + // other torrents. + // + // The ``resume_data`` points the resume data passed in by the client. + // + // If the ``resume_data->flags`` field has the seed_mode flag set, all + // files/pieces are expected to be on disk already. This should be + // verified. Not just the existence of the file, but also that it has + // the correct size. + // + // Any file with a piece set in the ``resume_data->have_pieces`` bitmask + // should exist on disk, this should be verified. Pad files and files + // with zero priority may be skipped. + virtual void async_check_files(storage_index_t storage + , add_torrent_params const* resume_data + , aux::vector links + , std::function handler) = 0; + + // This is called when a torrent is stopped. It gives the disk I/O + // object an opportunity to flush any data to disk that's currently kept + // cached. This function should at least do the same thing as + // async_release_files(). + virtual void async_stop_torrent(storage_index_t storage + , std::function handler = std::function()) = 0; + + // This function is called when the name of a file in the specified + // storage has been requested to be renamed. The disk I/O object is + // responsible for renaming the file without racing with other + // potentially outstanding operations against the file (such as read, + // write, move, etc.). + virtual void async_rename_file(storage_index_t storage + , file_index_t index, std::string name + , std::function handler) = 0; + + // This function is called when some file(s) on disk have been requested + // to be removed by the client. ``storage`` indicates which torrent is + // referred to. See session_handle for ``remove_flags_t`` flags + // indicating which files are to be removed. + // e.g. session_handle::delete_files - delete all files + // session_handle::delete_partfile - only delete part file. + virtual void async_delete_files(storage_index_t storage, remove_flags_t options + , std::function handler) = 0; + + // This is called to set the priority of some or all files. Changing the + // priority from or to 0 may involve moving data to and from the + // partfile. The disk I/O object is responsible for correctly + // synchronizing this work to not race with any potentially outstanding + // asynchronous operations affecting these files. + // + // ``prio`` is a vector of the file priority for all files. If it's + // shorter than the total number of files in the torrent, they are + // assumed to be set to the default priority. + virtual void async_set_file_priority(storage_index_t storage + , aux::vector prio + , std::function)> handler) = 0; + + // This is called when a piece fails the hash check, to ensure there are + // no outstanding disk operations to the piece before blocks are + // re-requested from peers to overwrite the existing blocks. The disk I/O + // object does not need to perform any action other than synchronize + // with all outstanding disk operations to the specified piece before + // posting the result back. + virtual void async_clear_piece(storage_index_t storage, piece_index_t index + , std::function handler) = 0; + + // update_stats_counters() is called to give the disk storage an + // opportunity to update gauges in the ``c`` stats counters, that aren't + // updated continuously as operations are performed. This is called + // before a snapshot of the counters are passed to the client. + virtual void update_stats_counters(counters& c) const = 0; + + // Return a list of all the files that are currently open for the + // specified storage/torrent. This is is just used for the client to + // query the currently open files, and which modes those files are open + // in. + virtual std::vector get_status(storage_index_t) const = 0; + + // this is called when the session is starting to shut down. The disk + // I/O object is expected to flush any outstanding write jobs, cancel + // hash jobs and initiate tearing down of any internal threads. If + // ``wait`` is true, this should be asynchronous. i.e. this call should + // not return until all threads have stopped and all jobs have either + // been aborted or completed and the disk I/O object is ready to be + // destructed. + virtual void abort(bool wait) = 0; + + // This will be called after a batch of disk jobs has been issues (via + // the ``async_*`` ). It gives the disk I/O object an opportunity to + // notify any potential condition variables to wake up the disk + // thread(s). The ``async_*`` calls can of course also notify condition + // variables, but doing it in this call allows for batching jobs, by + // issuing the notification once for a collection of jobs. + virtual void submit_jobs() = 0; + + // This is called to notify the disk I/O object that the settings have + // been updated. In the disk io constructor, a settings_interface + // reference is passed in. Whenever these settings are updated, this + // function is called to allow the disk I/O object to react to any + // changed settings relevant to its operations. + virtual void settings_updated() = 0; + + // hidden + virtual ~disk_interface() {} + }; + + // a unique, owning, reference to the storage of a torrent in a disk io + // subsystem (class that implements disk_interface). This is held by the + // internal libtorrent torrent object to tie the storage object allocated + // for a torrent to the lifetime of the internal torrent object. When a + // torrent is removed from the session, this holder is destructed and will + // inform the disk object. + struct TORRENT_EXPORT storage_holder + { + storage_holder() = default; + storage_holder(storage_index_t idx, disk_interface& disk_io) + : m_disk_io(&disk_io) + , m_idx(idx) + {} + ~storage_holder() + { + if (m_disk_io) m_disk_io->remove_torrent(m_idx); + } + + explicit operator bool() const { return m_disk_io != nullptr; } + + operator storage_index_t() const + { + TORRENT_ASSERT(m_disk_io); + return m_idx; + } + + void reset() + { + if (m_disk_io) m_disk_io->remove_torrent(m_idx); + m_disk_io = nullptr; + } + + storage_holder(storage_holder const&) = delete; + storage_holder& operator=(storage_holder const&) = delete; + + storage_holder(storage_holder&& rhs) noexcept + : m_disk_io(rhs.m_disk_io) + , m_idx(rhs.m_idx) + { + rhs.m_disk_io = nullptr; + } + + storage_holder& operator=(storage_holder&& rhs) noexcept + { + if (&rhs == this) return *this; + if (m_disk_io) m_disk_io->remove_torrent(m_idx); + m_disk_io = rhs.m_disk_io; + m_idx = rhs.m_idx; + rhs.m_disk_io = nullptr; + return *this; + } + private: + disk_interface* m_disk_io = nullptr; + storage_index_t m_idx{0}; + }; + +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/disk_observer.hpp b/include/libtorrent/disk_observer.hpp new file mode 100644 index 0000000..4b5d5e9 --- /dev/null +++ b/include/libtorrent/disk_observer.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2010, 2013-2015, 2017, 2020, Arvid Norberg +Copyright (c) 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_OBSERVER_HPP +#define TORRENT_DISK_OBSERVER_HPP + +#include "libtorrent/config.hpp" + +namespace libtorrent { + + struct TORRENT_EXPORT disk_observer + { + // called when the disk cache size has dropped + // below the low watermark again and we can + // resume downloading from peers + virtual void on_disk() = 0; + protected: + ~disk_observer() {} + }; +} + +#endif diff --git a/include/libtorrent/download_priority.hpp b/include/libtorrent/download_priority.hpp new file mode 100644 index 0000000..144dd3e --- /dev/null +++ b/include/libtorrent/download_priority.hpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2017-2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DOWNLOAD_PRIORITY_HPP_INCLUDED +#define TORRENT_DOWNLOAD_PRIORITY_HPP_INCLUDED + +#include "libtorrent/units.hpp" + +namespace libtorrent { + +using download_priority_t = aux::strong_typedef; + +// Don't download the file or piece. Partial pieces may still be downloaded when +// setting file priorities. +constexpr download_priority_t dont_download{0}; + +// The default priority for files and pieces. +constexpr download_priority_t default_priority{4}; + +// The lowest priority for files and pieces. +constexpr download_priority_t low_priority{1}; + +// The highest priority for files and pieces. +constexpr download_priority_t top_priority{7}; + +} + +#endif diff --git a/include/libtorrent/entry.hpp b/include/libtorrent/entry.hpp new file mode 100644 index 0000000..87e0ab6 --- /dev/null +++ b/include/libtorrent/entry.hpp @@ -0,0 +1,334 @@ +/* + +Copyright (c) 2003-2009, 2013-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ENTRY_HPP_INCLUDED +#define TORRENT_ENTRY_HPP_INCLUDED + +/* + * + * This file declares the entry class. It is a + * variant-type that can be an integer, list, + * dictionary (map) or a string. This type is + * used to hold bdecoded data (which is the + * encoding BitTorrent messages uses). + * + * it has 4 accessors to access the actual + * type of the object. They are: + * integer() + * string() + * list() + * dict() + * The actual type has to match the type you + * are asking for, otherwise you will get an + * assertion failure. + * When you default construct an entry, it is + * uninitialized. You can initialize it through the + * assignment operator, copy-constructor or + * the constructor that takes a data_type enum. + * + * + */ + +#include "libtorrent/config.hpp" + +#include +#include +#include +#include +#include +#include +#if TORRENT_USE_IOSTREAM +#include +#endif + +#include "libtorrent/assert.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/aux_/aligned_union.hpp" +#include "libtorrent/aux_/strview_less.hpp" + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 + // backwards compatibility + using type_error = system_error; +#endif + struct bdecode_node; + + // The ``entry`` class represents one node in a bencoded hierarchy. It works as a + // variant type, it can be either a list, a dictionary (``std::map``), an integer + // or a string. + class TORRENT_EXPORT entry + { + public: + + // the key is always a string. If a generic entry would be allowed + // as a key, sorting would become a problem (e.g. to compare a string + // to a list). The definition doesn't mention such a limit though. + using dictionary_type = std::map; + using string_type = std::string; + using list_type = std::vector; + using integer_type = std::int64_t; + using preformatted_type = std::vector; + + // the types an entry can have + enum data_type + { + int_t, + string_t, + list_t, + dictionary_t, + undefined_t, + preformatted_t + }; + + // returns the concrete type of the entry + data_type type() const; + + // constructors directly from a specific type. + // The content of the argument is copied into the + // newly constructed entry + entry(dictionary_type); // NOLINT + entry(span); // NOLINT + entry(list_type); // NOLINT + entry(integer_type); // NOLINT + entry(preformatted_type); // NOLINT + + // hidden + template ::value + || std::is_same::value + || std::is_same::value>::type> + entry(U v) // NOLINT + : m_type(undefined_t) + { +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + new(&data) string_type(std::move(v)); + m_type = string_t; + } + + // construct an empty entry of the specified type. + // see data_type enum. + entry(data_type t); // NOLINT + + // hidden + entry(entry const& e); + entry(entry&& e) noexcept; + + // construct from bdecode_node parsed form (see bdecode()) + entry(bdecode_node const& n); // NOLINT + + // hidden + entry(); + + // hidden + ~entry(); + + // copies the structure of the right hand side into this + // entry. + entry& operator=(bdecode_node const&) &; + entry& operator=(entry const&) &; + entry& operator=(entry&&) & noexcept; + entry& operator=(dictionary_type) &; + entry& operator=(span) &; + entry& operator=(list_type) &; + entry& operator=(integer_type) &; + entry& operator=(preformatted_type) &; + + // hidden + template ::value + || std::is_same::value>::type> + entry& operator=(U v) & + { + destruct(); + new(&data) string_type(std::move(v)); + m_type = string_t; +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + return *this; + } + + // The ``integer()``, ``string()``, ``list()`` and ``dict()`` functions + // are accessors that return the respective type. If the ``entry`` object + // isn't of the type you request, the accessor will throw + // system_error. You can ask an ``entry`` for its type through the + // ``type()`` function. + // + // If you want to create an ``entry`` you give it the type you want it to + // have in its constructor, and then use one of the non-const accessors + // to get a reference which you then can assign the value you want it to + // have. + // + // The typical code to get info from a torrent file will then look like + // this: + // + // .. code:: c++ + // + // entry torrent_file; + // // ... + // + // // throws if this is not a dictionary + // entry::dictionary_type const& dict = torrent_file.dict(); + // entry::dictionary_type::const_iterator i; + // i = dict.find("announce"); + // if (i != dict.end()) + // { + // std::string tracker_url = i->second.string(); + // std::cout << tracker_url << "\n"; + // } + // + // + // The following code is equivalent, but a little bit shorter: + // + // .. code:: c++ + // + // entry torrent_file; + // // ... + // + // // throws if this is not a dictionary + // if (entry* i = torrent_file.find_key("announce")) + // { + // std::string tracker_url = i->string(); + // std::cout << tracker_url << "\n"; + // } + // + // + // To make it easier to extract information from a torrent file, the + // class torrent_info exists. + integer_type& integer(); + integer_type const& integer() const; + string_type& string(); + string_type const& string() const; + list_type& list(); + list_type const& list() const; + dictionary_type& dict(); + dictionary_type const& dict() const; + preformatted_type& preformatted(); + preformatted_type const& preformatted() const; + + // swaps the content of *this* with ``e``. + void swap(entry& e); + + // All of these functions requires the entry to be a dictionary, if it + // isn't they will throw ``system_error``. + // + // The non-const versions of the ``operator[]`` will return a reference + // to either the existing element at the given key or, if there is no + // element with the given key, a reference to a newly inserted element at + // that key. + // + // The const version of ``operator[]`` will only return a reference to an + // existing element at the given key. If the key is not found, it will + // throw ``system_error``. + entry& operator[](string_view key); + entry const& operator[](string_view key) const; + + // These functions requires the entry to be a dictionary, if it isn't + // they will throw ``system_error``. + // + // They will look for an element at the given key in the dictionary, if + // the element cannot be found, they will return nullptr. If an element + // with the given key is found, the return a pointer to it. + entry* find_key(string_view key); + entry const* find_key(string_view key) const; + + // returns a pretty-printed string representation + // of the bencoded structure, with JSON-style syntax + std::string to_string(bool single_line = false) const; + + protected: + + void construct(data_type t); + void copy(const entry& e); + void destruct(); + + private: + + aux::aligned_union<1 +#if TORRENT_COMPLETE_TYPES_REQUIRED + // for implementations that require complete types, use char and hope + // for the best + , std::list + , std::map +#else + , list_type + , dictionary_type +#endif + , preformatted_type + , string_type + , integer_type + >::type data; + + // the bitfield is used so that the m_type_queried field still fits, so + // that the ABI is the same for debug builds and release builds. It + // appears to be very hard to match debug builds with debug versions of + // libtorrent + std::uint8_t m_type:7; + + public: + // hidden + // in debug mode this is set to false by bdecode to indicate that the + // program has not yet queried the type of this entry, and should not + // assume that it has a certain type. This is asserted in the accessor + // functions. This does not apply if exceptions are used. + mutable std::uint8_t m_type_queried:1; + }; + + // hidden + TORRENT_EXPORT bool operator==(entry const& lhs, entry const& rhs); + inline bool operator!=(entry const& lhs, entry const& rhs) { return !(lhs == rhs); } + +namespace aux { + + // internal + TORRENT_EXPORT string_view integer_to_str(std::array& buf + , entry::integer_type val); +} + +#if TORRENT_USE_IOSTREAM + // prints the bencoded structure to the ostream as a JSON-style structure. + inline std::ostream& operator<<(std::ostream& os, const entry& e) + { + os << e.to_string(); + return os; + } +#endif + +} + +#endif // TORRENT_ENTRY_HPP_INCLUDED diff --git a/include/libtorrent/enum_net.hpp b/include/libtorrent/enum_net.hpp new file mode 100644 index 0000000..2009f39 --- /dev/null +++ b/include/libtorrent/enum_net.hpp @@ -0,0 +1,234 @@ +/* + +Copyright (c) 2007-2008, 2010, 2014-2021, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ENUM_NET_HPP_INCLUDED +#define TORRENT_ENUM_NET_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#if TORRENT_USE_IFCONF || TORRENT_USE_NETLINK || TORRENT_USE_SYSCTL +#include // for SO_BINDTODEVICE +#endif + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/io_context.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/bind_to_device.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/flags.hpp" + +#include + +namespace libtorrent { + + // internal +using interface_flags = flags::bitfield_flag; + +namespace if_flags { + + // internal + constexpr interface_flags up = 0_bit; + constexpr interface_flags broadcast = 1_bit; + constexpr interface_flags loopback = 2_bit; + constexpr interface_flags pointopoint = 3_bit; + constexpr interface_flags running = 4_bit; + constexpr interface_flags noarp = 5_bit; + constexpr interface_flags promisc = 6_bit; + constexpr interface_flags allmulti = 7_bit; + constexpr interface_flags master = 8_bit; + constexpr interface_flags slave = 9_bit; + constexpr interface_flags multicast = 10_bit; + constexpr interface_flags dynamic = 11_bit; + constexpr interface_flags lower_up = 12_bit; + constexpr interface_flags dormant = 13_bit; +} + +// internal +enum class if_state : std::uint8_t { + + up, + dormant, + lowerlayerdown, + down, + notpresent, + testing, + unknown +}; + +// internal + struct ip_interface + { + address interface_address; + address netmask; + char name[64]{}; + char friendly_name[128]{}; + char description[128]{}; + // an interface is preferred if its address is + // not tentative/duplicate/deprecated + bool preferred = true; + + interface_flags flags = if_flags::up; + if_state state = if_state::unknown; + }; + +// internal + struct ip_route + { + address destination; + address netmask; + address gateway; + address source_hint; + char name[64]{}; + int mtu = 0; + }; + + // returns a list of the configured IP interfaces + // on the machine + TORRENT_EXTRA_EXPORT std::vector enum_net_interfaces(io_context& ios + , error_code& ec); + + TORRENT_EXTRA_EXPORT std::vector enum_routes(io_context& ios + , error_code& ec); + + // returns AF_INET or AF_INET6, depending on the address' family + TORRENT_EXTRA_EXPORT int family(address const& a); + + // return (a1 & mask) == (a2 & mask) + TORRENT_EXTRA_EXPORT bool match_addr_mask(address const& a1 + , address const& a2, address const& mask); + + // return a netmask with the specified address family and the specified + // number of prefix bit set, of the most significant bits in the resulting + // netmask + TORRENT_EXTRA_EXPORT address build_netmask(int bits, int family); + + // return the gateway for the given ip_interface, if there is one. Otherwise + // return nullopt. + TORRENT_EXTRA_EXPORT boost::optional
    get_gateway( + ip_interface const& iface, span routes); + + // returns whether there is a route to the specified device for for any global + // internet address of the specified address family. + TORRENT_EXTRA_EXPORT bool has_internet_route(string_view device, int family + , span routes); + + // returns whether there are *any* routes to the internet in the routing + // table. This can be used to determine if the routing table is fully + // populated or not. + TORRENT_EXTRA_EXPORT bool has_any_internet_route(span routes); + + // attempt to bind socket to the device with the specified name. For systems + // that don't support SO_BINDTODEVICE the socket will be bound to one of the + // IP addresses of the specified device. In this case it is necessary to + // verify the local endpoint of the socket once the connection is established. + // the returned address is the ip the socket was bound to (or address_v4::any() + // in case SO_BINDTODEVICE succeeded and we don't need to verify it). + // TODO: 3 use string_view for device_name + template + address bind_socket_to_device(io_context& ios, Socket& sock + , tcp const& protocol + , char const* device_name, int port, error_code& ec) + { + tcp::endpoint bind_ep(address_v4::any(), std::uint16_t(port)); + + address ip = make_address(device_name, ec); + if (!ec) + { + // this is to cover the case where "0.0.0.0" is considered any IPv4 or + // IPv6 address. If we're asking to be bound to an IPv6 address and + // providing 0.0.0.0 as the device, turn it into "::" + if (ip == address_v4::any() && protocol == boost::asio::ip::tcp::v6()) + ip = address_v6::any(); + bind_ep.address(ip); + // it appears to be an IP. Just bind to that address + sock.bind(bind_ep, ec); + return bind_ep.address(); + } + + ec.clear(); + +#if TORRENT_HAS_BINDTODEVICE + // try to use SO_BINDTODEVICE here, if that exists. If it fails, + // fall back to the mechanism we have below + aux::bind_device(sock, device_name, ec); + if (ec) +#endif + { + ec.clear(); + // TODO: 2 this could be done more efficiently by just looking up + // the interface with the given name, maybe even with if_nametoindex() + std::vector ifs = enum_net_interfaces(ios, ec); + if (ec) return bind_ep.address(); + + bool found = false; + + for (auto const& iface : ifs) + { + // we're looking for a specific interface, and its address + // (which must be of the same family as the address we're + // connecting to) + if (std::strcmp(iface.name, device_name) != 0) continue; + if (iface.interface_address.is_v4() != (protocol == boost::asio::ip::tcp::v4())) + continue; + + bind_ep.address(iface.interface_address); + found = true; + break; + } + + if (!found) + { + ec = error_code(boost::system::errc::no_such_device, generic_category()); + return bind_ep.address(); + } + } + sock.bind(bind_ep, ec); + return bind_ep.address(); + } + + // returns the device name whose local address is ``addr``. If + // no such device is found, an empty string is returned. + TORRENT_EXTRA_EXPORT std::string device_for_address(address addr + , io_context& ios, error_code& ec); + +} + +#endif diff --git a/include/libtorrent/error.hpp b/include/libtorrent/error.hpp new file mode 100644 index 0000000..939d101 --- /dev/null +++ b/include/libtorrent/error.hpp @@ -0,0 +1,53 @@ +/* + +Copyright (c) 2009, 2017, 2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ERROR_HPP_INCLUDED +#define TORRENT_ERROR_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#if defined TORRENT_WINDOWS || defined TORRENT_CYGWIN +// asio assumes that the windows error codes are defined already +#include +#endif + +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + + namespace error = boost::asio::error; +} + +#endif diff --git a/include/libtorrent/error_code.hpp b/include/libtorrent/error_code.hpp new file mode 100644 index 0000000..d34ab0e --- /dev/null +++ b/include/libtorrent/error_code.hpp @@ -0,0 +1,602 @@ +/* + +Copyright (c) 2008-2011, 2013-2021, Arvid Norberg +Copyright (c) 2016-2017, 2019, Steven Siloti +Copyright (c) 2018, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ERROR_CODE_HPP_INCLUDED +#define TORRENT_ERROR_CODE_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/operations.hpp" + +namespace libtorrent { + +namespace errors { + // libtorrent uses boost.system's ``error_code`` class to represent + // errors. libtorrent has its own error category + // libtorrent_category() with the error codes defined by + // error_code_enum. + enum error_code_enum + { + // Not an error + no_error = 0, + // Two torrents has files which end up overwriting each other + file_collision, + // A piece did not match its piece hash + failed_hash_check, + // The .torrent file does not contain a bencoded dictionary at + // its top level + torrent_is_no_dict, + // The .torrent file does not have an ``info`` dictionary + torrent_missing_info, + // The .torrent file's ``info`` entry is not a dictionary + torrent_info_no_dict, + // The .torrent file does not have a ``piece length`` entry + torrent_missing_piece_length, + // The .torrent file does not have a ``name`` entry + torrent_missing_name, + // The .torrent file's name entry is invalid + torrent_invalid_name, + // The length of a file, or of the whole .torrent file is invalid. + // Either negative or not an integer + torrent_invalid_length, + // Failed to parse a file entry in the .torrent + torrent_file_parse_failed, + // The ``pieces`` field is missing or invalid in the .torrent file + torrent_missing_pieces, + // The ``pieces`` string has incorrect length + torrent_invalid_hashes, + // The .torrent file has more pieces than is supported by libtorrent + too_many_pieces_in_torrent, + // The metadata (.torrent file) that was received from the swarm + // matched the info-hash, but failed to be parsed + invalid_swarm_metadata, + // The file or buffer is not correctly bencoded + invalid_bencoding, + // The .torrent file does not contain any files + no_files_in_torrent, + // The string was not properly url-encoded as expected + invalid_escaped_string, + // Operation is not permitted since the session is shutting down + session_is_closing, + // There's already a torrent with that info-hash added to the + // session + duplicate_torrent, + // The supplied torrent_handle is not referring to a valid torrent + invalid_torrent_handle, + // The type requested from the entry did not match its type + invalid_entry_type, + // The specified URI does not contain a valid info-hash + missing_info_hash_in_uri, + // One of the files in the torrent was unexpectedly small. This + // might be caused by files being changed by an external process + file_too_short, + // The URL used an unknown protocol. Currently ``http`` and + // ``https`` (if built with openssl support) are recognized. For + // trackers ``udp`` is recognized as well. + unsupported_url_protocol, + // The URL did not conform to URL syntax and failed to be parsed + url_parse_error, + // The peer sent a piece message of length 0 + peer_sent_empty_piece, + // A bencoded structure was corrupt and failed to be parsed + parse_failed, + // The fast resume file was missing or had an invalid file version + // tag + invalid_file_tag, + // The fast resume file was missing or had an invalid info-hash + missing_info_hash, + // The info-hash did not match the torrent + mismatching_info_hash, + // The URL contained an invalid hostname + invalid_hostname, + // The URL had an invalid port + invalid_port, + // The port is blocked by the port-filter, and prevented the + // connection + port_blocked, + // The IPv6 address was expected to end with "]" + expected_close_bracket_in_address, + // The torrent is being destructed, preventing the operation to + // succeed + destructing_torrent, + // The connection timed out + timed_out, + // The peer is upload only, and we are upload only. There's no point + // in keeping the connection + upload_upload_connection, + // The peer is upload only, and we're not interested in it. There's + // no point in keeping the connection + uninteresting_upload_peer, + // The peer sent an unknown info-hash + invalid_info_hash, + // The torrent is paused, preventing the operation from succeeding + torrent_paused, + // The peer sent an invalid have message, either wrong size or + // referring to a piece that doesn't exist in the torrent + invalid_have, + // The bitfield message had the incorrect size + invalid_bitfield_size, + // The peer kept requesting pieces after it was choked, possible + // abuse attempt. + too_many_requests_when_choked, + // The peer sent a piece message that does not correspond to a + // piece request sent by the client + invalid_piece, + // memory allocation failed + no_memory, + // The torrent is aborted, preventing the operation to succeed + torrent_aborted, + // The peer is a connection to ourself, no point in keeping it + self_connection, + // The peer sent a piece message with invalid size, either negative + // or greater than one block + invalid_piece_size, + // The peer has not been interesting or interested in us for too + // long, no point in keeping it around + timed_out_no_interest, + // The peer has not said anything in a long time, possibly dead + timed_out_inactivity, + // The peer did not send a handshake within a reasonable amount of + // time, it might not be a bittorrent peer + timed_out_no_handshake, + // The peer has been unchoked for too long without requesting any + // data. It might be lying about its interest in us + timed_out_no_request, + // The peer sent an invalid choke message + invalid_choke, + // The peer send an invalid unchoke message + invalid_unchoke, + // The peer sent an invalid interested message + invalid_interested, + // The peer sent an invalid not-interested message + invalid_not_interested, + // The peer sent an invalid piece request message + invalid_request, + // The peer sent an invalid hash-list message (this is part of the + // merkle-torrent extension) + invalid_hash_list, + // The peer sent an invalid hash-piece message (this is part of the + // merkle-torrent extension) + invalid_hash_piece, + // The peer sent an invalid cancel message + invalid_cancel, + // The peer sent an invalid DHT port-message + invalid_dht_port, + // The peer sent an invalid suggest piece-message + invalid_suggest, + // The peer sent an invalid have all-message + invalid_have_all, + // The peer sent an invalid have none-message + invalid_have_none, + // The peer sent an invalid reject message + invalid_reject, + // The peer sent an invalid allow fast-message + invalid_allow_fast, + // The peer sent an invalid extension message ID + invalid_extended, + // The peer sent an invalid message ID + invalid_message, + // The synchronization hash was not found in the encrypted handshake + sync_hash_not_found, + // The encryption constant in the handshake is invalid + invalid_encryption_constant, + // The peer does not support plain text, which is the selected mode + no_plaintext_mode, + // The peer does not support RC4, which is the selected mode + no_rc4_mode, + // The peer does not support any of the encryption modes that the + // client supports + unsupported_encryption_mode, + // The peer selected an encryption mode that the client did not + // advertise and does not support + unsupported_encryption_mode_selected, + // The pad size used in the encryption handshake is of invalid size + invalid_pad_size, + // The encryption handshake is invalid + invalid_encrypt_handshake, + // The client is set to not support incoming encrypted connections + // and this is an encrypted connection + no_incoming_encrypted, + // The client is set to not support incoming regular bittorrent + // connections, and this is a regular connection + no_incoming_regular, + // The client is already connected to this peer-ID + duplicate_peer_id, + // Torrent was removed + torrent_removed, + // The packet size exceeded the upper sanity check-limit + packet_too_large, + + reserved, + + // The web server responded with an error + http_error, + // The web server response is missing a location header + missing_location, + // The web seed redirected to a path that no longer matches the + // .torrent directory structure + invalid_redirection, + // The connection was closed because it redirected to a different + // URL + redirecting, + // The HTTP range header is invalid + invalid_range, + // The HTTP response did not have a content length + no_content_length, + // The IP is blocked by the IP filter + banned_by_ip_filter, + // At the connection limit + too_many_connections, + // The peer is marked as banned + peer_banned, + // The torrent is stopping, causing the operation to fail + stopping_torrent, + // The peer has sent too many corrupt pieces and is banned + too_many_corrupt_pieces, + // The torrent is not ready to receive peers + torrent_not_ready, + // The peer is not completely constructed yet + peer_not_constructed, + // The session is closing, causing the operation to fail + session_closing, + // The peer was disconnected in order to leave room for a + // potentially better peer + optimistic_disconnect, + // The torrent is finished + torrent_finished, + // No UPnP router found + no_router, + // The metadata message says the metadata exceeds the limit + metadata_too_large, + // The peer sent an invalid metadata request message + invalid_metadata_request, + // The peer advertised an invalid metadata size + invalid_metadata_size, + // The peer sent a message with an invalid metadata offset + invalid_metadata_offset, + // The peer sent an invalid metadata message + invalid_metadata_message, + // The peer sent a peer exchange message that was too large + pex_message_too_large, + // The peer sent an invalid peer exchange message + invalid_pex_message, + // The peer sent an invalid tracker exchange message + invalid_lt_tracker_message, + // The peer sent an pex messages too often. This is a possible + // attempt of and attack + too_frequent_pex, + // The operation failed because it requires the torrent to have + // the metadata (.torrent file) and it doesn't have it yet. + // This happens for magnet links before they have downloaded the + // metadata, and also torrents added by URL. + no_metadata, + // The peer sent an invalid ``dont_have`` message. The don't have + // message is an extension to allow peers to advertise that the + // no longer has a piece they previously had. + invalid_dont_have, + // The peer tried to connect to an SSL torrent without connecting + // over SSL. + requires_ssl_connection, + // The peer tried to connect to a torrent with a certificate + // for a different torrent. + invalid_ssl_cert, + // the torrent is not an SSL torrent, and the operation requires + // an SSL torrent + not_an_ssl_torrent, + // peer was banned because its listen port is within a banned port + // range, as specified by the port_filter. + banned_by_port_filter, + // The session_handle is not referring to a valid session_impl + invalid_session_handle, + // the listen socket associated with this request was closed + invalid_listen_socket, + invalid_hash_request, + invalid_hashes, + invalid_hash_reject, + +#if TORRENT_ABI_VERSION == 1 + // these error codes are deprecated, NAT-PMP/PCP error codes have + // been moved to their own category + + // The NAT-PMP router responded with an unsupported protocol version + unsupported_protocol_version TORRENT_DEPRECATED_ENUM = 120, + // You are not authorized to map ports on this NAT-PMP router + natpmp_not_authorized TORRENT_DEPRECATED_ENUM, + // The NAT-PMP router failed because of a network failure + network_failure TORRENT_DEPRECATED_ENUM, + // The NAT-PMP router failed because of lack of resources + no_resources TORRENT_DEPRECATED_ENUM, + // The NAT-PMP router failed because an unsupported opcode was sent + unsupported_opcode TORRENT_DEPRECATED_ENUM, +#else + deprecated_120 = 120, + deprecated_121, + deprecated_122, + deprecated_123, + deprecated_124, +#endif + + // The resume data file is missing the ``file sizes`` entry + missing_file_sizes = 130, + // The resume data file ``file sizes`` entry is empty + no_files_in_resume_data, + // The resume data file is missing the ``pieces`` and ``slots`` entry + missing_pieces, + // The number of files in the resume data does not match the number + // of files in the torrent + mismatching_number_of_files, + // One of the files on disk has a different size than in the fast + // resume file + mismatching_file_size, + // One of the files on disk has a different timestamp than in the + // fast resume file + mismatching_file_timestamp, + // The resume data file is not a dictionary + not_a_dictionary, + // The ``blocks per piece`` entry is invalid in the resume data file + invalid_blocks_per_piece, + // The resume file is missing the ``slots`` entry, which is required + // for torrents with compact allocation. *DEPRECATED* + missing_slots, + // The resume file contains more slots than the torrent + too_many_slots, + // The ``slot`` entry is invalid in the resume data + invalid_slot_list, + // One index in the ``slot`` list is invalid + invalid_piece_index, + // The pieces on disk needs to be re-ordered for the specified + // allocation mode. This happens if you specify sparse allocation + // and the files on disk are using compact storage. The pieces needs + // to be moved to their right position. *DEPRECATED* + pieces_need_reorder, + // this error is returned when asking to save resume data and + // specifying the flag to only save when there's anything new to save + // (torrent_handle::only_if_modified) and there wasn't anything changed. + resume_data_not_modified, + // the save_path in add_torrent_params is not valid + invalid_save_path, + + + // The HTTP header was not correctly formatted + http_parse_error = 150, + // The HTTP response was in the 300-399 range but lacked a location + // header + http_missing_location, + // The HTTP response was encoded with gzip or deflate but + // decompressing it failed + http_failed_decompress, + + + + // The URL specified an i2p address, but no i2p router is configured + no_i2p_router = 160, + // i2p acceptor is not available yet, can't announce without endpoint + no_i2p_endpoint = 161, + + + // The tracker URL doesn't support transforming it into a scrape + // URL. i.e. it doesn't contain "announce. + scrape_not_available = 170, + // invalid tracker response + invalid_tracker_response, + // invalid peer dictionary entry. Not a dictionary + invalid_peer_dict, + // tracker sent a failure message + tracker_failure, + // missing or invalid ``files`` entry + invalid_files_entry, + // missing or invalid ``hash`` entry + invalid_hash_entry, + // missing or invalid ``peers`` and ``peers6`` entry + invalid_peers_entry, + // UDP tracker response packet has invalid size + invalid_tracker_response_length, + // invalid transaction id in UDP tracker response + invalid_tracker_transaction_id, + // invalid action field in UDP tracker response + invalid_tracker_action, + // skipped announce (because it's assumed to be unreachable over the + // given source network interface) + announce_skipped, + +#if TORRENT_ABI_VERSION == 1 + // expected string in bencoded string + expected_string = 190, + // expected colon in bencoded string + expected_colon, + // unexpected end of file in bencoded string + unexpected_eof, + // expected value (list, dict, int or string) in bencoded string + expected_value, + // bencoded recursion depth limit exceeded + depth_exceeded, + // bencoded item count limit exceeded + limit_exceeded, + // integer overflow + overflow, +#endif + + // random number generation failed + no_entropy = 200, + // blocked by SSRF mitigation + ssrf_mitigation, + // blocked because IDNA host names are banned + blocked_by_idna, + + // the torrent file has an unknown meta version + torrent_unknown_version = 210, + // the v2 torrent file has no file tree + torrent_missing_file_tree, + // the torrent contains v2 keys but does not specify meta version 2 + torrent_missing_meta_version, + // the v1 and v2 file metadata does not match + torrent_inconsistent_files, + // one or more files are missing piece layer hashes + torrent_missing_piece_layer, + // a piece layer has the wrong size or failed hash check + torrent_invalid_piece_layer, + // a v2 file entry has no root hash + torrent_missing_pieces_root, + // the v1 and v2 hashes do not describe the same data + torrent_inconsistent_hashes, + // a file in the v2 metadata has the pad attribute set + torrent_invalid_pad_file, + + // the number of error codes + error_code_max + }; + + // HTTP errors are reported in the libtorrent::http_category, with error code enums in + // the ``libtorrent::errors`` namespace. + enum http_errors + { + cont = 100, + ok = 200, + created = 201, + accepted = 202, + no_content = 204, + multiple_choices = 300, + moved_permanently = 301, + moved_temporarily = 302, + not_modified = 304, + bad_request = 400, + unauthorized = 401, + forbidden = 403, + not_found = 404, + internal_server_error = 500, + not_implemented = 501, + bad_gateway = 502, + service_unavailable = 503 + }; + + // hidden + TORRENT_EXPORT boost::system::error_code make_error_code(error_code_enum e); + +} // namespace errors + + // return the instance of the libtorrent_error_category which + // maps libtorrent error codes to human readable error messages. + TORRENT_EXPORT boost::system::error_category& libtorrent_category(); + + // returns the error_category for HTTP errors + TORRENT_EXPORT boost::system::error_category& http_category(); + + using error_code = boost::system::error_code; + using error_condition = boost::system::error_condition; + + // internal + using boost::system::generic_category; + using boost::system::system_category; + + using system_error = boost::system::system_error; + +#ifndef BOOST_NO_EXCEPTIONS +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + inline boost::system::error_category& get_libtorrent_category() + { return libtorrent_category(); } + + TORRENT_DEPRECATED + inline boost::system::error_category& get_http_category() + { return http_category(); } +#endif +#endif + + // used by storage to return errors + // also includes which underlying file the + // error happened on + struct TORRENT_EXPORT storage_error + { + // hidden + storage_error(): file_idx(-1), operation(operation_t::unknown) {} + explicit storage_error(error_code e): ec(e), file_idx(-1), operation(operation_t::unknown) {} + storage_error(error_code e, operation_t const op) + : ec(e), file_idx(-1), operation(op) {} + storage_error(error_code e, file_index_t f, operation_t const op) + : ec(e), file_idx(f), operation(op) {} + + // explicitly converts to true if this object represents an error, and + // false if it does not. + explicit operator bool() const { return ec.value() != 0; } + + // the error that occurred + error_code ec; + + // set and query the index (in the torrent) of the file this error + // occurred on. This may also have special values defined in + // torrent_status. + file_index_t file() const { return file_index_t(file_idx); } + void file(file_index_t f) { file_idx = static_cast(f); } + + private: + // internal + std::int32_t file_idx:24; + + public: + + // A code from operation_t enum, indicating what + // kind of operation failed. + operation_t operation; + +#if TORRENT_ABI_VERSION == 1 + // Returns a string literal representing the file operation + // that failed. If there were no failure, it returns + // an empty string. + TORRENT_DEPRECATED + char const* operation_str() const + { return operation_name(operation); } +#endif + }; + + // internal + std::string print_error(error_code const&); +} // namespace libtorrent + +namespace boost { +namespace system { + + template<> struct is_error_code_enum + { static const bool value = true; }; + + template<> struct is_error_code_enum + { static const bool value = true; }; +} +} + +#endif diff --git a/include/libtorrent/extensions.hpp b/include/libtorrent/extensions.hpp new file mode 100644 index 0000000..8302b9d --- /dev/null +++ b/include/libtorrent/extensions.hpp @@ -0,0 +1,552 @@ +/* + +Copyright (c) 2006-2007, 2011, 2013-2020, Arvid Norberg +Copyright (c) 2014-2019, Steven Siloti +Copyright (c) 2016, Alden Torres +Copyright (c) 2018, Greg Hazel +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_EXTENSIONS_HPP_INCLUDED +#define TORRENT_EXTENSIONS_HPP_INCLUDED + +#include "libtorrent/units.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/peer_info.hpp" // for peer_source_flags_t +#include "libtorrent/torrent_status.hpp" // for torrent_status::state_t + +// OVERVIEW +// +// libtorrent has a plugin interface for implementing extensions to the protocol. +// These can be general extensions for transferring metadata or peer exchange +// extensions, or it could be used to provide a way to customize the protocol +// to fit a particular (closed) network. +// +// In short, the plugin interface makes it possible to: +// +// * register extension messages (sent in the extension handshake), see +// extensions_. +// * add data and parse data from the extension handshake. +// * send extension messages and standard bittorrent messages. +// * override or block the handling of standard bittorrent messages. +// * save and restore state via the session state +// * see all alerts that are posted +// +// a word of caution +// ----------------- +// +// Writing your own plugin is a very easy way to introduce serious bugs such as +// dead locks and race conditions. Since a plugin has access to internal +// structures it is also quite easy to sabotage libtorrent's operation. +// +// All the callbacks are always called from the libtorrent network thread. In +// case portions of your plugin are called from other threads, typically the main +// thread, you cannot use any of the member functions on the internal structures +// in libtorrent, since those require being called from the libtorrent network +// thread . Furthermore, you also need to synchronize your own shared data +// within the plugin, to make sure it is not accessed at the same time from the +// libtorrent thread (through a callback). If you need to send out a message +// from another thread, it is advised to use an internal queue, and do the +// actual sending in ``tick()``. +// +// Since the plugin interface gives you easy access to internal structures, it +// is not supported as a stable API. Plugins should be considered specific to a +// specific version of libtorrent. Although, in practice the internals mostly +// don't change that dramatically. +// +// +// plugin-interface +// ---------------- +// +// The plugin interface consists of three base classes that the plugin may +// implement. These are called plugin, torrent_plugin and peer_plugin. +// They are found in the ```` header. +// +// These plugins are instantiated for each session, torrent and possibly each peer, +// respectively. +// +// For plugins that only need per torrent state, it is enough to only implement +// ``torrent_plugin`` and pass a constructor function or function object to +// ``session::add_extension()`` or ``torrent_handle::add_extension()`` (if the +// torrent has already been started and you want to hook in the extension at +// run-time). +// +// The signature of the function is: +// +// .. code:: c++ +// +// std::shared_ptr (*)(torrent_handle const&, client_data_t); +// +// The second argument is the userdata passed to ``session::add_torrent()`` or +// ``torrent_handle::add_extension()``. +// +// The function should return a ``std::shared_ptr`` which +// may or may not be 0. If it is a nullptr, the extension is simply ignored +// for this torrent. If it is a valid pointer (to a class inheriting +// ``torrent_plugin``), it will be associated with this torrent and callbacks +// will be made on torrent events. +// +// For more elaborate plugins which require session wide state, you would +// implement ``plugin``, construct an object (in a ``std::shared_ptr``) and pass +// it in to ``session::add_extension()``. +// +// custom alerts +// ------------- +// +// Since plugins are running within internal libtorrent threads, one convenient +// way to communicate with the client is to post custom alerts. +// +// The expected interface of any alert, apart from deriving from the alert +// base class, looks like this: +// +// .. parsed-literal:: +// +// static const int alert_type = **; +// virtual int type() const { return alert_type; } +// +// virtual std::string message() const; +// +// static const alert_category_t static_category = **; +// virtual alert_category_t category() const { return static_category; } +// +// virtual char const* what() const { return **; } +// +// The ``alert_type`` is used for the type-checking in ``alert_cast``. It must +// not collide with any other alert. The built-in alerts in libtorrent will +// not use alert type IDs greater than ``user_alert_id``. When defining your +// own alert, make sure it's greater than this constant. +// +// ``type()`` is the run-time equivalence of the ``alert_type``. +// +// The ``message()`` virtual function is expected to construct a useful +// string representation of the alert and the event or data it represents. +// Something convenient to put in a log file for instance. +// +// ``clone()`` is used internally to copy alerts. The suggested implementation +// of simply allocating a new instance as a copy of ``*this`` is all that's +// expected. +// +// The static category is required for checking whether or not the category +// for a specific alert is enabled or not, without instantiating the alert. +// The ``category`` virtual function is the run-time equivalence. +// +// The ``what()`` virtual function may simply be a string literal of the class +// name of your alert. +// +// For more information, see the `alert section`_. +// +// .. _`alert section`: reference-Alerts.html + +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" + +namespace libtorrent { + +#ifndef TORRENT_DISABLE_EXTENSIONS + + // these are flags that can be returned by implemented_features() + // indicating which callbacks this plugin is interested in + using feature_flags_t = flags::bitfield_flag; + +TORRENT_VERSION_NAMESPACE_3 + + // this is the base class for a session plugin. One primary feature + // is that it is notified of all torrents that are added to the session, + // and can add its own torrent_plugins. + struct TORRENT_EXPORT plugin + { + // hidden + virtual ~plugin() {} + +#if TORRENT_ABI_VERSION == 1 + using feature_flags_t = libtorrent::feature_flags_t; +#endif + + // include this bit if your plugin needs to alter the order of the + // optimistic unchoke of peers. i.e. have the on_optimistic_unchoke() + // callback be called. + static constexpr feature_flags_t optimistic_unchoke_feature = 1_bit; + + // include this bit if your plugin needs to have on_tick() called + static constexpr feature_flags_t tick_feature = 2_bit; + + // include this bit if your plugin needs to have on_dht_request() + // called + static constexpr feature_flags_t dht_request_feature = 3_bit; + + // include this bit if your plugin needs to have on_alert() + // called + static constexpr feature_flags_t alert_feature = 4_bit; + + // include this bit if your plugin needs to have on_unknown_torrent() + // called even if there is no active torrent in the session + static constexpr feature_flags_t unknown_torrent_feature = 5_bit; + + // This function is expected to return a bitmask indicating which features + // this plugin implements. Some callbacks on this object may not be called + // unless the corresponding feature flag is returned here. Note that + // callbacks may still be called even if the corresponding feature is not + // specified in the return value here. See feature_flags_t for possible + // flags to return. + virtual feature_flags_t implemented_features() { return {}; } + + // this is called by the session every time a new torrent is added. + // The ``torrent*`` points to the internal torrent object created + // for the new torrent. The client_data_t is the userdata pointer as + // passed in via add_torrent_params. + // + // If the plugin returns a torrent_plugin instance, it will be added + // to the new torrent. Otherwise, return an empty shared_ptr to a + // torrent_plugin (the default). + virtual std::shared_ptr new_torrent(torrent_handle const&, client_data_t) + { return std::shared_ptr(); } + + // called when plugin is added to a session + virtual void added(session_handle const&) {} + + // called when the session is aborted + // the plugin should perform any cleanup necessary to allow the session's + // destruction (e.g. cancel outstanding async operations) + virtual void abort() {} + + // called when a dht request is received. + // If your plugin expects this to be called, make sure to include the flag + // ``dht_request_feature`` in the return value from implemented_features(). + virtual bool on_dht_request(string_view /* query */ + , udp::endpoint const& /* source */, bdecode_node const& /* message */ + , entry& /* response */) + { return false; } + + // called when an alert is posted alerts that are filtered are not posted. + // If your plugin expects this to be called, make sure to include the flag + // ``alert_feature`` in the return value from implemented_features(). + virtual void on_alert(alert const*) {} + + // return true if the add_torrent_params should be added + virtual bool on_unknown_torrent(info_hash_t const& /* info_hash */ + , peer_connection_handle const& /* pc */, add_torrent_params& /* p */) + { return false; } + + // called once per second. + // If your plugin expects this to be called, make sure to include the flag + // ``tick_feature`` in the return value from implemented_features(). + virtual void on_tick() {} + + // called when choosing peers to optimistically unchoke. The return value + // indicates the peer's priority for unchoking. Lower return values + // correspond to higher priority. Priorities above 2^63-1 are reserved. + // If your plugin has no priority to assign a peer it should return 2^64-1. + // If your plugin expects this to be called, make sure to include the flag + // ``optimistic_unchoke_feature`` in the return value from implemented_features(). + // If multiple plugins implement this function the lowest return value + // (i.e. the highest priority) is used. + virtual uint64_t get_unchoke_priority(peer_connection_handle const& /* peer */) + { return (std::numeric_limits::max)(); } + +#if TORRENT_ABI_VERSION <= 2 + // called when saving settings state + virtual void save_state(entry&) {} + + // called when loading settings state + virtual void load_state(bdecode_node const&) {} +#endif + + virtual std::map save_state() const { return {}; } + + // called on startup while loading settings state from the session_params + virtual void load_state(std::map const&) {} + }; + +TORRENT_VERSION_NAMESPACE_3_END + + using add_peer_flags_t = flags::bitfield_flag; + + // Torrent plugins are associated with a single torrent and have a number + // of functions called at certain events. Many of its functions have the + // ability to change or override the default libtorrent behavior. + struct TORRENT_EXPORT torrent_plugin + { + // hidden + virtual ~torrent_plugin() {} + +#if TORRENT_ABI_VERSION == 1 + using flags_t = libtorrent::add_peer_flags_t; +#endif + + // This function is called each time a new peer is connected to the torrent. You + // may choose to ignore this by just returning a default constructed + // ``shared_ptr`` (in which case you don't need to override this member + // function). + // + // If you need an extension to the peer connection (which most plugins do) you + // are supposed to return an instance of your peer_plugin class. Which in + // turn will have its hook functions called on event specific to that peer. + // + // The ``peer_connection_handle`` will be valid as long as the ``shared_ptr`` + // is being held by the torrent object. So, it is generally a good idea to not + // keep a ``shared_ptr`` to your own peer_plugin. If you want to keep references + // to it, use ``weak_ptr``. + // + // If this function throws an exception, the connection will be closed. + virtual std::shared_ptr new_connection(peer_connection_handle const&) + { return std::shared_ptr(); } + + // These hooks are called when a piece passes the hash check or fails the hash + // check, respectively. The ``index`` is the piece index that was downloaded. + // It is possible to access the list of peers that participated in sending the + // piece through the ``torrent`` and the ``piece_picker``. + virtual void on_piece_pass(piece_index_t) {} + virtual void on_piece_failed(piece_index_t) {} + + // This hook is called approximately once per second. It is a way of making it + // easy for plugins to do timed events, for sending messages or whatever. + virtual void tick() {} + + // These hooks are called when the torrent is paused and resumed respectively. + // The return value indicates if the event was handled. A return value of + // ``true`` indicates that it was handled, and no other plugin after this one + // will have this hook function called, and the standard handler will also not be + // invoked. So, returning true effectively overrides the standard behavior of + // pause or resume. + // + // Note that if you call ``pause()`` or ``resume()`` on the torrent from your + // handler it will recurse back into your handler, so in order to invoke the + // standard handler, you have to keep your own state on whether you want standard + // behavior or overridden behavior. + virtual bool on_pause() { return false; } + virtual bool on_resume() { return false; } + + // This function is called when the initial files of the torrent have been + // checked. If there are no files to check, this function is called immediately. + // + // i.e. This function is always called when the torrent is in a state where it + // can start downloading. + virtual void on_files_checked() {} + + // called when the torrent changes state + // the state is one of torrent_status::state_t + // enum members + virtual void on_state(torrent_status::state_t) {} + + // this is the first time we see this peer + static constexpr add_peer_flags_t first_time = 1_bit; + + // this peer was not added because it was + // filtered by the IP filter + static constexpr add_peer_flags_t filtered = 2_bit; + + // called every time a new peer is added to the peer list. + // This is before the peer is connected to. For ``flags``, see + // torrent_plugin::flags_t. The ``source`` argument refers to + // the source where we learned about this peer from. It's a + // bitmask, because many sources may have told us about the same + // peer. For peer source flags, see peer_info::peer_source_flags. + virtual void on_add_peer(tcp::endpoint const&, + peer_source_flags_t, add_peer_flags_t) {} + }; + + // peer plugins are associated with a specific peer. A peer could be + // both a regular bittorrent peer (``bt_peer_connection``) or one of the + // web seed connections (``web_peer_connection`` or ``http_seed_connection``). + // In order to only attach to certain peers, make your + // torrent_plugin::new_connection only return a plugin for certain peer + // connection types + struct TORRENT_EXPORT peer_plugin + { + // hidden + virtual ~peer_plugin() {} + + // This function is expected to return the name of + // the plugin. + virtual string_view type() const { return {}; } + + // can add entries to the extension handshake + // this is not called for web seeds + virtual void add_handshake(entry&) {} + + // called when the peer is being disconnected. + virtual void on_disconnect(error_code const&) {} + + // called when the peer is successfully connected. Note that + // incoming connections will have been connected by the time + // the peer plugin is attached to it, and won't have this hook + // called. + virtual void on_connected() {} + + // throwing an exception from any of the handlers (except add_handshake) + // closes the connection + + // this is called when the initial bittorrent handshake is received. + // Returning false means that the other end doesn't support this extension + // and will remove it from the list of plugins. this is not called for web + // seeds + virtual bool on_handshake(span) { return true; } + + // called when the extension handshake from the other end is received + // if this returns false, it means that this extension isn't + // supported by this peer. It will result in this peer_plugin + // being removed from the peer_connection and destructed. + // this is not called for web seeds + virtual bool on_extension_handshake(bdecode_node const&) { return true; } + + // returning true from any of the message handlers + // indicates that the plugin has handled the message. + // it will break the plugin chain traversing and not let + // anyone else handle the message, including the default + // handler. + virtual bool on_choke() { return false; } + virtual bool on_unchoke() { return false; } + virtual bool on_interested() { return false; } + virtual bool on_not_interested() { return false; } + virtual bool on_have(piece_index_t) { return false; } + virtual bool on_dont_have(piece_index_t) { return false; } + virtual bool on_bitfield(bitfield const& /*bitfield*/) { return false; } + virtual bool on_have_all() { return false; } + virtual bool on_have_none() { return false; } + virtual bool on_allowed_fast(piece_index_t) { return false; } + virtual bool on_request(peer_request const&) { return false; } + + // This function is called when the peer connection is receiving + // a piece. ``buf`` points (non-owning pointer) to the data in an + // internal immutable disk buffer. The length of the data is specified + // in the ``length`` member of the ``piece`` parameter. + // returns true to indicate that the piece is handled and the + // rest of the logic should be ignored. + virtual bool on_piece(peer_request const& /*piece*/ + , span /*buf*/) { return false; } + + virtual bool on_cancel(peer_request const&) { return false; } + virtual bool on_reject(peer_request const&) { return false; } + virtual bool on_suggest(piece_index_t) { return false; } + + virtual void sent_have_all() {} + virtual void sent_have_none() {} + virtual void sent_reject_request(peer_request const&) {} + virtual void sent_allow_fast(piece_index_t) {} + virtual void sent_suggest(piece_index_t) {} + virtual void sent_cancel(peer_request const&) {} + virtual void sent_request(peer_request const&) {} + virtual void sent_choke() {} + // called after a choke message has been sent to the peer + virtual void sent_unchoke() {} + virtual void sent_interested() {} + virtual void sent_not_interested() {} + virtual void sent_have(piece_index_t) {} + virtual void sent_piece(peer_request const&) {} + + // called after piece data has been sent to the peer + // this can be used for stats book keeping + virtual void sent_payload(int /* bytes */) {} + + // called when libtorrent think this peer should be disconnected. + // if the plugin returns false, the peer will not be disconnected. + virtual bool can_disconnect(error_code const& /*ec*/) { return true; } + + // called when an extended message is received. If returning true, + // the message is not processed by any other plugin and if false + // is returned the next plugin in the chain will receive it to + // be able to handle it. This is not called for web seeds. + // thus function may be called more than once per incoming message, but + // only the last of the calls will the ``body`` size equal the ``length``. + // i.e. Every time another fragment of the message is received, this + // function will be called, until finally the whole message has been + // received. The purpose of this is to allow early disconnects for invalid + // messages and for reporting progress of receiving large messages. + virtual bool on_extended(int /*length*/, int /*msg*/, + span /*body*/) + { return false; } + + // this is not called for web seeds + virtual bool on_unknown_message(int /*length*/, int /*msg*/, + span /*body*/) + { return false; } + + // called when a piece that this peer participated in either + // fails or passes the hash_check + virtual void on_piece_pass(piece_index_t) {} + virtual void on_piece_failed(piece_index_t) {} + + // called approximately once every second + virtual void tick() {} + + // called each time a request message is to be sent. If true + // is returned, the original request message won't be sent and + // no other plugin will have this function called. + virtual bool write_request(peer_request const&) { return false; } + }; +#endif // TORRENT_DISABLE_EXTENSIONS + +#if !defined TORRENT_DISABLE_ENCRYPTION + + struct TORRENT_EXPORT crypto_plugin + { + // hidden + virtual ~crypto_plugin() {} + + virtual void set_incoming_key(span key) = 0; + virtual void set_outgoing_key(span key) = 0; + + // encrypted the provided buffers and returns the number of bytes which + // are now ready to be sent to the lower layer. This must be at least + // as large as the number of bytes passed in and may be larger if there + // is additional data to be inserted at the head of the send buffer. + // The additional data is returned as the second tuple value. Any + // returned buffer as well as the iovec itself, to be prepended to the + // send buffer, must be owned by the crypto plugin and guaranteed to stay + // alive until the crypto_plugin is destructed or this function is called + // again. + virtual std::tuple>> + encrypt(span> /*send_vec*/) = 0; + + // decrypt the provided buffers. + // returns is a tuple representing the values + // (consume, produce, packet_size) + // + // consume is set to the number of bytes which should be trimmed from the + // head of the buffers, default is 0 + // + // produce is set to the number of bytes of payload which are now ready to + // be sent to the upper layer. default is the number of bytes passed in receive_vec + // + // packet_size is set to the minimum number of bytes which must be read to + // advance the next step of decryption. default is 0 + virtual std::tuple decrypt(span> /*receive_vec*/) = 0; + }; + +#endif // TORRENT_DISABLE_ENCRYPTION +} + +#endif // TORRENT_EXTENSIONS_HPP_INCLUDED diff --git a/include/libtorrent/extensions/smart_ban.hpp b/include/libtorrent/extensions/smart_ban.hpp new file mode 100644 index 0000000..d4acafa --- /dev/null +++ b/include/libtorrent/extensions/smart_ban.hpp @@ -0,0 +1,61 @@ +/* + +Copyright (c) 2007, 2013, 2017, 2019, Arvid Norberg +Copyright (c) 2015, Steven Siloti +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SMART_BAN_HPP_INCLUDED +#define TORRENT_SMART_BAN_HPP_INCLUDED + +#ifndef TORRENT_DISABLE_EXTENSIONS + +#include "libtorrent/config.hpp" + +#include + +namespace libtorrent { + + struct torrent_plugin; + struct torrent_handle; + struct client_data_t; + + // constructor function for the smart ban extension. The extension keeps + // track of the data peers have sent us for failing pieces and once the + // piece completes and passes the hash check bans the peers that turned + // out to have sent corrupt data. + // This function can either be passed in the add_torrent_params::extensions + // field, or via torrent_handle::add_extension(). + TORRENT_EXPORT std::shared_ptr create_smart_ban_plugin(torrent_handle const&, client_data_t); +} + +#endif // TORRENT_DISABLE_EXTENSIONS + +#endif // TORRENT_SMART_BAN_HPP_INCLUDED diff --git a/include/libtorrent/extensions/ut_metadata.hpp b/include/libtorrent/extensions/ut_metadata.hpp new file mode 100644 index 0000000..57ccfdc --- /dev/null +++ b/include/libtorrent/extensions/ut_metadata.hpp @@ -0,0 +1,63 @@ +/* + +Copyright (c) 2007, 2013, 2016-2017, 2019, Arvid Norberg +Copyright (c) 2015, Steven Siloti +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UT_METADATA_HPP_INCLUDED +#define TORRENT_UT_METADATA_HPP_INCLUDED + +#ifndef TORRENT_DISABLE_EXTENSIONS + +#include "libtorrent/config.hpp" + +#include + +namespace libtorrent { + + struct torrent_plugin; + struct torrent_handle; + struct client_data_t; + + // constructor function for the ut_metadata extension. The ut_metadata + // extension allows peers to request the .torrent file (or more + // specifically the info-dictionary of the .torrent file) from each + // other. This is the main building block in making magnet links work. + // This extension is enabled by default unless explicitly disabled in + // the session constructor. + // + // This can either be passed in the add_torrent_params::extensions field, or + // via torrent_handle::add_extension(). + TORRENT_EXPORT std::shared_ptr create_ut_metadata_plugin(torrent_handle const&, client_data_t); +} + +#endif // TORRENT_DISABLE_EXTENSIONS +#endif // TORRENT_UT_METADATA_HPP_INCLUDED diff --git a/include/libtorrent/extensions/ut_pex.hpp b/include/libtorrent/extensions/ut_pex.hpp new file mode 100644 index 0000000..2bdc448 --- /dev/null +++ b/include/libtorrent/extensions/ut_pex.hpp @@ -0,0 +1,64 @@ +/* + +Copyright (c) 2006, MassaRoddel +Copyright (c) 2006, 2013, 2015-2017, 2019, Arvid Norberg +Copyright (c) 2015, Steven Siloti +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UT_PEX_EXTENSION_HPP_INCLUDED +#define TORRENT_UT_PEX_EXTENSION_HPP_INCLUDED + +#ifndef TORRENT_DISABLE_EXTENSIONS + +#include "libtorrent/config.hpp" + +#include + +namespace libtorrent { + + struct torrent_plugin; + struct torrent_handle; + struct client_data_t; + + // constructor function for the ut_pex extension. The ut_pex + // extension allows peers to gossip about their connections, allowing + // the swarm stay well connected and peers aware of more peers in the + // swarm. This extension is enabled by default unless explicitly disabled in + // the session constructor. + // + // This can either be passed in the add_torrent_params::extensions field, or + // via torrent_handle::add_extension(). + TORRENT_EXPORT std::shared_ptr create_ut_pex_plugin(torrent_handle const&, client_data_t); +} + +#endif // TORRENT_DISABLE_EXTENSIONS + +#endif // TORRENT_UT_PEX_EXTENSION_HPP_INCLUDED diff --git a/include/libtorrent/file.hpp b/include/libtorrent/file.hpp new file mode 100644 index 0000000..02a0014 --- /dev/null +++ b/include/libtorrent/file.hpp @@ -0,0 +1,128 @@ +/* + +Copyright (c) 2004, 2008-2010, 2014-2018, 2020, Arvid Norberg +Copyright (c) 2016, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FILE_HPP_INCLUDED +#define TORRENT_FILE_HPP_INCLUDED + +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/aux_/storage_utils.hpp" +#include "libtorrent/aux_/open_mode.hpp" +#include "libtorrent/flags.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#ifdef TORRENT_WINDOWS +// windows part +#include "libtorrent/aux_/windows.hpp" +#include +#include +#else +// posix part + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 +#endif + +#include +#include +#include +#include +#include // for DIR + +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/error_code.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent { + +#ifdef TORRENT_WINDOWS + using handle_type = HANDLE; + const handle_type invalid_handle = INVALID_HANDLE_VALUE; +#else + using handle_type = int; + const handle_type invalid_handle = -1; +#endif + +namespace aux { + + int pwrite_all(handle_type handle + , span buf + , std::int64_t file_offset + , error_code& ec); + + int pread_all(handle_type handle + , span buf + , std::int64_t file_offset + , error_code& ec); + + struct TORRENT_EXTRA_EXPORT file_handle + { + file_handle(): m_fd(invalid_handle) {} + file_handle(string_view name, std::int64_t size, open_mode_t mode); + file_handle(file_handle const& rhs) = delete; + file_handle& operator=(file_handle const& rhs) = delete; + + file_handle(file_handle&& rhs) : m_fd(rhs.m_fd) { rhs.m_fd = invalid_handle; } + file_handle& operator=(file_handle&& rhs) &; + + ~file_handle(); + + std::int64_t get_size() const; + + handle_type fd() const { return m_fd; } + private: + void close(); + handle_type m_fd; +#ifdef TORRENT_WINDOWS + aux::open_mode_t m_open_mode; +#endif + }; + +} // namespace aux +} + +#endif // TORRENT_FILE_HPP_INCLUDED diff --git a/include/libtorrent/file_layout.hpp b/include/libtorrent/file_layout.hpp new file mode 100644 index 0000000..4d6f0e0 --- /dev/null +++ b/include/libtorrent/file_layout.hpp @@ -0,0 +1,44 @@ +/* + +Copyright (c) 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FILE_LAYOUT_HPP_INCLUDED +#define TORRENT_FILE_LAYOUT_HPP_INCLUDED + +#include "libtorrent/file_storage.hpp" + +namespace libtorrent { + + using file_layout = file_storage; +} + +#endif + diff --git a/include/libtorrent/file_storage.hpp b/include/libtorrent/file_storage.hpp new file mode 100644 index 0000000..8050397 --- /dev/null +++ b/include/libtorrent/file_storage.hpp @@ -0,0 +1,744 @@ +/* + +Copyright (c) 2008-2010, 2012-2021, Arvid Norberg +Copyright (c) 2016, 2020, Alden Torres +Copyright (c) 2017, 2019, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FILE_STORAGE_HPP_INCLUDED +#define TORRENT_FILE_STORAGE_HPP_INCLUDED + + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/assert.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/index_range.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/disk_interface.hpp" // for default_block_size +#include "libtorrent/fwd.hpp" + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 + // information about a file in a file_storage + struct TORRENT_DEPRECATED_EXPORT file_entry + { +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + // hidden + file_entry(); + // hidden + ~file_entry(); + file_entry(file_entry const&) = default; + file_entry& operator=(file_entry const&) & = default; + file_entry(file_entry&&) noexcept = default; + file_entry& operator=(file_entry&&) & = default; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // the full path of this file. The paths are unicode strings + // encoded in UTF-8. + std::string path; + + // the path which this is a symlink to, or empty if this is + // not a symlink. This field is only used if the ``symlink_attribute`` is set. + std::string symlink_path; + + // the offset of this file inside the torrent + std::int64_t offset; + + // the size of the file (in bytes) and ``offset`` is the byte offset + // of the file within the torrent. i.e. the sum of all the sizes of the files + // before it in the list. + std::int64_t size; + + // the modification time of this file specified in posix time. + std::time_t mtime; + + // a SHA-1 hash of the content of the file, or zeros, if no + // file hash was present in the torrent file. It can be used to potentially + // find alternative sources for the file. + sha1_hash filehash; + + // set to true for files that are not part of the data of the torrent. + // They are just there to make sure the next file is aligned to a particular byte offset + // or piece boundary. These files should typically be hidden from an end user. They are + // not written to disk. + bool pad_file:1; + + // true if the file was marked as hidden (on windows). + bool hidden_attribute:1; + + // true if the file was marked as executable (posix) + bool executable_attribute:1; + + // true if the file was a symlink. If this is the case + // the ``symlink_index`` refers to a string which specifies the original location + // where the data for this file was found. + bool symlink_attribute:1; + }; + +#endif // TORRENT_ABI_VERSION + +namespace aux { + struct path_index_tag; + using path_index_t = aux::strong_typedef; + + // internal + struct file_entry + { + friend class ::lt::file_storage; + file_entry(); + file_entry(file_entry const& fe); + file_entry& operator=(file_entry const& fe) &; + file_entry(file_entry&& fe) noexcept; + file_entry& operator=(file_entry&& fe) & noexcept; + ~file_entry(); + + void set_name(string_view n, bool borrow_string = false); + string_view filename() const; + + enum { + name_is_owned = (1 << 12) - 1, + not_a_symlink = (1 << 15) - 1, + }; + + static constexpr aux::path_index_t no_path{(1 << 30) - 1}; + static constexpr aux::path_index_t path_is_absolute{(1 << 30) - 2}; + + // the offset of this file inside the torrent + std::uint64_t offset:48; + + // index into file_storage::m_symlinks or not_a_symlink + // if this is not a symlink + std::uint64_t symlink_index:15; + + // if this is true, don't include m_name as part of the + // path to this file + std::uint64_t no_root_dir:1; + + // the size of this file + std::uint64_t size:48; + + // the number of characters in the name. If this is + // name_is_owned, name is 0-terminated and owned by this object + // (i.e. it should be freed in the destructor). If + // the len is not name_is_owned, the name pointer does not belong + // to this object, and it's not 0-terminated + std::uint64_t name_len:12; + std::uint64_t pad_file:1; + std::uint64_t hidden_attribute:1; + std::uint64_t executable_attribute:1; + std::uint64_t symlink_attribute:1; + + // make it available for logging + private: + // This string is not necessarily 0-terminated! + // that's why it's private, to keep people away from it + char const* name = nullptr; + public: + // the SHA-256 root of the merkle tree for this file + // this is a pointer into the .torrent file + char const* root = nullptr; + + // the index into file_storage::m_paths. To get + // the full path to this file, concatenate the path + // from that array with the 'name' field in + // this struct + // values for path_index include: + // no_path means no path (i.e. single file torrent) + // path_is_absolute means the filename + // in this field contains the full, absolute path + // to the file + aux::path_index_t path_index = file_entry::no_path; + }; + +} // aux namespace + + // represents a window of a file in a torrent. + // + // The ``file_index`` refers to the index of the file (in the torrent_info). + // To get the path and filename, use ``file_path()`` and give the ``file_index`` + // as argument. The ``offset`` is the byte offset in the file where the range + // starts, and ``size`` is the number of bytes this range is. The size + offset + // will never be greater than the file size. + struct TORRENT_EXPORT file_slice + { + // the index of the file + file_index_t file_index; + + // the offset from the start of the file, in bytes + std::int64_t offset; + + // the size of the window, in bytes + std::int64_t size; + }; + + // hidden + using file_flags_t = flags::bitfield_flag; + + // The ``file_storage`` class represents a file list and the piece + // size. Everything necessary to interpret a regular bittorrent storage + // file structure. + class TORRENT_EXPORT file_storage + { + public: + // hidden + file_storage(); + // hidden + ~file_storage(); + file_storage(file_storage const&); + file_storage& operator=(file_storage const&) &; + file_storage(file_storage&&) noexcept; + file_storage& operator=(file_storage&&) &; + + // internal limitations restrict file sizes to not be larger than this + // We use int to index into file merkle trees, so a file may not contain more + // than INT_MAX entries. That means INT_MAX / 2 blocks (leafs) in each + // tree. + static constexpr std::int64_t max_file_size = (std::min)( + (std::int64_t(1) << 48) - 1 + , std::int64_t((std::numeric_limits::max)() / 2) * default_block_size); + static constexpr std::int64_t max_file_offset = (std::int64_t(1) << 48) - 1; + + // we use a signed 32 bit integer for piece indices internally, but + // frequently need headroom for intermediate calculations, so we limit + // the number of pieces 1 bit below the maximum + static constexpr std::int32_t max_num_pieces = (std::int32_t(1) << 30) - 1; + + // limit the piece length at (2 ^ 30) to get a bit of headroom. We + // commonly compute the number of blocks per pieces by adding + // block_size - 1 before dividing by block_size. That would overflow with + // a piece size of 2 ^ 31. This limit is still an unreasonably large + // piece size anyway. + // The piece picker (currently) has a limit of no more than (2^15)-1 + // blocks per piece, which is more restrictive, at a block size of 16 + // kiB (0x4000). + static constexpr std::int32_t max_piece_size = ((1 << 15) - 1) * 0x4000; + + // returns true if the piece length has been initialized + // on the file_storage. This is typically taken as a proxy + // of whether the file_storage as a whole is initialized or + // not. + bool is_valid() const { return m_piece_length > 0; } + +#if TORRENT_ABI_VERSION == 1 + using flags_t = file_flags_t; + TORRENT_DEPRECATED static constexpr file_flags_t pad_file = 0_bit; + TORRENT_DEPRECATED static constexpr file_flags_t attribute_hidden = 1_bit; + TORRENT_DEPRECATED static constexpr file_flags_t attribute_executable = 2_bit; + TORRENT_DEPRECATED static constexpr file_flags_t attribute_symlink = 3_bit; +#endif + + // allocates space for ``num_files`` in the internal file list. This can + // be used to avoid reallocating the internal file list when the number + // of files to be added is known up-front. + void reserve(int num_files); + + // Adds a file to the file storage. The ``add_file_borrow`` version + // expects that ``filename`` is the file name (without a path) of + // the file that's being added. + // This memory is *borrowed*, i.e. it is the caller's + // responsibility to make sure it stays valid throughout the lifetime + // of this file_storage object or any copy of it. The same thing applies + // to ``filehash``, which is an optional pointer to a 20 byte binary + // SHA-1 hash of the file. + // + // if ``filename`` is empty, the filename from ``path`` is used and not + // borrowed. + // + // The ``path`` argument is the full path (in the torrent file) to + // the file to add. Note that this is not supposed to be an absolute + // path, but it is expected to include the name of the torrent as the + // first path element. + // + // ``file_size`` is the size of the file in bytes. + // + // The ``file_flags`` argument sets attributes on the file. The file + // attributes is an extension and may not work in all bittorrent clients. + // + // For possible file attributes, see file_storage::flags_t. + // + // The ``mtime`` argument is optional and can be set to 0. If non-zero, + // it is the posix time of the last modification time of this file. + // + // ``symlink_path`` is the path the file is a symlink to. To make this a + // symlink you also need to set the file_storage::flag_symlink file flag. + // + // ``root_hash`` is an optional pointer to a 32 byte SHA-256 hash, being + // the merkle tree root hash for this file. This is only used for v2 + // torrents. If the ``root hash`` is specified for one file, it has to + // be specified for all, otherwise this function will fail. + // Note that the buffer ``root_hash`` points to must out-live the + // file_storage object, it will not be copied. This parameter is only + // used when *loading* torrents, that already have their file hashes + // computed. When creating torrents, the file hashes will be computed by + // the piece hashes. + // + // If more files than one are added, certain restrictions to their paths + // apply. In a multi-file file storage (torrent), all files must share + // the same root directory. + // + // That is, the first path element of all files must be the same. + // This shared path element is also set to the name of the torrent. It + // can be changed by calling ``set_name``. + // + // The overloads that take an `error_code` reference will report failures + // via that variable, otherwise `system_error` is thrown. +#ifndef BOOST_NO_EXCEPTIONS + void add_file_borrow(string_view filename + , std::string const& path, std::int64_t file_size + , file_flags_t file_flags = {}, char const* filehash = nullptr + , std::int64_t mtime = 0, string_view symlink_path = string_view() + , char const* root_hash = nullptr); + void add_file(std::string const& path, std::int64_t file_size + , file_flags_t file_flags = {} + , std::time_t mtime = 0, string_view symlink_path = string_view() + , char const* root_hash = nullptr); +#endif // BOOST_NO_EXCEPTIONS + void add_file_borrow(error_code& ec, string_view filename + , std::string const& path, std::int64_t file_size + , file_flags_t file_flags = {}, char const* filehash = nullptr + , std::int64_t mtime = 0, string_view symlink_path = string_view() + , char const* root_hash = nullptr); + void add_file(error_code& ec, std::string const& path, std::int64_t file_size + , file_flags_t file_flags = {} + , std::time_t mtime = 0, string_view symlink_path = string_view() + , char const* root_hash = nullptr); + + // renames the file at ``index`` to ``new_filename``. Keep in mind + // that filenames are expected to be UTF-8 encoded. + void rename_file(file_index_t index, std::string const& new_filename); + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + TORRENT_DEPRECATED + void add_file_borrow(char const* filename, int filename_len + , std::string const& path, std::int64_t file_size + , file_flags_t file_flags = {}, char const* filehash = nullptr + , std::int64_t mtime = 0, string_view symlink_path = string_view()); + TORRENT_DEPRECATED + void add_file(file_entry const& fe, char const* filehash = nullptr); + + // all functions depending on aux::file_entry + // were deprecated in 1.0. Use the variants that take an + // index instead + using iterator = std::vector::const_iterator; + using reverse_iterator = std::vector::const_reverse_iterator; + + TORRENT_DEPRECATED + iterator file_at_offset(std::int64_t offset) const; + TORRENT_DEPRECATED + iterator begin() const { return m_files.begin(); } + TORRENT_DEPRECATED + iterator end() const { return m_files.end(); } + TORRENT_DEPRECATED + reverse_iterator rbegin() const { return m_files.rbegin(); } + TORRENT_DEPRECATED + reverse_iterator rend() const { return m_files.rend(); } + TORRENT_DEPRECATED + aux::file_entry const& internal_at(int const index) const; + TORRENT_DEPRECATED + file_entry at(iterator i) const; + + // returns a file_entry with information about the file + // at ``index``. Index must be in the range [0, ``num_files()`` ). + TORRENT_DEPRECATED + file_entry at(int index) const; + + iterator begin_deprecated() const { return m_files.begin(); } + iterator end_deprecated() const { return m_files.end(); } + reverse_iterator rbegin_deprecated() const { return m_files.rbegin(); } + reverse_iterator rend_deprecated() const { return m_files.rend(); } + iterator file_at_offset_deprecated(std::int64_t offset) const; + file_entry at_deprecated(int index) const; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // TORRENT_ABI_VERSION + + // returns a list of file_slice objects representing the portions of + // files the specified piece index, byte offset and size range overlaps. + // this is the inverse mapping of map_file(). + // + // Preconditions of this function is that the input range is within the + // torrents address space. ``piece`` may not be negative and + // + // ``piece`` * piece_size + ``offset`` + ``size`` + // + // may not exceed the total size of the torrent. + std::vector map_block(piece_index_t piece, std::int64_t offset + , std::int64_t size) const; + + // returns a peer_request representing the piece index, byte offset + // and size the specified file range overlaps. This is the inverse + // mapping over map_block(). Note that the ``peer_request`` return type + // is meant to hold bittorrent block requests, which may not be larger + // than 16 kiB. Mapping a range larger than that may return an overflown + // integer. + peer_request map_file(file_index_t file, std::int64_t offset, int size) const; + + // returns the number of files in the file_storage + int num_files() const noexcept; + + // returns the index of the one-past-end file in the file storage + file_index_t end_file() const noexcept; + + // returns an implementation-defined type that can be used as the + // container in a range-for loop. Where the values are the indices of all + // files in the file_storage. + index_range file_range() const noexcept; + + // returns the total number of bytes all the files in this torrent spans + std::int64_t total_size() const { return m_total_size; } + + // set and get the number of pieces in the torrent + void set_num_pieces(int n) { m_num_pieces = n; } + int num_pieces() const { TORRENT_ASSERT(m_piece_length > 0); return m_num_pieces; } + + // returns the index of the one-past-end piece in the file storage + piece_index_t end_piece() const + { return piece_index_t(m_num_pieces); } + + // returns the index of the last piece in the torrent. The last piece is + // special in that it may be smaller than the other pieces (and the other + // pieces are all the same size). + piece_index_t last_piece() const + { return piece_index_t(m_num_pieces - 1); } + + // returns an implementation-defined type that can be used as the + // container in a range-for loop. Where the values are the indices of all + // pieces in the file_storage. + index_range piece_range() const noexcept; + + // set and get the size of each piece in this torrent. It must be a power of two + // and at least 16 kiB. + void set_piece_length(int l) { m_piece_length = l; } + int piece_length() const { TORRENT_ASSERT(m_piece_length > 0); return m_piece_length; } + + // returns the piece size of ``index``. This will be the same as piece_length(), except + // for the last piece, which may be shorter. + int piece_size(piece_index_t index) const; + + // Returns the size of the given piece. If the piece spans multiple files, + // only the first file is considered part of the piece. This is used for + // v2 torrents, where all files are piece aligned and padded. i.e. The pad + // files are not considered part of the piece for this purpose. + int piece_size2(piece_index_t index) const; + + // returns the number of blocks in the specified piece, for v2 torrents. + int blocks_in_piece2(piece_index_t index) const; + + // returns the number of blocks there are in the typical piece. There + // may be fewer in the last piece) + int blocks_per_piece() const; + + // set and get the name of this torrent. For multi-file torrents, this is also + // the name of the root directory all the files are stored in. + void set_name(std::string const& n) { m_name = n; } + std::string const& name() const { return m_name; } + + // swap all content of *this* with *ti*. + void swap(file_storage& ti) noexcept; + + // arrange files and padding to match the canonical form required + // by BEP 52 + void canonicalize(); + + // These functions are used to query attributes of files at + // a given index. + // + // The ``hash()`` is a SHA-1 hash of the file, or 0 if none was + // provided in the torrent file. This can potentially be used to + // join a bittorrent network with other file sharing networks. + // + // ``root()`` returns the SHA-256 merkle tree root of the specified file, + // in case this is a v2 torrent. Otherwise returns zeros. + // ``root_ptr()`` returns a pointer to the SHA-256 merkle tree root hash + // for the specified file. The pointer points into storage referred to + // when the file was added, it is not owned by this object. Torrents + // that are not v2 torrents return nullptr. + // + // The ``mtime()`` is the modification time is the posix + // time when a file was last modified when the torrent + // was created, or 0 if it was not included in the torrent file. + // + // ``file_path()`` returns the full path to a file. + // + // ``file_size()`` returns the size of a file. + // + // ``pad_file_at()`` returns true if the file at the given + // index is a pad-file. + // + // ``file_name()`` returns *just* the name of the file, whereas + // ``file_path()`` returns the path (inside the torrent file) with + // the filename appended. + // + // ``file_offset()`` returns the byte offset within the torrent file + // where this file starts. It can be used to map the file to a piece + // index (given the piece size). + sha1_hash hash(file_index_t index) const; + sha256_hash root(file_index_t index) const; + char const* root_ptr(file_index_t const index) const; + std::string symlink(file_index_t index) const; + std::time_t mtime(file_index_t index) const; + std::string file_path(file_index_t index, std::string const& save_path = "") const; + string_view file_name(file_index_t index) const; + std::int64_t file_size(file_index_t index) const; + bool pad_file_at(file_index_t index) const; + std::int64_t file_offset(file_index_t index) const; + + // Returns the number of pieces or blocks the file at `index` spans, + // under the assumption that the file is aligned to the start of a piece. + // This is only meaningful for v2 torrents, where files are guaranteed + // such alignment. + // These numbers are used to size and navigate the merkle hash tree for + // each file. + int file_num_pieces(file_index_t index) const; + int file_num_blocks(file_index_t index) const; + index_range file_piece_range(file_index_t) const; + + // index of first piece node in the merkle tree + int file_first_piece_node(file_index_t index) const; + int file_first_block_node(file_index_t index) const; + + // returns the crc32 hash of file_path(index) + std::uint32_t file_path_hash(file_index_t index, std::string const& save_path) const; + + // this will add the CRC32 hash of all directory entries to the table. No + // filename will be included, just directories. Every depth of directories + // are added separately to allow test for collisions with files at all + // levels. i.e. if one path in the torrent is ``foo/bar/baz``, the CRC32 + // hashes for ``foo``, ``foo/bar`` and ``foo/bar/baz`` will be added to + // the set. + void all_path_hashes(std::unordered_set& table) const; + + // the file is a pad file. It's required to contain zeros + // at it will not be saved to disk. Its purpose is to make + // the following file start on a piece boundary. + static constexpr file_flags_t flag_pad_file = 0_bit; + + // this file has the hidden attribute set. This is primarily + // a windows attribute + static constexpr file_flags_t flag_hidden = 1_bit; + + // this file has the executable attribute set. + static constexpr file_flags_t flag_executable = 2_bit; + + // this file is a symbolic link. It should have a link + // target string associated with it. + static constexpr file_flags_t flag_symlink = 3_bit; + + // internal + // returns all directories used in the torrent. Files in the torrent are + // located in one of these directories. This is not a tree, it's a flat + // list of all *leaf* directories. i.e. the union of the parent paths of + // all files. + aux::vector const& paths() const { return m_paths; } + + // returns a bitmask of flags from file_flags_t that apply + // to file at ``index``. + file_flags_t file_flags(file_index_t index) const; + + // returns true if the file at the specified index has been renamed to + // have an absolute path, i.e. is not anchored in the save path of the + // torrent. + bool file_absolute_path(file_index_t index) const; + + // returns the index of the file at the given offset in the torrent + file_index_t file_index_at_offset(std::int64_t offset) const; + file_index_t file_index_at_piece(piece_index_t piece) const; + + // finds the file with the given root hash and returns its index + // if there is no file with the root hash, file_index_t{-1} is returned + file_index_t file_index_for_root(sha256_hash const& root_hash) const; + + // returns the piece index the given file starts at + piece_index_t piece_index_at_file(file_index_t f) const; + +#if TORRENT_USE_INVARIANT_CHECKS + // internal + bool owns_name(file_index_t const f) const + { return m_files[f].name_len == aux::file_entry::name_is_owned; } +#endif + +#if TORRENT_ABI_VERSION <= 2 + // low-level function. returns a pointer to the internal storage for + // the filename. This string may not be 0-terminated! + // the ``file_name_len()`` function returns the length of the filename. + // prefer to use ``file_name()`` instead, which returns a ``string_view``. + TORRENT_DEPRECATED + char const* file_name_ptr(file_index_t index) const; + TORRENT_DEPRECATED + int file_name_len(file_index_t index) const; +#endif + +#if TORRENT_ABI_VERSION == 1 + // these were deprecated in 1.0. Use the versions that take an index instead + TORRENT_DEPRECATED + sha1_hash hash(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + std::string symlink(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + std::time_t mtime(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + int file_index(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + std::string file_path(aux::file_entry const& fe, std::string const& save_path = "") const; + TORRENT_DEPRECATED + std::string file_name(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + std::int64_t file_size(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + bool pad_file_at(aux::file_entry const& fe) const; + TORRENT_DEPRECATED + std::int64_t file_offset(aux::file_entry const& fe) const; +#endif + + // validate any symlinks, to ensure they all point to + // other files or directories inside this storage. Any invalid symlinks + // are updated to point to themselves. + void sanitize_symlinks(); + + // returns true if this torrent contains v2 metadata. + bool v2() const { return m_v2; } + + // internal + // this is an optimization for create_torrent + std::string const& internal_symlink(file_index_t index) const; + + // internal + void remove_tail_padding(); + + // internal + void canonicalize_impl(bool backwards_compatible); + + private: + + std::string internal_file_path(file_index_t index) const; + file_index_t last_file() const noexcept; + + aux::path_index_t get_or_add_path(string_view path); + + // the number of bytes in a regular piece + // (i.e. not the potentially truncated last piece) + int m_piece_length = 0; + + // the number of pieces in the torrent + int m_num_pieces = 0; + + // whether this is a v2 torrent or not. Additional requirements apply to + // v2 torrents + bool m_v2 = false; + + void update_path_index(aux::file_entry& e, std::string const& path + , bool set_name = true); + + // the list of files that this torrent consists of + aux::vector m_files; + + // if there are sha1 hashes for each individual file there are as many + // entries in this array as the m_files array. Each entry in m_files has + // a corresponding hash pointer in this array. The reason to split it up + // in separate arrays is to save memory in case the torrent doesn't have + // file hashes + // the pointers in this vector are pointing into the .torrent file in + // memory which is _not_ owned by this file_storage object. It's simply + // a non-owning pointer. It is the user's responsibility that the hash + // stays valid throughout the lifetime of this file_storage object. + aux::vector m_file_hashes; + + // for files that are symlinks, the symlink + // path_index in the aux::file_entry indexes + // this vector of strings + std::vector m_symlinks; + + // the modification times of each file. This vector + // is empty if no file have a modification time. + // each element corresponds to the file with the same + // index in m_files + aux::vector m_mtime; + + // all unique paths files have. The aux::file_entry::path_index + // points into this array. The paths don't include the root directory + // name for multi-file torrents. The m_name field need to be + // prepended to these paths, and the filename of a specific file + // entry appended, to form full file paths + aux::vector m_paths; + + // name of torrent. For multi-file torrents + // this is always the root directory + std::string m_name; + + // the sum of all file sizes + std::int64_t m_total_size = 0; + }; + +namespace aux { + + TORRENT_EXTRA_EXPORT + int calc_num_pieces(file_storage const& fs); + + // this is used when loading v2 torrents that are backwards compatible with + // v1 torrents. Both v1 and v2 structures must describe the same file layout, + // this compares the two. + TORRENT_EXTRA_EXPORT + bool files_compatible(file_storage const& lhs, file_storage const& rhs); + + // returns the piece range that entirely falls within the specified file. the + // end piece is one-past the last piece that entirely falls within the file. + // i.e. They can conveniently be used as loop boundaries. No edge partial + // pieces will be included. + TORRENT_EXTRA_EXPORT std::tuple + file_piece_range_exclusive(file_storage const& fs, file_index_t file); + + // returns the piece range of pieces that overlaps with the specified file. + // the end piece is one-past the last piece. i.e. They can conveniently be + // used as loop boundaries. + TORRENT_EXTRA_EXPORT std::tuple + file_piece_range_inclusive(file_storage const& fs, file_index_t file); + + TORRENT_EXTRA_EXPORT + std::int64_t size_on_disk(file_storage const& fs); + +} // namespace aux +} // namespace libtorrent + +#endif // TORRENT_FILE_STORAGE_HPP_INCLUDED diff --git a/include/libtorrent/fingerprint.hpp b/include/libtorrent/fingerprint.hpp new file mode 100644 index 0000000..cadde82 --- /dev/null +++ b/include/libtorrent/fingerprint.hpp @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2003, 2006, 2009, 2013, 2016-2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FINGERPRINT_HPP_INCLUDED +#define TORRENT_FINGERPRINT_HPP_INCLUDED + +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/export.hpp" + +namespace libtorrent { + + // This is a utility function to produce a client ID fingerprint formatted to + // the most common convention. The fingerprint can be set via the + // ``peer_fingerprint`` setting, in settings_pack. + // + // The name string should contain exactly two characters. These are the + // characters unique to your client, used to identify it. Make sure not to + // clash with anybody else. Here are some taken id's: + // + // +----------+-----------------------+ + // | id chars | client | + // +==========+=======================+ + // | LT | libtorrent (default) | + // +----------+-----------------------+ + // | UT | uTorrent | + // +----------+-----------------------+ + // | UM | uTorrent Mac | + // +----------+-----------------------+ + // | qB | qBittorrent | + // +----------+-----------------------+ + // | BP | BitTorrent Pro | + // +----------+-----------------------+ + // | BT | BitTorrent | + // +----------+-----------------------+ + // | DE | Deluge | + // +----------+-----------------------+ + // | AZ | Azureus | + // +----------+-----------------------+ + // | TL | Tribler | + // +----------+-----------------------+ + // + // There's an informal directory of client id's here_. + // + // .. _here: http://wiki.theory.org/BitTorrentSpecification#peer_id + // + // The ``major``, ``minor``, ``revision`` and ``tag`` parameters are used to + // identify the version of your client. + TORRENT_EXPORT std::string generate_fingerprint(std::string name + , int major, int minor = 0, int revision = 0, int tag = 0); + + // The fingerprint class represents information about a client and its version. It is used + // to encode this information into the client's peer id. + struct TORRENT_DEPRECATED_EXPORT fingerprint + { + fingerprint(const char* id_string, int major, int minor, int revision, int tag); + +#if TORRENT_ABI_VERSION == 1 + // generates the actual string put in the peer-id, and return it. + std::string to_string() const; +#endif + + char name[2]; + int major_version; + int minor_version; + int revision_version; + int tag_version; + }; + +} + +#endif // TORRENT_FINGERPRINT_HPP_INCLUDED diff --git a/include/libtorrent/flags.hpp b/include/libtorrent/flags.hpp new file mode 100644 index 0000000..9175d8f --- /dev/null +++ b/include/libtorrent/flags.hpp @@ -0,0 +1,141 @@ +/* + +Copyright (c) 2017-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FLAGS_HPP_INCLUDED +#define TORRENT_FLAGS_HPP_INCLUDED + +#include // for enable_if +#include + +namespace libtorrent { + +struct bit_t +{ + explicit constexpr bit_t(int b) : m_bit_idx(b) {} + explicit constexpr operator int() const { return m_bit_idx; } +private: + int m_bit_idx; +}; + +constexpr bit_t operator ""_bit(unsigned long long int b) { return bit_t{static_cast(b)}; } + +namespace flags { + +template::value>::type> +struct bitfield_flag +{ + static_assert(std::is_unsigned::value + , "flags must use unsigned integers as underlying types"); + + using underlying_type = UnderlyingType; + + constexpr bitfield_flag(bitfield_flag const& rhs) noexcept = default; + constexpr bitfield_flag(bitfield_flag&& rhs) noexcept = default; + constexpr bitfield_flag() noexcept : m_val(0) {} + explicit constexpr bitfield_flag(UnderlyingType const val) noexcept : m_val(val) {} + constexpr bitfield_flag(bit_t const bit) noexcept : m_val(static_cast(UnderlyingType{1} << static_cast(bit))) {} +#if TORRENT_ABI_VERSION >= 2 + explicit constexpr operator UnderlyingType() const noexcept { return m_val; } +#else + constexpr operator UnderlyingType() const noexcept { return m_val; } +#endif + explicit constexpr operator bool() const noexcept { return m_val != 0; } + + static constexpr bitfield_flag all() + { + return bitfield_flag(static_cast(~UnderlyingType{0})); + } + + bool constexpr operator==(bitfield_flag const f) const noexcept + { return m_val == f.m_val; } + + bool constexpr operator!=(bitfield_flag const f) const noexcept + { return m_val != f.m_val; } + + bitfield_flag& operator|=(bitfield_flag const f) & noexcept + { + m_val |= f.m_val; + return *this; + } + + bitfield_flag& operator&=(bitfield_flag const f) & noexcept + { + m_val &= f.m_val; + return *this; + } + + bitfield_flag& operator^=(bitfield_flag const f) & noexcept + { + m_val ^= f.m_val; + return *this; + } + + constexpr friend bitfield_flag operator|(bitfield_flag const lhs, bitfield_flag const rhs) noexcept + { + return bitfield_flag(lhs.m_val | rhs.m_val); + } + + constexpr friend bitfield_flag operator&(bitfield_flag const lhs, bitfield_flag const rhs) noexcept + { + return bitfield_flag(lhs.m_val & rhs.m_val); + } + + constexpr friend bitfield_flag operator^(bitfield_flag const lhs, bitfield_flag const rhs) noexcept + { + return bitfield_flag(lhs.m_val ^ rhs.m_val); + } + + constexpr bitfield_flag operator~() const noexcept + { + // technically, m_val is promoted to int before applying operator~, which + // means the result may not fit into the underlying type again. So, + // explicitly cast it + return bitfield_flag(static_cast(~m_val)); + } + + bitfield_flag& operator=(bitfield_flag const& rhs) & noexcept = default; + bitfield_flag& operator=(bitfield_flag&& rhs) & noexcept = default; + +#if TORRENT_USE_IOSTREAM + friend std::ostream& operator<<(std::ostream& os, bitfield_flag val) + { return os << static_cast(val); } +#endif + +private: + UnderlyingType m_val; +}; + +} // flags +} // libtorrent + +#endif diff --git a/include/libtorrent/fwd.hpp b/include/libtorrent/fwd.hpp new file mode 100644 index 0000000..44a7223 --- /dev/null +++ b/include/libtorrent/fwd.hpp @@ -0,0 +1,323 @@ +/* + +Copyright (c) 2017-2018, Steven Siloti +Copyright (c) 2017-2021, Arvid Norberg +Copyright (c) 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FWD_HPP +#define TORRENT_FWD_HPP + +#include "libtorrent/config.hpp" + +namespace libtorrent { + +// include/libtorrent/add_torrent_params.hpp +TORRENT_VERSION_NAMESPACE_3 +struct add_torrent_params; +TORRENT_VERSION_NAMESPACE_3_END + +// include/libtorrent/alert.hpp +struct alert; + +// include/libtorrent/alert_types.hpp +struct dht_routing_bucket; +TORRENT_VERSION_NAMESPACE_3 +struct torrent_alert; +struct peer_alert; +struct tracker_alert; +struct torrent_removed_alert; +struct read_piece_alert; +struct file_completed_alert; +struct file_renamed_alert; +struct file_rename_failed_alert; +struct performance_alert; +struct state_changed_alert; +struct tracker_error_alert; +struct tracker_warning_alert; +struct scrape_reply_alert; +struct scrape_failed_alert; +struct tracker_reply_alert; +struct dht_reply_alert; +struct tracker_announce_alert; +struct hash_failed_alert; +struct peer_ban_alert; +struct peer_unsnubbed_alert; +struct peer_snubbed_alert; +struct peer_error_alert; +struct peer_connect_alert; +struct peer_disconnected_alert; +struct invalid_request_alert; +struct torrent_finished_alert; +struct piece_finished_alert; +struct request_dropped_alert; +struct block_timeout_alert; +struct block_finished_alert; +struct block_downloading_alert; +struct unwanted_block_alert; +struct storage_moved_alert; +struct storage_moved_failed_alert; +struct torrent_deleted_alert; +struct torrent_delete_failed_alert; +struct save_resume_data_alert; +struct save_resume_data_failed_alert; +struct torrent_paused_alert; +struct torrent_resumed_alert; +struct torrent_checked_alert; +struct url_seed_alert; +struct file_error_alert; +struct metadata_failed_alert; +struct metadata_received_alert; +struct udp_error_alert; +struct external_ip_alert; +struct listen_failed_alert; +struct listen_succeeded_alert; +struct portmap_error_alert; +struct portmap_alert; +struct portmap_log_alert; +struct fastresume_rejected_alert; +struct peer_blocked_alert; +struct dht_announce_alert; +struct dht_get_peers_alert; +struct cache_flushed_alert; +struct lsd_peer_alert; +struct trackerid_alert; +struct dht_bootstrap_alert; +struct torrent_error_alert; +struct torrent_need_cert_alert; +struct incoming_connection_alert; +struct add_torrent_alert; +struct state_update_alert; +struct session_stats_alert; +struct dht_error_alert; +struct dht_immutable_item_alert; +struct dht_mutable_item_alert; +struct dht_put_alert; +struct i2p_alert; +struct dht_outgoing_get_peers_alert; +struct log_alert; +struct torrent_log_alert; +struct peer_log_alert; +struct lsd_error_alert; +struct dht_lookup; +struct dht_stats_alert; +struct incoming_request_alert; +struct dht_log_alert; +struct dht_pkt_alert; +struct dht_get_peers_reply_alert; +struct dht_direct_response_alert; +struct picker_log_alert; +struct session_error_alert; +struct dht_live_nodes_alert; +struct session_stats_header_alert; +struct dht_sample_infohashes_alert; +struct block_uploaded_alert; +struct alerts_dropped_alert; +struct socks5_alert; +struct file_prio_alert; +TORRENT_VERSION_NAMESPACE_3_END +struct oversized_file_alert; +struct torrent_conflict_alert; + +// include/libtorrent/announce_entry.hpp +TORRENT_VERSION_NAMESPACE_2 +struct announce_infohash; +struct announce_endpoint; +struct announce_entry; +TORRENT_VERSION_NAMESPACE_2_END + +// include/libtorrent/bdecode.hpp +struct bdecode_node; + +// include/libtorrent/bitfield.hpp +struct bitfield; + +// include/libtorrent/client_data.hpp +struct client_data_t; + +// include/libtorrent/create_torrent.hpp +struct create_torrent; + +// include/libtorrent/disk_buffer_holder.hpp +struct buffer_allocator_interface; +struct disk_buffer_holder; + +// include/libtorrent/disk_interface.hpp +struct open_file_state; +struct disk_interface; +struct storage_holder; + +// include/libtorrent/disk_observer.hpp +struct disk_observer; + +// include/libtorrent/entry.hpp +class entry; + +// include/libtorrent/error_code.hpp +struct storage_error; + +// include/libtorrent/extensions.hpp +TORRENT_VERSION_NAMESPACE_3 +struct plugin; +TORRENT_VERSION_NAMESPACE_3_END +struct torrent_plugin; +struct peer_plugin; +struct crypto_plugin; + +// include/libtorrent/file_storage.hpp +struct file_slice; +class file_storage; + +// include/libtorrent/hasher.hpp +TORRENT_CRYPTO_NAMESPACE +class hasher; +class hasher256; +TORRENT_CRYPTO_NAMESPACE_END + +// include/libtorrent/info_hash.hpp +struct info_hash_t; + +// include/libtorrent/ip_filter.hpp +struct ip_filter; +class port_filter; + +// include/libtorrent/kademlia/dht_state.hpp +namespace dht { +struct dht_state; +} + +// include/libtorrent/kademlia/dht_storage.hpp +namespace dht { +struct dht_storage_counters; +} +namespace dht { +struct dht_storage_interface; +} + +// include/libtorrent/peer_class.hpp +struct peer_class_info; + +// include/libtorrent/peer_class_type_filter.hpp +struct peer_class_type_filter; + +// include/libtorrent/peer_connection_handle.hpp +struct peer_connection_handle; +struct bt_peer_connection_handle; + +// include/libtorrent/peer_info.hpp +TORRENT_VERSION_NAMESPACE_2 +struct peer_info; +TORRENT_VERSION_NAMESPACE_2_END + +// include/libtorrent/peer_request.hpp +struct peer_request; + +// include/libtorrent/performance_counters.hpp +struct counters; + +// include/libtorrent/piece_block.hpp +struct piece_block; + +// include/libtorrent/session.hpp +struct session_proxy; +struct session; + +// include/libtorrent/session_handle.hpp +struct session_handle; + +// include/libtorrent/session_params.hpp +TORRENT_VERSION_NAMESPACE_3 +struct session_params; +TORRENT_VERSION_NAMESPACE_3_END + +// include/libtorrent/session_stats.hpp +struct stats_metric; + +// include/libtorrent/settings_pack.hpp +struct settings_interface; +struct settings_pack; + +// include/libtorrent/storage_defs.hpp +struct storage_params; + +// include/libtorrent/torrent_handle.hpp +struct block_info; +struct partial_piece_info; +struct torrent_handle; + +// include/libtorrent/torrent_info.hpp +struct web_seed_entry; +struct load_torrent_limits; +TORRENT_VERSION_NAMESPACE_3 +class torrent_info; +TORRENT_VERSION_NAMESPACE_3_END + +// include/libtorrent/torrent_status.hpp +TORRENT_VERSION_NAMESPACE_3 +struct torrent_status; +TORRENT_VERSION_NAMESPACE_3_END + +#if TORRENT_ABI_VERSION <= 2 + +// include/libtorrent/alert_types.hpp +TORRENT_VERSION_NAMESPACE_3 +struct torrent_added_alert; +struct stats_alert; +struct anonymous_mode_alert; +struct mmap_cache_alert; +TORRENT_VERSION_NAMESPACE_3_END + +// include/libtorrent/file_storage.hpp +struct file_entry; + +// include/libtorrent/fingerprint.hpp +struct fingerprint; + +// include/libtorrent/kademlia/dht_settings.hpp +namespace dht { +struct dht_settings; +} + +// include/libtorrent/session_settings.hpp +struct pe_settings; + +// include/libtorrent/session_status.hpp +struct utp_status; +struct session_status; + +#endif // TORRENT_ABI_VERSION + + using file_layout = file_storage; + +} + +namespace lt = libtorrent; + +#endif // TORRENT_FWD_HPP diff --git a/include/libtorrent/gzip.hpp b/include/libtorrent/gzip.hpp new file mode 100644 index 0000000..f8f96c1 --- /dev/null +++ b/include/libtorrent/gzip.hpp @@ -0,0 +1,137 @@ +/* + +Copyright (c) 2008-2009, 2014-2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_GZIP_HPP_INCLUDED +#define TORRENT_GZIP_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/span.hpp" + +#include + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT void inflate_gzip( + span in + , std::vector& buffer + , int maximum_size + , error_code& ec); + + // get the ``error_category`` for zip errors + TORRENT_EXPORT boost::system::error_category& gzip_category(); + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + inline boost::system::error_category& get_gzip_category() + { return gzip_category(); } +#endif + +namespace gzip_errors { + // libtorrent uses boost.system's ``error_code`` class to represent errors. libtorrent has + // its own error category get_gzip_category() with the error codes defined by error_code_enum. + enum error_code_enum + { + // Not an error + no_error = 0, + + // the supplied gzip buffer has invalid header + invalid_gzip_header, + + // the gzip buffer would inflate to more bytes than the specified + // maximum size, and was rejected. + inflated_data_too_large, + + // available inflate data did not terminate + data_did_not_terminate, + + // output space exhausted before completing inflate + space_exhausted, + + // invalid block type (type == 3) + invalid_block_type, + + // stored block length did not match one's complement + invalid_stored_block_length, + + // dynamic block code description: too many length or distance codes + too_many_length_or_distance_codes, + + // dynamic block code description: code lengths codes incomplete + code_lengths_codes_incomplete, + + // dynamic block code description: repeat lengths with no first length + repeat_lengths_with_no_first_length, + + // dynamic block code description: repeat more than specified lengths + repeat_more_than_specified_lengths, + + // dynamic block code description: invalid literal/length code lengths + invalid_literal_length_code_lengths, + + // dynamic block code description: invalid distance code lengths + invalid_distance_code_lengths, + + // invalid literal/length or distance code in fixed or dynamic block + invalid_literal_code_in_block, + + // distance is too far back in fixed or dynamic block + distance_too_far_back_in_block, + + // an unknown error occurred during gzip inflation + unknown_gzip_error, + + // the number of error codes + error_code_max + }; + + // hidden + TORRENT_EXPORT boost::system::error_code make_error_code(error_code_enum e); +} // namespace gzip_errors + +} // namespace libtorrent + +namespace boost { +namespace system { + +template<> +struct is_error_code_enum +{ static const bool value = true; }; + +template<> +struct is_error_condition_enum +{ static const bool value = true; }; + +} +} + +#endif diff --git a/include/libtorrent/hash_picker.hpp b/include/libtorrent/hash_picker.hpp new file mode 100644 index 0000000..ed3e95f --- /dev/null +++ b/include/libtorrent/hash_picker.hpp @@ -0,0 +1,246 @@ +/* + +Copyright (c) 2017, BitTorrent Inc. +Copyright (c) 2019-2021, Arvid Norberg +Copyright (c) 2019, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. +* Neither the name of the author nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HASH_PICKER_HPP_INCLUDED +#define TORRENT_HASH_PICKER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/merkle_tree.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/index_range.hpp" +#include +#include + +namespace libtorrent +{ + struct torrent_peer; + + struct set_block_hash_result + { + enum class result + { + // hash is verified + success, + // hash cannot be verified yet + unknown, + // hash conflict in leaf node + block_hash_failed, + // hash conflict in a parent node + piece_hash_failed + }; + + explicit set_block_hash_result(result s) : status(s), first_verified_block(0), num_verified(0) {} + set_block_hash_result(result st, int first_block, int num) : status(st), first_verified_block(first_block), num_verified(num) {} + + index_range piece_range( + piece_index_t const piece + , int const blocks_per_piece) const + { + using delta = piece_index_t::diff_type; + piece_index_t const start = piece + delta(first_verified_block / blocks_per_piece); + piece_index_t const end = start + delta(num_verified / blocks_per_piece); + return {start, end}; + } + + static set_block_hash_result success(int first_block, int num) { return set_block_hash_result(result::success, first_block, num); } + static set_block_hash_result unknown() { return set_block_hash_result(result::unknown); } + static set_block_hash_result block_hash_failed() { return set_block_hash_result(result::block_hash_failed); } + static set_block_hash_result piece_hash_failed(int first_block, int num) { return set_block_hash_result(result::piece_hash_failed, first_block, num); } + + result status; + // if status is success, this will hold the index of the first verified + // block hash as an offset from the index of the first block in the piece + int first_verified_block; + int num_verified; + }; + + struct add_hashes_result + { + explicit add_hashes_result(bool const v) : valid(v) {} + + bool valid; + // the vector contains the block indices (within the piece) that failed + // the hash check + std::vector>> hash_failed; + std::vector hash_passed; + }; + + struct node_index + { + node_index(file_index_t f, std::int32_t n) : file(f), node(n) {} + bool operator==(node_index const& o) const { return file == o.file && node == o.node; } + file_index_t file; + std::int32_t node; + }; + + // the hash request represents a range of hashes in the merkle hash tree for + // a specific file ('file'). + struct TORRENT_EXTRA_EXPORT hash_request + { + hash_request() = default; + hash_request(file_index_t const f, int const b, int const i, int const c, int const p) + : file(f), base(b), index(i), count(c), proof_layers(p) + {} + + hash_request(hash_request const&) = default; + hash_request& operator=(hash_request const& o) = default; + + bool operator==(hash_request const& o) const + { + return file == o.file && base == o.base && index == o.index && count == o.count + && proof_layers == o.proof_layers; + } + + file_index_t file{0}; + // indicates which *level* of the tree we're referring to. 0 means the + // leaf level. + int base = 0; + // the index of the first hash at the specified level. + int index = 0; + // the number of hashes in the range + int count = 0; + int proof_layers = 0; + }; + + // validates the hash_request, to ensure its invariant as well as matching + // the torrent's file_storage and the number of hashes accompanying the + // request + TORRENT_EXTRA_EXPORT + bool validate_hash_request(hash_request const& hr, file_storage const& fs); + + class TORRENT_EXTRA_EXPORT hash_picker + { + public: + hash_picker(file_storage const& files + , aux::vector& trees); + + hash_request pick_hashes(typed_bitfield const& pieces); + + add_hashes_result add_hashes(hash_request const& req, span hashes); + // TODO: support batched adding of block hashes for reduced overhead? + set_block_hash_result set_block_hash(piece_index_t piece, int offset, sha256_hash const& h); + void hashes_rejected(hash_request const& req); + void verify_block_hashes(piece_index_t index); + + // do we know the piece layer hash for a piece + bool have_hash(piece_index_t index) const; + // do we know all the block hashes for a file? + bool have_all(file_index_t file) const; + bool have_all() const; + bool piece_verified(piece_index_t piece) const; + + int piece_layer() const { return m_piece_layer; } + + private: + // returns the number of proof layers needed to verify the node's hash + int layers_to_verify(node_index idx) const; + int file_num_layers(file_index_t idx) const; + + struct piece_hash_request + { + time_point last_request = min_time(); + int num_requests = 0; + bool have = false; + }; + + struct priority_block_request + { + priority_block_request(file_index_t const f, int const b) + : file(f), block(b) {} + file_index_t file; + int block; + int num_requests = 0; + bool operator==(priority_block_request const& o) const + { return file == o.file && block == o.block; } + bool operator!=(priority_block_request const& o) const + { return !(*this == o); } + bool operator<(priority_block_request const& o) const + { return num_requests < o.num_requests; } + }; + + struct piece_block_request + { + piece_block_request(file_index_t const f, piece_index_t::diff_type const p) : file(f), piece(p) {} + file_index_t file; + // the piece from the start of the file + piece_index_t::diff_type piece; + time_point last_request = min_time(); + int num_requests = 0; + bool operator==(piece_block_request const& o) const + { return file == o.file && piece == o.piece; } + bool operator!=(piece_block_request const& o) const + { return !(*this == o); } + bool operator<(piece_block_request const& o) const + { return num_requests < o.num_requests; } + }; + + file_storage const& m_files; + aux::vector& m_merkle_trees; + + // information about every 512-piece span of each file. We request hashes + // for 512 pieces at a time + aux::vector, file_index_t> m_piece_hash_requested; + + // this is for a future per-block request feature +#if 0 + // blocks are only added to this list if there is a time critical block which + // has been downloaded but we don't have its hash or if the initial request + // for the hash was rejected + // this block hash will be requested from every peer possible until the hash + // is received + // the vector is sorted by the number of requests sent for each block + aux::vector m_priority_block_requests; +#endif + + // when a piece fails hash check a request is queued to download the piece's + // block hashes + aux::vector m_piece_block_requests; + + // this is the number of tree levels in a piece. if the piece size is 16 + // kiB, this is 0, since there is no tree per piece. If the piece size is + // 32 kiB, it's 1, and so on. + int const m_piece_layer; + + // this is the number of tree layers for a 512-piece range, which is + // the granularity with which we send hash requests. The number of layers + // all the way down the the block level. + int const m_piece_tree_root_layer; + }; +} // namespace libtorrent + +#endif // TORRENT_HASH_PICKER_HPP_INCLUDED diff --git a/include/libtorrent/hasher.hpp b/include/libtorrent/hasher.hpp new file mode 100644 index 0000000..28e293f --- /dev/null +++ b/include/libtorrent/hasher.hpp @@ -0,0 +1,185 @@ +/* + +Copyright (c) 2003-2004, 2007, 2009, 2012-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, 2019, Andrei Kurushin +Copyright (c) 2017, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HASHER_HPP_INCLUDED +#define TORRENT_HASHER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/span.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#ifdef TORRENT_USE_LIBGCRYPT +#include + +#elif TORRENT_USE_COMMONCRYPTO +#include + +#elif TORRENT_USE_CNG +#include "libtorrent/aux_/win_cng.hpp" + +#elif TORRENT_USE_CRYPTOAPI +#include "libtorrent/aux_/win_crypto_provider.hpp" + +#if !TORRENT_USE_CRYPTOAPI_SHA_512 +#include "libtorrent/sha256.hpp" +#endif + +#elif defined TORRENT_USE_LIBCRYPTO + +extern "C" { +#include +} + +#else +#include "libtorrent/sha1.hpp" +#include "libtorrent/sha256.hpp" +#endif +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { +TORRENT_CRYPTO_NAMESPACE + + // this is a SHA-1 hash class. + // + // You use it by first instantiating it, then call ``update()`` to feed it + // with data. i.e. you don't have to keep the entire buffer of which you want to + // create the hash in memory. You can feed the hasher parts of it at a time. When + // You have fed the hasher with all the data, you call ``final()`` and it + // will return the sha1-hash of the data. + // + // The constructor that takes a ``char const*`` and an integer will construct the + // sha1 context and feed it the data passed in. + // + // If you want to reuse the hasher object once you have created a hash, you have to + // call ``reset()`` to reinitialize it. + // + // The built-in software version of sha1-algorithm was implemented + // by Steve Reid and released as public domain. + // For more info, see ``src/sha1.cpp``. + class TORRENT_EXPORT hasher + { + public: + + hasher(); + + // this is the same as default constructing followed by a call to + // ``update(data, len)``. + hasher(char const* data, int len); + explicit hasher(span data); + hasher(hasher const&); + hasher& operator=(hasher const&) &; + + // append the following bytes to what is being hashed + hasher& update(span data); + hasher& update(char const* data, int len); + + // returns the SHA-1 digest of the buffers previously passed to + // update() and the hasher constructor. + sha1_hash final(); + + // restore the hasher state to be as if the hasher has just been + // default constructed. + void reset(); + + // hidden + ~hasher(); + + private: + +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_hd_t m_context; +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA1_CTX m_context; +#elif TORRENT_USE_CNG + aux::cng_hash m_context; +#elif TORRENT_USE_CRYPTOAPI + aux::crypt_hash m_context; +#elif defined TORRENT_USE_LIBCRYPTO + SHA_CTX m_context; +#else + sha1_ctx m_context; +#endif + }; + + class TORRENT_EXPORT hasher256 + { + public: + hasher256(); + + // this is the same as default constructing followed by a call to + // ``update(data, len)``. + hasher256(char const* data, int len); + explicit hasher256(span data); + hasher256(hasher256 const&); + hasher256& operator=(hasher256 const&) &; + + // append the following bytes to what is being hashed + hasher256& update(span data); + hasher256& update(char const* data, int len); + + // returns the SHA-1 digest of the buffers previously passed to + // update() and the hasher constructor. + sha256_hash final(); + + // restore the hasher state to be as if the hasher has just been + // default constructed. + void reset(); + + ~hasher256(); + + private: +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_hd_t m_context; +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA256_CTX m_context; +#elif TORRENT_USE_CNG + aux::cng_hash m_context; +#elif TORRENT_USE_CRYPTOAPI_SHA_512 + aux::crypt_hash m_context; +#elif defined TORRENT_USE_LIBCRYPTO + SHA256_CTX m_context; +#else + sha256_ctx m_context; +#endif + }; + +TORRENT_CRYPTO_NAMESPACE_END +} + +#endif // TORRENT_HASHER_HPP_INCLUDED diff --git a/include/libtorrent/hex.hpp b/include/libtorrent/hex.hpp new file mode 100644 index 0000000..140f944 --- /dev/null +++ b/include/libtorrent/hex.hpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2004, 2009, 2013, 2015-2020, Arvid Norberg +Copyright (c) 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HEX_HPP_INCLUDED +#define TORRENT_HEX_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/span.hpp" + +#include + +namespace libtorrent { + +namespace aux { + + TORRENT_EXTRA_EXPORT int hex_to_int(char in); + TORRENT_EXTRA_EXPORT bool is_hex(span in); + +#if TORRENT_ABI_VERSION == 1 +#define TORRENT_CONDITIONAL_EXPORT TORRENT_EXPORT +#else +#define TORRENT_CONDITIONAL_EXPORT TORRENT_EXTRA_EXPORT +#endif + + // The overload taking a ``std::string`` converts (binary) the string ``s`` + // to hexadecimal representation and returns it. + // The overload taking a ``char const*`` and a length converts the binary + // buffer [``in``, ``in`` + len) to hexadecimal and prints it to the buffer + // ``out``. The caller is responsible for making sure the buffer pointed to + // by ``out`` is large enough, i.e. has at least len * 2 bytes of space. + TORRENT_CONDITIONAL_EXPORT std::string to_hex(span s); + TORRENT_CONDITIONAL_EXPORT void to_hex(span in, char* out); + TORRENT_CONDITIONAL_EXPORT void to_hex(char const* in, int len, char* out); + + // converts the buffer [``in``, ``in`` + len) from hexadecimal to + // binary. The binary output is written to the buffer pointed to + // by ``out``. The caller is responsible for making sure the buffer + // at ``out`` has enough space for the result to be written to, i.e. + // (len + 1) / 2 bytes. + TORRENT_CONDITIONAL_EXPORT bool from_hex(span in, char* out); + +#undef TORRENT_CONDITIONAL_EXPORT + +} // namespace aux + +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // deprecated in 1.2 + TORRENT_DEPRECATED + inline void to_hex(char const* in, int len, char* out) + { aux::to_hex({in, len}, out); } + TORRENT_DEPRECATED + inline std::string to_hex(std::string const& s) + { return aux::to_hex(s); } + TORRENT_DEPRECATED + inline bool from_hex(char const *in, int len, char* out) + { return aux::from_hex({in, len}, out); } + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif +} // namespace libtorrent + +#endif // TORRENT_HEX_HPP_INCLUDED diff --git a/include/libtorrent/http_connection.hpp b/include/libtorrent/http_connection.hpp new file mode 100644 index 0000000..f59fb60 --- /dev/null +++ b/include/libtorrent/http_connection.hpp @@ -0,0 +1,262 @@ +/* + +Copyright (c) 2017, Steven Siloti +Copyright (c) 2007-2020, 2022, Arvid Norberg +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2016-2017, 2020, Alden Torres +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_CONNECTION +#define TORRENT_HTTP_CONNECTION + +#include +#include +#include + +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/http_parser.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/i2p_stream.hpp" +#include "libtorrent/aux_/socket_type.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/resolver_interface.hpp" +#include "libtorrent/optional.hpp" +#include "libtorrent/ssl.hpp" + +namespace libtorrent { + +struct http_connection; +namespace aux { struct resolver_interface; } + +struct close_visitor; + +// internal +constexpr int default_max_bottled_buffer_size = 2 * 1024 * 1024; + +using http_handler = std::function data, http_connection&)>; + +using http_connect_handler = std::function; + +using http_filter_handler = std::function&)>; +using hostname_filter_handler = std::function; + +struct bind_info_t +{ + std::string device; + address ip; + bool operator==(bind_info_t const& rhs) const + { + return device == rhs.device && ip == rhs.ip; + } +}; + +// when bottled, the last two arguments to the handler +// will always be 0 +struct TORRENT_EXTRA_EXPORT http_connection + : std::enable_shared_from_this +{ + friend struct close_visitor; + + http_connection(io_context& ios + , aux::resolver_interface& resolver + , http_handler handler + , bool bottled + , int max_bottled_buffer_size + , http_connect_handler ch + , http_filter_handler fh + , hostname_filter_handler hfh +#if TORRENT_USE_SSL + , ssl::context* ssl_ctx +#endif + ); + + // non-copyable + http_connection(http_connection const&) = delete; + http_connection& operator=(http_connection const&) = delete; + + virtual ~http_connection(); + + void rate_limit(int limit); + + int rate_limit() const + { return m_rate_limit; } + + std::string m_sendbuffer; + + void get(std::string const& url, time_duration timeout = seconds(30) + , aux::proxy_settings const* ps = nullptr, int handle_redirects = 5 + , std::string const& user_agent = std::string() + , boost::optional const& bind_addr = boost::none + , aux::resolver_flags resolve_flags = aux::resolver_flags{}, std::string const& auth_ = std::string() +#if TORRENT_USE_I2P + , i2p_connection* i2p_conn = nullptr +#endif + ); + + void start(std::string const& hostname, int port + , time_duration timeout, aux::proxy_settings const* ps = nullptr + , bool ssl = false, int handle_redirect = 5 + , boost::optional const& bind_addr = boost::none + , aux::resolver_flags resolve_flags = aux::resolver_flags{} +#if TORRENT_USE_I2P + , i2p_connection* i2p_conn = nullptr +#endif + ); + + void close(bool force = false); + + aux::socket_type const& socket() const { return *m_sock; } + + std::vector const& endpoints() const { return m_endpoints; } + + std::string const& url() const { return m_url; } + +private: + +#if TORRENT_USE_I2P + void connect_i2p_tracker(char const* destination); + void on_i2p_resolve(error_code const& e + , char const* destination); +#endif + void on_resolve(error_code const& e, std::vector
    const& addresses); + void connect(); + void on_connect(error_code const& e); + void on_write(error_code const& e); + void on_read(error_code const& e, std::size_t bytes_transferred); + static void on_timeout(std::weak_ptr p + , error_code const& e); + void on_assign_bandwidth(error_code const& e); + + void callback(error_code e, span data = {}); + + aux::vector m_recvbuffer; + io_context& m_ios; + + std::string m_hostname; + std::string m_url; + std::string m_user_agent; + + aux::vector m_endpoints; + + // if the current connection attempt fails, we'll connect to the + // endpoint with this index (in m_endpoints) next + int m_next_ep; + + boost::optional m_sock; + +#if TORRENT_USE_SSL + ssl::context* m_ssl_ctx; +#endif + +#if TORRENT_USE_I2P + i2p_connection* m_i2p_conn; +#endif + aux::resolver_interface& m_resolver; + + http_parser m_parser; + http_handler m_handler; + http_connect_handler m_connect_handler; + http_filter_handler m_filter_handler; + hostname_filter_handler m_hostname_filter_handler; + deadline_timer m_timer; + + time_duration m_completion_timeout; + + // the timer fires every 250 millisecond as long + // as all the quota was used. + deadline_timer m_limiter_timer; + + time_point m_last_receive; + time_point m_start_time; + + // specifies whether or not the connection is + // configured to use a proxy + aux::proxy_settings m_proxy; + + // the address and/or device to bind to. unset means do not bind + boost::optional m_bind_addr; + + // if username password was passed in, remember it in case we need to + // re-issue the request for a redirect + std::string m_auth; + + int m_read_pos; + + // the number of redirects to follow (in sequence) + int m_redirects; + + // maximum size of bottled buffer + int m_max_bottled_buffer_size; + + // the current download limit, in bytes per second + // 0 is unlimited. + int m_rate_limit; + + // the number of bytes we are allowed to receive + int m_download_quota; + + // used for DNS lookups + aux::resolver_flags m_resolve_flags; + + std::uint16_t m_port; + + // bottled means that the handler is called once, when + // everything is received (and buffered in memory). + // non bottled means that once the headers have been + // received, data is streamed to the handler + bool m_bottled; + + // set to true the first time the handler is called + bool m_called = false; + + // only hand out new quota 4 times a second if the + // quota is 0. If it isn't 0 wait for it to reach + // 0 and continue to hand out quota at that time. + bool m_limiter_timer_active = false; + + // true if the connection is using ssl + bool m_ssl = false; + + bool m_abort = false; + + // true while waiting for an async_connect + bool m_connecting = false; + + // true while resolving hostname + bool m_resolving_host = false; +}; + +} + +#endif diff --git a/include/libtorrent/http_parser.hpp b/include/libtorrent/http_parser.hpp new file mode 100644 index 0000000..f26faa5 --- /dev/null +++ b/include/libtorrent/http_parser.hpp @@ -0,0 +1,168 @@ +/* + +Copyright (c) 2008-2017, 2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_PARSER_HPP_INCLUDED +#define TORRENT_HTTP_PARSER_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/time.hpp" // for seconds32 +#include "libtorrent/optional.hpp" +#include "libtorrent/aux_/strview_less.hpp" + +namespace libtorrent { + + // return true if the status code is 200, 206, or in the 300-400 range + TORRENT_EXTRA_EXPORT bool is_ok_status(int http_status); + + // return true if the status code is a redirect + TORRENT_EXTRA_EXPORT bool is_redirect(int http_status); + + TORRENT_EXTRA_EXPORT std::string resolve_redirect_location(std::string referrer + , std::string location); + + class TORRENT_EXTRA_EXPORT http_parser + { + public: + enum flags_t { dont_parse_chunks = 1 }; + explicit http_parser(int flags = 0); + ~http_parser(); + std::string const& header(string_view key) const; + boost::optional header_duration(string_view key) const; + std::string const& protocol() const { return m_protocol; } + int status_code() const { return m_status_code; } + std::string const& method() const { return m_method; } + std::string const& path() const { return m_path; } + std::string const& message() const { return m_server_message; } + span get_body() const; + bool header_finished() const { return m_state == read_body; } + bool finished() const { return m_finished; } + std::tuple incoming(span recv_buffer + , bool& error); + int body_start() const { return m_body_start_pos; } + std::int64_t content_length() const { return m_content_length; } + std::pair content_range() const + { return std::make_pair(m_range_start, m_range_end); } + + // returns true if this response is using chunked encoding. + // in this case the body is split up into chunks. You need + // to call parse_chunk_header() for each chunk, starting with + // the start of the body. + bool chunked_encoding() const { return m_chunked_encoding; } + + // removes the chunk headers from the supplied buffer. The buffer + // must be the stream received from the http server this parser + // instanced parsed. It will use the internal chunk list to determine + // where the chunks are in the buffer. It returns the new length of + // the buffer + span collapse_chunk_headers(span buffer) const; + + // returns false if the buffer doesn't contain a complete + // chunk header. In this case, call the function again with + // a bigger buffer once more bytes have been received. + // chunk_size is filled in with the number of bytes in the + // chunk that follows. 0 means the response terminated. In + // this case there might be additional headers in the parser + // object. + // header_size is filled in with the number of bytes the header + // itself was. Skip this number of bytes to get to the actual + // chunk data. + // if the function returns false, the chunk size and header + // size may still have been modified, but their values are + // undefined + bool parse_chunk_header(span buf + , std::int64_t* chunk_size, int* header_size); + + // reset the whole state and start over + void reset(); + + bool connection_close() const { return m_connection_close; } + + std::multimap const& headers() const { return m_header; } + std::vector> const& chunks() const { return m_chunked_ranges; } + + private: + std::int64_t m_recv_pos = 0; + std::string m_method; + std::string m_path; + std::string m_protocol; + std::string m_server_message; + + std::int64_t m_content_length = -1; + std::int64_t m_range_start = -1; + std::int64_t m_range_end = -1; + + std::multimap m_header; + span m_recv_buffer; + // contains offsets of the first and one-past-end of + // each chunked range in the response + std::vector> m_chunked_ranges; + + // while reading a chunk, this is the offset where the + // current chunk will end (it refers to the first character + // in the chunk tail header or the next chunk header) + std::int64_t m_cur_chunk_end = -1; + + int m_status_code = -1; + + // the sum of all chunk headers read so far + int m_chunk_header_size = 0; + + int m_partial_chunk_header = 0; + + // controls some behaviors of the parser + int m_flags; + + int m_body_start_pos = 0; + + enum { read_status, read_header, read_body, error_state } m_state = read_status; + + // this is true if the server is HTTP/1.0 or + // if it sent "connection: close" + bool m_connection_close = false; + bool m_chunked_encoding = false; + bool m_finished = false; + }; + +} + +#endif // TORRENT_HTTP_PARSER_HPP_INCLUDED diff --git a/include/libtorrent/http_seed_connection.hpp b/include/libtorrent/http_seed_connection.hpp new file mode 100644 index 0000000..3d269ac --- /dev/null +++ b/include/libtorrent/http_seed_connection.hpp @@ -0,0 +1,116 @@ +/* + +Copyright (c) 2008-2010, 2012, 2014-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_SEED_CONNECTION_HPP_INCLUDED +#define TORRENT_HTTP_SEED_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/web_connection_base.hpp" +#include "libtorrent/piece_block_progress.hpp" + +namespace libtorrent { + + struct torrent; + struct peer_request; + + class TORRENT_EXTRA_EXPORT http_seed_connection + : public web_connection_base + { + friend struct invariant_access; + public: + + // this is the constructor where the we are the active part. + // The peer_connection should handshake and verify that the + // other end has the correct id + http_seed_connection(peer_connection_args& pack + , web_seed_t& web); + + connection_type type() const override + { return connection_type::http_seed; } + + // called from the main loop when this connection has any + // work to do. + void on_receive(error_code const& error + , std::size_t bytes_transferred) override; + + void on_connected() override; + + std::string const& url() const override { return m_url; } + + void get_specific_peer_info(peer_info& p) const override; + void disconnect(error_code const& ec, operation_t op + , disconnect_severity_t error = peer_connection_interface::normal) override; + + void write_request(peer_request const& r) override; + + private: + + // returns the block currently being + // downloaded. And the progress of that + // block. If the peer isn't downloading + // a piece for the moment, the boost::optional + // will be invalid. + piece_block_progress downloading_piece_progress() const override; + + void disable(error_code const& ec); + + // this is const since it's used as a key in the web seed list in the torrent + // if it's changed referencing back into that list will fail + const std::string m_url; + + web_seed_t* m_web; + + // the number of bytes left to receive of the response we're + // currently parsing + std::int64_t m_response_left; + + // this is the offset inside the current receive + // buffer where the next chunk header will be. + // this is updated for each chunk header that's + // parsed. It does not necessarily point to a valid + // offset in the receive buffer, if we haven't received + // it yet. This offset never includes the HTTP header + std::int64_t m_chunk_pos; + + // this is the number of bytes we've already received + // from the next chunk header we're waiting for + int m_partial_chunk_header; + }; +} + +#endif // TORRENT_WEB_PEER_CONNECTION_HPP_INCLUDED diff --git a/include/libtorrent/http_stream.hpp b/include/libtorrent/http_stream.hpp new file mode 100644 index 0000000..9151dce --- /dev/null +++ b/include/libtorrent/http_stream.hpp @@ -0,0 +1,230 @@ +/* + +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2007, 2010, 2015, 2019, 2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_STREAM_HPP_INCLUDED +#define TORRENT_HTTP_STREAM_HPP_INCLUDED + +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/aux_/escape_string.hpp" // for base64encode +#include "libtorrent/socket_io.hpp" // for print_endpoint + +namespace libtorrent { + +class http_stream : public proxy_base +{ +public: + + explicit http_stream(io_context& io_context) + : proxy_base(io_context) + , m_no_connect(false) + {} + + void set_no_connect(bool c) { m_no_connect = c; } + + void set_username(std::string const& user + , std::string const& password) + { + m_user = user; + m_password = password; + } + + void set_dst_name(std::string const& host) + { + m_dst_name = host; + } + + void close(error_code& ec) + { + m_dst_name.clear(); + proxy_base::close(ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void close() + { + m_dst_name.clear(); + proxy_base::close(); + } +#endif + + template + void async_connect(endpoint_type const& endpoint, Handler const& handler) + { + m_remote_endpoint = endpoint; + + // the connect is split up in the following steps: + // 1. resolve name of proxy server + // 2. connect to proxy server + // 3. send HTTP CONNECT method and possibly username+password + // 4. read CONNECT response + + m_resolver.async_resolve(m_hostname, to_string(m_port).data(), wrap_allocator( + [this](error_code const& ec, tcp::resolver::results_type ips, Handler hn) { + name_lookup(ec, std::move(ips), std::move(hn)); + }, std::move(handler))); + } + +private: + + template + void name_lookup(error_code const& e, tcp::resolver::results_type ips + , Handler h) + { + if (handle_error(e, h)) return; + + auto i = ips.begin(); + m_sock.async_connect(i->endpoint(), wrap_allocator( + [this](error_code const& ec, Handler hn) { + connected(ec, std::move(hn)); + }, std::move(h))); + } + + template + void connected(error_code const& e, Handler h) + { + if (handle_error(e, h)) return; + + using namespace libtorrent::aux; + + if (m_no_connect) + { + std::vector().swap(m_buffer); + std::move(h)(e); + return; + } + + // send CONNECT + std::back_insert_iterator> p(m_buffer); + std::string const endpoint = print_endpoint(m_remote_endpoint); + write_string("CONNECT " + endpoint + " HTTP/1.0\r\n", p); + if (!m_user.empty()) + { + write_string("Proxy-Authorization: Basic " + base64encode( + m_user + ":" + m_password) + "\r\n", p); + } + write_string("\r\n", p); + async_write(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake1(ec, std::move(hn)); + }, std::move(h))); + } + + template + void handshake1(error_code const& e, Handler h) + { + if (handle_error(e, h)) return; + + // read one byte from the socket + m_buffer.resize(1); + async_read(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake2(ec, std::move(hn)); + }, std::move(h))); + } + + template + void handshake2(error_code const& e, Handler h) + { + if (handle_error(e, h)) return; + + std::size_t const read_pos = m_buffer.size(); + // look for \n\n and \r\n\r\n + // both of which means end of http response header + bool found_end = false; + if (read_pos > 2 && m_buffer[read_pos - 1] == '\n') + { + if (m_buffer[read_pos - 2] == '\n') + { + found_end = true; + } + else if (read_pos > 4 + && m_buffer[read_pos - 2] == '\r' + && m_buffer[read_pos - 3] == '\n' + && m_buffer[read_pos - 4] == '\r') + { + found_end = true; + } + } + + if (found_end) + { + m_buffer.push_back(0); + char const* status = std::strchr(m_buffer.data(), ' '); + if (status == nullptr) + { + h(boost::asio::error::operation_not_supported); + error_code ec; + close(ec); + return; + } + + status++; + int const code = std::atoi(status); + if (code != 200) + { + h(boost::asio::error::operation_not_supported); + error_code ec; + close(ec); + return; + } + + h(e); + std::vector().swap(m_buffer); + return; + } + + // read another byte from the socket + m_buffer.resize(read_pos + 1); + async_read(m_sock, boost::asio::buffer(m_buffer.data() + read_pos, 1), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake2(ec, std::move(hn)); + }, std::move(h))); + } + + // send and receive buffer + std::vector m_buffer; + // proxy authentication + std::string m_user; + std::string m_password; + std::string m_dst_name; + + // this is true if the connection is HTTP based and + // want to talk directly to the proxy + bool m_no_connect; +}; + +} + +#endif diff --git a/include/libtorrent/http_tracker_connection.hpp b/include/libtorrent/http_tracker_connection.hpp new file mode 100644 index 0000000..0c775af --- /dev/null +++ b/include/libtorrent/http_tracker_connection.hpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2004, 2006-2009, 2012, 2014-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, 2018, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED +#define TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED + +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/tracker_manager.hpp" // for tracker_connection + +namespace libtorrent { + + class tracker_manager; + struct http_connection; + class http_parser; + struct bdecode_node; + struct peer_entry; + + class TORRENT_EXTRA_EXPORT http_tracker_connection + : public tracker_connection + { + friend class tracker_manager; + public: + + http_tracker_connection( + io_context& ios + , tracker_manager& man + , tracker_request req + , std::weak_ptr c); + + void start() override; + void close() override; + + private: + + std::shared_ptr shared_from_this() + { + return std::static_pointer_cast( + tracker_connection::shared_from_this()); + } + + void on_filter(http_connection& c, std::vector& endpoints); + bool on_filter_hostname(http_connection& c, string_view hostname); + void on_connect(http_connection& c); + void on_response(error_code const& ec, http_parser const& parser + , span data); + + void on_timeout(error_code const&) override {} + + std::shared_ptr m_tracker_connection; + address m_tracker_ip; + io_context& m_ioc; + }; + + TORRENT_EXTRA_EXPORT tracker_response parse_tracker_response( + span data, error_code& ec + , tracker_request_flags_t flags, sha1_hash const& scrape_ih); + + TORRENT_EXTRA_EXPORT bool extract_peer_info(bdecode_node const& info + , peer_entry& ret, error_code& ec); +} + +#endif // TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED diff --git a/include/libtorrent/i2p_stream.hpp b/include/libtorrent/i2p_stream.hpp new file mode 100644 index 0000000..55d6914 --- /dev/null +++ b/include/libtorrent/i2p_stream.hpp @@ -0,0 +1,642 @@ +/* + +Copyright (c) 2009, 2013-2019, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_I2P_STREAM_HPP_INCLUDED +#define TORRENT_I2P_STREAM_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_I2P + +#include +#include +#include +#include + +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/proxy_settings.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/hex.hpp" // for to_hex +#include "libtorrent/debug.hpp" + +namespace libtorrent { + +namespace i2p_error { + + // error values for the i2p_category error_category. + enum i2p_error_code + { + no_error = 0, + parse_failed, + cant_reach_peer, + i2p_error, + invalid_key, + invalid_id, + timeout, + key_not_found, + duplicated_id, + num_errors + }; + + // hidden + TORRENT_EXPORT boost::system::error_code make_error_code(i2p_error_code e); +} +} + +namespace boost { +namespace system { + +template<> +struct is_error_code_enum +{ static const bool value = true; }; + +} +} + + +namespace libtorrent { + + // returns the error category for I2P errors + TORRENT_EXPORT boost::system::error_category& i2p_category(); + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + inline boost::system::error_category& get_i2p_category() + { return i2p_category(); } +#endif + +struct i2p_session_options +{ + int m_inbound_quantity = 3; + int m_outbound_quantity = 3; + int m_inbound_length = 3; + int m_outbound_length = 3; +}; + +struct i2p_stream : proxy_base +{ + explicit i2p_stream(io_context& io_context); + i2p_stream(i2p_stream&&) noexcept = default; + // explicitly disallow assignment, to silence msvc warning + i2p_stream& operator=(i2p_stream const&) = delete; + + enum command_t : std::uint8_t + { + cmd_none, + cmd_create_session, + cmd_connect, + cmd_accept, + cmd_name_lookup, + cmd_incoming + }; + + void set_command(command_t c) { m_command = c; } + + void set_session_options(i2p_session_options const& session_options) + { + m_session_options = session_options; + } + + void set_session_id(char const* id) { m_id = id; } + + void set_local_i2p_endpoint(string_view d) { m_local = d.to_string(); } + std::string const& local_i2p_endpoint() const { return m_local; } + void set_destination(string_view d) { m_dest = d.to_string(); } + std::string const& destination() const { return m_dest; } + + template + void async_connect(endpoint_type const&, Handler h) + { + // since we don't support regular endpoints, just ignore the one + // provided and use m_dest. + + // the connect is split up in the following steps: + // 1. resolve name of proxy server + // 2. connect to SAM bridge + // 4 send command message (CONNECT/ACCEPT) + + m_resolver.async_resolve(m_hostname, to_string(m_port).data(), wrap_allocator( + [this](error_code const& ec, tcp::resolver::results_type ips, Handler hn) { + do_connect(ec, std::move(ips), std::move(hn)); + }, std::move(h))); + } + + std::string name_lookup() const { return m_name_lookup; } + void set_name_lookup(char const* name) { m_name_lookup = name; } + + template + void send_name_lookup(Handler h) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_state = read_name_lookup_response; + char cmd[1024]; + int size = std::snprintf(cmd, sizeof(cmd), "NAMING LOOKUP NAME=%s\n", m_name_lookup.c_str()); + ADD_OUTSTANDING_ASYNC("i2p_stream::start_read_line"); + async_write(m_sock, boost::asio::buffer(cmd, std::size_t(size)), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + start_read_line(ec, std::move(hn)); + }, std::move(h))); + } + +private: + + template + void do_connect(error_code const& e, tcp::resolver::results_type ips, Handler h) + { + TORRENT_ASSERT(m_magic == 0x1337); + if (e || ips.empty()) + { + h(e); + error_code ec; + close(ec); + return; + } + + auto i = ips.begin(); + ADD_OUTSTANDING_ASYNC("i2p_stream::connected"); + m_sock.async_connect(i->endpoint(), wrap_allocator( + [this](error_code const& ec, Handler hn) { + connected(ec, std::move(hn)); + }, std::move(h))); + } + + template + void connected(error_code const& e, Handler h) + { + TORRENT_ASSERT(m_magic == 0x1337); + COMPLETE_ASYNC("i2p_stream::connected"); + if (handle_error(e, h)) return; + + // send hello command + m_state = read_hello_response; + static const char cmd[] = "HELLO VERSION MIN=3.1 MAX=3.1\n"; + + ADD_OUTSTANDING_ASYNC("i2p_stream::start_read_line"); + async_write(m_sock, boost::asio::buffer(cmd, sizeof(cmd) - 1), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + start_read_line(ec, std::move(hn)); + }, std::move(h))); + } + + template + void start_read_line(error_code const& e, Handler h) + { + TORRENT_ASSERT(m_magic == 0x1337); + COMPLETE_ASYNC("i2p_stream::start_read_line"); + if (handle_error(e, h)) return; + + ADD_OUTSTANDING_ASYNC("i2p_stream::read_line"); + m_buffer.resize(1); + async_read(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + read_line(ec, std::move(hn)); + }, std::move(h))); + } + + template + void read_line(error_code const& e, Handler h) + { + TORRENT_ASSERT(m_magic == 0x1337); + COMPLETE_ASYNC("i2p_stream::read_line"); + if (handle_error(e, h)) return; + + auto const read_pos = int(m_buffer.size()); + + // look for \n which means end of the response + if (m_buffer[read_pos - 1] != '\n') + { + ADD_OUTSTANDING_ASYNC("i2p_stream::read_line"); + // read another byte from the socket + m_buffer.resize(read_pos + 1); + async_read(m_sock, boost::asio::buffer(&m_buffer[read_pos], 1), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + read_line(ec, std::move(hn)); + }, std::move(h))); + return; + } + m_buffer[read_pos - 1] = 0; + + if (m_command == cmd_incoming) + { + // this is the line containing the destination + // of the incoming connection in an accept call + m_dest = &m_buffer[0]; + h(e); + std::vector().swap(m_buffer); + return; + } + + error_code invalid_response(i2p_error::parse_failed + , i2p_category()); + + string_view expect1; + string_view expect2; + + switch (m_state) + { + case read_hello_response: + expect1 = "HELLO"_sv; + expect2 = "REPLY"_sv; + break; + case read_connect_response: + case read_accept_response: + expect1 = "STREAM"_sv; + expect2 = "STATUS"_sv; + break; + case read_session_create_response: + expect1 = "SESSION"_sv; + expect2 = "STATUS"_sv; + break; + case read_name_lookup_response: + expect1 = "NAMING"_sv; + expect2 = "REPLY"_sv; + break; + } + + TORRENT_ASSERT(m_buffer[int(m_buffer.size()) - 1] == '\0'); + string_view remaining(m_buffer.data(), m_buffer.size() - 1); + string_view token; + + std::tie(token, remaining) = split_string(remaining, ' '); + if (expect1.empty() || expect1 != token) + { handle_error(invalid_response, h); return; } + + std::tie(token, remaining) = split_string(remaining, ' '); + if (expect2.empty() || expect2 != token) + { handle_error(invalid_response, h); return; } + + int result = 0; + + for(;;) + { + string_view name; + std::tie(name, remaining) = split_string(remaining, '='); + if (name.empty()) break; + string_view value; + if (remaining[0] == '"') + { + std::tie(value, remaining) = split_string(remaining.substr(1), '"'); + if (value.empty()) { handle_error(invalid_response, h); return; } + value.remove_suffix(1); + } + else + { + std::tie(value, remaining) = split_string(remaining, ' '); + } + if (value.empty()) { handle_error(invalid_response, h); return; } + + if ("RESULT"_sv == name) + { + if ("OK"_sv == value) + result = i2p_error::no_error; + else if ("CANT_REACH_PEER"_sv == value) + result = i2p_error::cant_reach_peer; + else if ("I2P_ERROR"_sv == value) + result = i2p_error::i2p_error; + else if ("INVALID_KEY"_sv == value) + result = i2p_error::invalid_key; + else if ("INVALID_ID"_sv == value) + result = i2p_error::invalid_id; + else if ("TIMEOUT"_sv == value) + result = i2p_error::timeout; + else if ("KEY_NOT_FOUND"_sv == value) + result = i2p_error::key_not_found; + else if ("DUPLICATED_ID"_sv == value) + result = i2p_error::duplicated_id; + else + result = i2p_error::num_errors; // unknown error + } + /*else if ("MESSAGE" == name) + { + } + else if ("VERSION"_sv == name) + { + }*/ + else if ("VALUE"_sv == name) + { + m_name_lookup = value.to_string(); + } + else if ("DESTINATION"_sv == name) + { + m_dest = value.to_string(); + } + } + + error_code ec(result, i2p_category()); + if (ec) + { + std::forward(h)(ec); + return; + } + + switch (m_state) + { + case read_hello_response: + switch (m_command) + { + case cmd_create_session: + send_session_create(std::move(h)); + break; + case cmd_accept: + send_accept(std::move(h)); + break; + case cmd_connect: + send_connect(std::move(h)); + break; + case cmd_none: + case cmd_name_lookup: + case cmd_incoming: + std::forward(h)(ec); + std::vector().swap(m_buffer); + } + break; + case read_connect_response: + case read_session_create_response: + case read_name_lookup_response: + std::forward(h)(ec); + std::vector().swap(m_buffer); + break; + case read_accept_response: + // the SAM bridge is waiting for an incoming + // connection. + // wait for one more line containing + // the destination of the remote peer + m_command = cmd_incoming; + m_buffer.resize(1); + ADD_OUTSTANDING_ASYNC("i2p_stream::read_line"); + async_read(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& err, std::size_t, Handler hn) { + read_line(err, std::move(hn)); + }, std::move(h))); + break; + } + } + + template + void send_connect(Handler h) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_state = read_connect_response; + char cmd[1024]; + int size = std::snprintf(cmd, sizeof(cmd), "STREAM CONNECT ID=%s DESTINATION=%s\n" + , m_id, m_dest.c_str()); + ADD_OUTSTANDING_ASYNC("i2p_stream::start_read_line"); + async_write(m_sock, boost::asio::buffer(cmd, std::size_t(size)), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + start_read_line(ec, std::move(hn)); + }, std::move(h))); + } + + template + void send_accept(Handler h) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_state = read_accept_response; + char cmd[400]; + int size = std::snprintf(cmd, sizeof(cmd), "STREAM ACCEPT ID=%s\n", m_id); + ADD_OUTSTANDING_ASYNC("i2p_stream::start_read_line"); + async_write(m_sock, boost::asio::buffer(cmd, std::size_t(size)), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + start_read_line(ec, std::move(hn)); + }, std::move(h))); + } + + template + void send_session_create(Handler h) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_state = read_session_create_response; + char cmd[400]; + int size = std::snprintf(cmd, sizeof(cmd), + "SESSION CREATE STYLE=STREAM ID=%s " + "DESTINATION=TRANSIENT SIGNATURE_TYPE=7 i2cp.leaseSetEncType=4,0 " + "inbound.quantity=%d outbound.quantity=%d inbound.length=%d outbound.length=%d\n", + m_id, m_session_options.m_inbound_quantity, m_session_options.m_outbound_quantity, + m_session_options.m_inbound_length, m_session_options.m_outbound_length); + ADD_OUTSTANDING_ASYNC("i2p_stream::start_read_line"); + async_write(m_sock, boost::asio::buffer(cmd, std::size_t(size)), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + start_read_line(ec, std::move(hn)); + }, std::move(h))); + } + + // send and receive buffer + aux::noexcept_movable> m_buffer; + char const* m_id = nullptr; + std::string m_dest; + std::string m_local; + std::string m_name_lookup; + + i2p_session_options m_session_options; + + enum state_t : std::uint8_t + { + read_hello_response, + read_connect_response, + read_accept_response, + read_session_create_response, + read_name_lookup_response + }; + + command_t m_command; + state_t m_state; +}; + +class i2p_connection +{ +public: + explicit i2p_connection(io_context& ios); + ~i2p_connection(); + // explicitly disallow assignment, to silence msvc warning + i2p_connection& operator=(i2p_connection const&) = delete; + + aux::proxy_settings proxy() const; + + bool is_open() const + { + return m_sam_socket + && m_sam_socket->is_open() + && m_state != sam_connecting; + } + template + void open(std::string const& hostname, int port, + i2p_session_options const& session_options, Handler handler) + { + // we already seem to have a session to this SAM router + if (m_hostname == hostname + && m_port == port + && m_sam_socket + && (is_open() || m_state == sam_connecting)) return; + + m_hostname = hostname; + m_port = port; + + if (m_hostname.empty()) return; + + m_state = sam_connecting; + + char tmp[20]; + aux::random_bytes(tmp); + m_session_id.resize(sizeof(tmp)*2); + aux::to_hex(tmp, &m_session_id[0]); + + m_sam_socket = std::make_shared(m_io_service); + m_sam_socket->set_proxy(m_hostname, m_port); + m_sam_socket->set_command(i2p_stream::cmd_create_session); + m_sam_socket->set_session_id(m_session_id.c_str()); + m_sam_socket->set_session_options(session_options); + + ADD_OUTSTANDING_ASYNC("i2p_stream::on_sam_connect"); + m_sam_socket->async_connect(tcp::endpoint(), wrap_allocator( + [this,s=m_sam_socket](error_code const& ec, Handler hn) { + on_sam_connect(ec, s, std::move(hn)); + }, std::move(handler))); + } + void close(error_code&); + + // TODO: make this a string_view + char const* session_id() const { return m_session_id.c_str(); } + std::string const& local_endpoint() const { return m_i2p_local_endpoint; } + + template + void async_name_lookup(char const* name, Handler handler) + { + if (m_state == sam_idle && m_name_lookup.empty() && is_open()) + do_name_lookup(name, std::move(handler)); + else + m_name_lookup.emplace_back(std::string(name) + , std::move(handler)); + } + +private: + + template + void on_sam_connect(error_code const& ec, std::shared_ptr, Handler h) + { + COMPLETE_ASYNC("i2p_stream::on_sam_connect"); + m_state = sam_idle; + + if (ec) + { + h(ec); + return; + } + + do_name_lookup("ME", wrap_allocator( + [this](error_code const& e, char const* dst, Handler hn) { + set_local_endpoint(e, dst, std::move(hn)); + }, std::move(h))); + } + + using name_lookup_handler = std::function; + + template + void do_name_lookup(std::string const& name, Handler handler) + { + TORRENT_ASSERT(m_state == sam_idle); + m_state = sam_name_lookup; + m_sam_socket->set_name_lookup(name.c_str()); + m_sam_socket->send_name_lookup(wrap_allocator( + [this,s=m_sam_socket](error_code const& ec, Handler hn) { + on_name_lookup(ec, s, std::move(hn)); + }, std::move(handler))); + } + + template + void on_name_lookup(error_code const& ec, std::shared_ptr, Handler handler) + { + m_state = sam_idle; + + std::string name = m_sam_socket->name_lookup(); + if (!m_name_lookup.empty()) + { + std::pair& nl = m_name_lookup.front(); + do_name_lookup(nl.first, std::move(nl.second)); + m_name_lookup.pop_front(); + } + + if (ec) + { + handler(ec, nullptr); + return; + } + + handler(ec, name.c_str()); + } + + + template + void set_local_endpoint(error_code const& ec, char const* dest, Handler h) + { + if (!ec && dest != nullptr) + m_i2p_local_endpoint = dest; + else + m_i2p_local_endpoint.clear(); + + h(ec); + } + + // to talk to i2p SAM bridge + std::shared_ptr m_sam_socket; + std::string m_hostname; + int m_port; + + // our i2p endpoint key + std::string m_i2p_local_endpoint; + std::string m_session_id; + + std::list> m_name_lookup; + + enum state_t + { + sam_connecting, + sam_name_lookup, + sam_idle + }; + + state_t m_state; + + io_context& m_io_service; +}; + +} + +#endif // TORRENT_USE_I2P + +#endif diff --git a/include/libtorrent/identify_client.hpp b/include/libtorrent/identify_client.hpp new file mode 100644 index 0000000..64bf9b2 --- /dev/null +++ b/include/libtorrent/identify_client.hpp @@ -0,0 +1,86 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2003, 2006, 2013-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IDENTIFY_CLIENT_HPP_INCLUDED +#define TORRENT_IDENTIFY_CLIENT_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/fingerprint.hpp" + +// TODO: hide this declaration when deprecated functions are disabled, and +// remove its internal use +namespace libtorrent { + +namespace aux { + + TORRENT_EXTRA_EXPORT + std::string identify_client_impl(const peer_id& p); + +} + + // these functions don't really need to be public. This mechanism of + // advertising client software and version is also out-dated. + + // This function can can be used to extract a string describing a client + // version from its peer-id. It will recognize most clients that have this + // kind of identification in the peer-id. + TORRENT_DEPRECATED_EXPORT + std::string identify_client(const peer_id& p); + +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // Returns an optional fingerprint if any can be identified from the peer + // id. This can be used to automate the identification of clients. It will + // not be able to identify peers with non- standard encodings. Only Azureus + // style, Shadow's style and Mainline style. + TORRENT_DEPRECATED_EXPORT + boost::optional + client_fingerprint(peer_id const& p); + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif // TORRENT_ABI_VERSION + +} + +#endif // TORRENT_IDENTIFY_CLIENT_HPP_INCLUDED diff --git a/include/libtorrent/index_range.hpp b/include/libtorrent/index_range.hpp new file mode 100644 index 0000000..ad5e292 --- /dev/null +++ b/include/libtorrent/index_range.hpp @@ -0,0 +1,72 @@ +/* + +Copyright (c) 2018-2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_INDEX_RANGE_HPP +#define TORRENT_INDEX_RANGE_HPP + +namespace libtorrent { + +template +struct index_iter +{ + explicit index_iter(Index i) : m_idx(i) {} + index_iter operator++() + { + ++m_idx; + return *this; + } + index_iter operator--() + { + --m_idx; + return *this; + } + Index operator*() const { return m_idx; } + friend inline bool operator==(index_iter lhs, index_iter rhs) + { return lhs.m_idx == rhs.m_idx; } + friend inline bool operator!=(index_iter lhs, index_iter rhs) + { return lhs.m_idx != rhs.m_idx; } +private: + Index m_idx; +}; + +template +struct index_range +{ + Index _begin; + Index _end; + index_iter begin() const { return index_iter{_begin}; } + index_iter end() const { return index_iter{_end}; } +}; + +} + +#endif diff --git a/include/libtorrent/info_hash.hpp b/include/libtorrent/info_hash.hpp new file mode 100644 index 0000000..a8c580f --- /dev/null +++ b/include/libtorrent/info_hash.hpp @@ -0,0 +1,167 @@ +/* + +Copyright (c) 2018, BitTorrent Inc. +Copyright (c) 2018, Steven Siloti +Copyright (c) 2019-2022, Arvid Norberg +Copyright (c) 2020, Mike Tzou +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. +* Neither the name of the author nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_INFO_HASH_HPP_INCLUDED +#define TORRENT_INFO_HASH_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/sha1_hash.hpp" + +#if TORRENT_USE_IOSTREAM +#include +#endif // TORRENT_USE_IOSTREAM + +namespace libtorrent +{ + // BitTorrent version enumerator + enum class protocol_version : std::uint8_t + { + // The original BitTorrent version, using SHA-1 hashes + V1, + // Version 2 of the BitTorrent protocol, using SHA-256 hashes + V2, + NUM + }; + + // internal + constexpr std::size_t num_protocols = int(protocol_version::NUM); + +namespace { + std::initializer_list const all_versions{ + protocol_version::V1, + protocol_version::V2 + }; +} + + // class holding the info-hash of a torrent. It can hold a v1 info-hash + // (SHA-1) or a v2 info-hash (SHA-256) or both. + // + // .. note:: + // + // If ``has_v2()`` is false then the v1 hash might actually be a truncated + // v2 hash + struct TORRENT_EXPORT info_hash_t + { + // The default constructor creates an object that has neither a v1 or v2 + // hash. + // + // For backwards compatibility, make it possible to construct directly + // from a v1 hash. This constructor allows *implicit* conversion from a + // v1 hash, but the implicitness is deprecated. + info_hash_t() noexcept = default; + explicit info_hash_t(sha1_hash h1) noexcept : v1(h1) {} // NOLINT + explicit info_hash_t(sha256_hash h2) noexcept : v2(h2) {} + info_hash_t(sha1_hash h1, sha256_hash h2) noexcept + : v1(h1), v2(h2) {} + + // hidden + info_hash_t(info_hash_t const&) noexcept = default; + info_hash_t& operator=(info_hash_t const&) & noexcept = default; + + // returns true if the corresponding info hash is present in this + // object. + bool has_v1() const { return !v1.is_all_zeros(); } + bool has_v2() const { return !v2.is_all_zeros(); } + bool has(protocol_version v) const + { + TORRENT_ASSERT(v != protocol_version::NUM); + return v == protocol_version::V1 ? has_v1() : has_v2(); + } + + // returns the has for the specified protocol version + sha1_hash get(protocol_version v) const + { + TORRENT_ASSERT(v != protocol_version::NUM); + return v == protocol_version::V1 ? v1 : sha1_hash(v2.data()); + } + + // returns the v2 (truncated) info-hash, if there is one, otherwise + // returns the v1 info-hash + sha1_hash get_best() const + { + return has_v2() ? get(protocol_version::V2) : v1; + } + + friend bool operator!=(info_hash_t const& lhs, info_hash_t const& rhs) + { + return std::tie(lhs.v1, lhs.v2) != std::tie(rhs.v1, rhs.v2); + } + + friend bool operator==(info_hash_t const& lhs, info_hash_t const& rhs) noexcept + { + return std::tie(lhs.v1, lhs.v2) == std::tie(rhs.v1, rhs.v2); + } + + // calls the function object ``f`` for each hash that is available. + // starting with v1. The signature of ``F`` is:: + // + // void(sha1_hash const&, protocol_version); + template void for_each(F f) const + { + if (has_v1()) f(v1, protocol_version::V1); + if (has_v2()) f(sha1_hash(v2.data()), protocol_version::V2); + } + + bool operator<(info_hash_t const& o) const + { + return std::tie(v1, v2) < std::tie(o.v1, o.v2); + } + +#if TORRENT_USE_IOSTREAM + friend std::ostream& operator<<(std::ostream& os, info_hash_t const& ih) + { + return os << '[' << ih.v1 << ',' << ih.v2 << ']'; + } +#endif // TORRENT_USE_IOSTREAM + + sha1_hash v1; + sha256_hash v2; + }; + +} + +namespace std { + template <> + struct hash + { + std::size_t operator()(libtorrent::info_hash_t const& k) const + { + return std::hash{}(k.v1) + ^ std::hash{}(k.v2) ; + } + }; +} + +#endif diff --git a/include/libtorrent/io.hpp b/include/libtorrent/io.hpp new file mode 100644 index 0000000..7802e16 --- /dev/null +++ b/include/libtorrent/io.hpp @@ -0,0 +1,189 @@ +/* + +Copyright (c) 2004, 2007, 2009, 2011, 2016-2017, 2019, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IO_HPP_INCLUDED +#define TORRENT_IO_HPP_INCLUDED + +#include +#include +#include // for copy +#include // for memcpy +#include +#include + +#include "assert.hpp" +#include "libtorrent/aux_/io.hpp" + +namespace libtorrent { +namespace aux { + + // reads an integer from a byte stream + // in big endian byte order and converts + // it to native endianness + template + inline T read_impl(InIt& start, type) + { + T ret = 0; + for (int i = 0; i < int(sizeof(T)); ++i) + { + ret <<= 8; + ret |= static_cast(*start); + ++start; + } + return ret; + } + + template + std::uint8_t read_impl(InIt& start, type) + { + return static_cast(*start++); + } + + template + std::int8_t read_impl(InIt& start, type) + { + return static_cast(*start++); + } + + template + typename std::enable_if<(std::is_integral::value + && !std::is_same::value) + || std::is_enum::value, void>::type + write_impl(In data, OutIt& start) + { + // Note: the test for [OutItT==void] below is necessary because + // in C++11 std::back_insert_iterator::value_type is void. + // This could change in C++17 or above + using OutItT = typename std::iterator_traits::value_type; + using Byte = typename std::conditional< + std::is_same::value, char, OutItT>::type; + static_assert(sizeof(Byte) == 1, "wrong iterator or pointer type"); + + T val = static_cast(data); + TORRENT_ASSERT(data == static_cast(val)); + for (int i = int(sizeof(T)) - 1; i >= 0; --i) + { + *start = static_cast((val >> (i * 8)) & 0xff); + ++start; + } + } + + template + typename std::enable_if::value, void>::type + write_impl(Val val, OutIt& start) + { write_impl(val ? 1 : 0, start); } + + // -- adaptors + + template + std::int64_t read_int64(InIt& start) + { return read_impl(start, type()); } + + template + std::uint64_t read_uint64(InIt& start) + { return read_impl(start, type()); } + + template + std::uint32_t read_uint32(InIt& start) + { return read_impl(start, type()); } + + template + std::int32_t read_int32(InIt& start) + { return read_impl(start, type()); } + + template + std::int16_t read_int16(InIt& start) + { return read_impl(start, type()); } + + template + std::uint16_t read_uint16(InIt& start) + { return read_impl(start, type()); } + + template + std::int8_t read_int8(InIt& start) + { return read_impl(start, type()); } + + template + std::uint8_t read_uint8(InIt& start) + { return read_impl(start, type()); } + + + template + void write_uint64(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_int64(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_uint32(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_int32(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_uint16(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_int16(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_uint8(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_int8(T val, OutIt& start) + { write_impl(val, start); } + + inline int write_string(std::string const& str, char*& start) + { + std::memcpy(reinterpret_cast(start), str.c_str(), str.size()); + start += str.size(); + return int(str.size()); + } + + template + int write_string(std::string const& val, OutIt& out) + { + for (auto const c : val) *out++ = c; + return int(val.length()); + } +} // namespace aux +} + +#endif // TORRENT_IO_HPP_INCLUDED diff --git a/include/libtorrent/io_context.hpp b/include/libtorrent/io_context.hpp new file mode 100644 index 0000000..efb2710 --- /dev/null +++ b/include/libtorrent/io_context.hpp @@ -0,0 +1,63 @@ +/* + +Copyright (c) 2006-2007, 2009, 2015, 2017, 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IO_CONTEXT_HPP_INCLUDED +#define TORRENT_IO_CONTEXT_HPP_INCLUDED + +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#else +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#include +#include +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // SIMULATOR + +namespace libtorrent { + +#if defined TORRENT_BUILD_SIMULATOR + using sim::asio::io_context; +#else + using boost::asio::io_context; +#endif + using boost::asio::executor_work_guard; + using boost::asio::make_work_guard; + + using boost::asio::post; + using boost::asio::dispatch; + using boost::asio::defer; +} + +#endif diff --git a/include/libtorrent/io_service.hpp b/include/libtorrent/io_service.hpp new file mode 100644 index 0000000..86b0b99 --- /dev/null +++ b/include/libtorrent/io_service.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2006-2007, 2009, 2015, 2017, 2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IO_SERVICE_HPP_INCLUDED +#define TORRENT_IO_SERVICE_HPP_INCLUDED + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#error warning "this header is deprecated, use io_context.hpp instead" +namespace libtorrent { + + using io_service = boost::asio::io_context; +} + +#endif diff --git a/include/libtorrent/ip_filter.hpp b/include/libtorrent/ip_filter.hpp new file mode 100644 index 0000000..fdaccc0 --- /dev/null +++ b/include/libtorrent/ip_filter.hpp @@ -0,0 +1,241 @@ +/* + +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2005-2007, 2009-2010, 2013, 2015-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016-2018, 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IP_FILTER_HPP +#define TORRENT_IP_FILTER_HPP + +#include "libtorrent/config.hpp" + +#include +#include +#include +#include +#include + +#include "libtorrent/address.hpp" + +namespace libtorrent { + +template +struct ip_range +{ + Addr first; + Addr last; + std::uint32_t flags; + friend bool operator==(ip_range const& lhs, ip_range const& rhs) + { + return lhs.first == rhs.first + && lhs.last == rhs.last + && lhs.flags == rhs.flags; + } +}; + +namespace aux { + + template + TORRENT_EXTRA_EXPORT Addr zero(); + template + TORRENT_EXTRA_EXPORT Addr plus_one(Addr const& a); + template + TORRENT_EXTRA_EXPORT Addr minus_one(Addr const& a); + template + TORRENT_EXTRA_EXPORT Addr max_addr(); + + extern template address_v4::bytes_type minus_one(address_v4::bytes_type const&); + extern template address_v6::bytes_type minus_one(address_v6::bytes_type const&); + extern template address_v4::bytes_type plus_one(address_v4::bytes_type const&); + extern template address_v6::bytes_type plus_one(address_v6::bytes_type const&); + extern template address_v4::bytes_type zero(); + extern template address_v6::bytes_type zero(); + extern template address_v4::bytes_type max_addr(); + extern template address_v6::bytes_type max_addr(); + + inline std::uint16_t plus_one(std::uint16_t val) { return val + 1; } + inline std::uint16_t minus_one(std::uint16_t val) { return val - 1; } + template<> + inline std::uint16_t zero() { return 0; } + template<> + inline std::uint16_t max_addr() + { return (std::numeric_limits::max)(); } + + // this is the generic implementation of + // a filter for a specific address type. + // it works with IPv4 and IPv6 + template + class filter_impl + { + public: + + filter_impl(); + bool empty() const; + void add_rule(Addr first, Addr last, std::uint32_t flags); + std::uint32_t access(Addr const& addr) const; + template + std::vector> export_filter() const; + + private: + + struct range + { + range(Addr addr, std::uint32_t a = 0) : start(addr), access(a) {} // NOLINT + bool operator<(range const& r) const { return start < r.start; } + bool operator<(Addr const& a) const { return start < a; } + Addr start; + // the end of the range is implicit + // and given by the next entry in the set + std::uint32_t access; + friend bool operator==(range const& lhs, range const& rhs) + { return lhs.start == rhs.start && lhs.access == rhs.access; } + }; + + std::set m_access_list; + }; + + extern template class filter_impl; + extern template class filter_impl; + extern template class filter_impl; + + extern template std::vector> filter_impl::export_filter() const; + extern template std::vector> filter_impl::export_filter() const; + extern template std::vector> filter_impl::export_filter() const; +} + +// The ``ip_filter`` class is a set of rules that uniquely categorizes all +// ip addresses as allowed or disallowed. The default constructor creates +// a single rule that allows all addresses (0.0.0.0 - 255.255.255.255 for +// the IPv4 range, and the equivalent range covering all addresses for the +// IPv6 range). +// +// A default constructed ip_filter does not filter any address. +struct TORRENT_EXPORT ip_filter +{ + ip_filter(); + ip_filter(ip_filter const&); + ip_filter(ip_filter&&); + ip_filter& operator=(ip_filter const&); + ip_filter& operator=(ip_filter&&); + ~ip_filter(); + + // the flags defined for an IP range + enum access_flags + { + // indicates that IPs in this range should not be connected + // to nor accepted as incoming connections + blocked = 1 + }; + + // returns true if the filter does not contain any rules + bool empty() const; + + // Adds a rule to the filter. ``first`` and ``last`` defines a range of + // ip addresses that will be marked with the given flags. The ``flags`` + // can currently be 0, which means allowed, or ``ip_filter::blocked``, which + // means disallowed. + // + // precondition: + // ``first.is_v4() == last.is_v4() && first.is_v6() == last.is_v6()`` + // + // postcondition: + // ``access(x) == flags`` for every ``x`` in the range [``first``, ``last``] + // + // This means that in a case of overlapping ranges, the last one applied takes + // precedence. + void add_rule(address const& first, address const& last, std::uint32_t flags); + + // Returns the access permissions for the given address (``addr``). The permission + // can currently be 0 or ``ip_filter::blocked``. The complexity of this operation + // is O(``log`` n), where n is the minimum number of non-overlapping ranges to describe + // the current filter. + std::uint32_t access(address const& addr) const; + + using filter_tuple_t = std::tuple> + , std::vector>>; + + // This function will return the current state of the filter in the minimum number of + // ranges possible. They are sorted from ranges in low addresses to high addresses. Each + // entry in the returned vector is a range with the access control specified in its + // ``flags`` field. + // + // The return value is a tuple containing two range-lists. One for IPv4 addresses + // and one for IPv6 addresses. + filter_tuple_t export_filter() const; + +private: + + aux::filter_impl m_filter4; + aux::filter_impl m_filter6; +}; + +// the port filter maps non-overlapping port ranges to flags. This +// is primarily used to indicate whether a range of ports should +// be connected to or not. The default is to have the full port +// range (0-65535) set to flag 0. +class TORRENT_EXPORT port_filter +{ +public: + + port_filter(); + port_filter(port_filter const&); + port_filter(port_filter&&); + port_filter& operator=(port_filter const&); + port_filter& operator=(port_filter&&); + ~port_filter(); + + // the defined flags for a port range + enum access_flags + { + // this flag indicates that destination ports in the + // range should not be connected to + blocked = 1 + }; + + // set the flags for the specified port range (``first``, ``last``) to + // ``flags`` overwriting any existing rule for those ports. The range + // is inclusive, i.e. the port ``last`` also has the flag set on it. + void add_rule(std::uint16_t first, std::uint16_t last, std::uint32_t flags); + + // test the specified port (``port``) for whether it is blocked + // or not. The returned value is the flags set for this port. + // see access_flags. + std::uint32_t access(std::uint16_t port) const; + +private: + + aux::filter_impl m_filter; + +}; + +} + +#endif diff --git a/include/libtorrent/ip_voter.hpp b/include/libtorrent/ip_voter.hpp new file mode 100644 index 0000000..a10d93f --- /dev/null +++ b/include/libtorrent/ip_voter.hpp @@ -0,0 +1,136 @@ +/* + +Copyright (c) 2013-2015, 2017, 2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2016, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IP_VOTER_HPP_INCLUDED +#define TORRENT_IP_VOTER_HPP_INCLUDED + +#include +#include "libtorrent/address.hpp" +#include "libtorrent/bloom_filter.hpp" +#include "libtorrent/time.hpp" // for time_point +#include "libtorrent/aux_/session_interface.hpp" // for ip_source_t + +namespace libtorrent { + + // this is an object that keeps the state for a single external IP + // based on peoples votes + struct TORRENT_EXTRA_EXPORT ip_voter + { + ip_voter(); + + // returns true if a different IP is the top vote now + // i.e. we changed our idea of what our external IP is + bool cast_vote(address const& ip, aux::ip_source_t source_type, address const& source); + + address external_address() const { return m_external_address; } + + private: + + bool maybe_rotate(); + + struct external_ip_t + { + bool add_vote(sha1_hash const& k, aux::ip_source_t type); + + // we want to sort descending + bool operator<(external_ip_t const& rhs) const + { + if (num_votes > rhs.num_votes) return true; + if (num_votes < rhs.num_votes) return false; + return static_cast(sources) > static_cast(rhs.sources); + } + + // this is a bloom filter of the IPs that have + // reported this address + bloom_filter<16> voters; + // this is the actual external address + address addr; + // a bitmask of sources the reporters have come from + aux::ip_source_t sources{}; + // the total number of votes for this IP + std::uint16_t num_votes = 0; + }; + + // this is a bloom filter of all the IPs that have + // been the first to report an external address. Each + // IP only gets to add a new item once. + bloom_filter<32> m_external_address_voters; + + std::vector m_external_addresses; + address m_external_address; + + // the total number of unique IPs that have voted + int m_total_votes; + + // this is true from the first time we rotate. Before + // we rotate for the first time, we keep updating the + // external address as we go, since we don't have any + // stable setting to fall back on. Once this is true, + // we stop updating it on the fly, and just use the + // address from when we rotated. + bool m_valid_external; + + // the last time we rotated this ip_voter. i.e. threw + // away all the votes and started from scratch, in case + // our IP has changed + time_point m_last_rotate; + }; + + // stores one address for each combination of local/global and ipv4/ipv6 + // use of this class should be avoided, get the IP from the appropriate + // listen interface wherever possible + struct TORRENT_EXTRA_EXPORT external_ip + { + external_ip() + : m_addresses{{address_v4(), address_v6()}, {address_v4(), address_v6()}} + {} + + external_ip(address const& local4, address const& global4 + , address const& local6, address const& global6); + + // the external IP as it would be observed from `ip` + address external_address(address const& ip) const; + + private: + + // support one local and one global address per address family + // [0][n] = global [1][n] = local + // [n][0] = IPv4 [n][1] = IPv6 + // TODO: 1 have one instance per possible subnet, 192.168.x.x, 10.x.x.x, etc. + address m_addresses[2][2]; + }; + +} + +#endif diff --git a/include/libtorrent/kademlia/announce_flags.hpp b/include/libtorrent/kademlia/announce_flags.hpp new file mode 100644 index 0000000..6d79345 --- /dev/null +++ b/include/libtorrent/kademlia/announce_flags.hpp @@ -0,0 +1,63 @@ +/* + +Copyright (c) 2018-2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef ANNOUNCE_FLAGS_HPP +#define ANNOUNCE_FLAGS_HPP + +#include + +#include "libtorrent/flags.hpp" + +namespace libtorrent { +namespace dht { + +using announce_flags_t = flags::bitfield_flag; + +namespace announce { + +// announce to DHT as a seed +constexpr announce_flags_t seed = 0_bit; + +// announce to DHT with the implied-port flag set. This tells the network to use +// your source UDP port as your listen port, rather than the one specified in +// the message. This may improve the chances of traversing NATs when using uTP. +constexpr announce_flags_t implied_port = 1_bit; + +// Specify the port number for the SSL listen socket in the DHT announce. +constexpr announce_flags_t ssl_torrent = 2_bit; + +} + +} +} + +#endif diff --git a/include/libtorrent/kademlia/dht_observer.hpp b/include/libtorrent/kademlia/dht_observer.hpp new file mode 100644 index 0000000..3b49ab9 --- /dev/null +++ b/include/libtorrent/kademlia/dht_observer.hpp @@ -0,0 +1,100 @@ +/* + +Copyright (c) 2012, 2014-2017, 2019, Arvid Norberg +Copyright (c) 2014, 2017-2018, Steven Siloti +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef DHT_OBSERVER_HPP +#define DHT_OBSERVER_HPP + +#include "libtorrent/config.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/kademlia/msg.hpp" +#include "libtorrent/aux_/session_udp_sockets.hpp" // for transport + +namespace libtorrent { + +class entry; + +namespace aux { +struct listen_socket_handle; +} + +namespace dht { + + struct TORRENT_EXTRA_EXPORT dht_logger + { +#ifndef TORRENT_DISABLE_LOGGING + enum module_t + { + tracker, + node, + routing_table, + rpc_manager, + traversal + }; + + enum message_direction_t + { + incoming_message, + outgoing_message + }; + + virtual bool should_log(module_t m) const = 0; + virtual void log(module_t m, char const* fmt, ...) TORRENT_FORMAT(3,4) = 0; + virtual void log_packet(message_direction_t dir, span pkt + , udp::endpoint const& node) = 0; +#endif + + protected: + ~dht_logger() = default; + }; + + struct TORRENT_EXTRA_EXPORT dht_observer : dht_logger + { + virtual void set_external_address(aux::listen_socket_handle const& iface + , address const& addr, address const& source) = 0; + virtual int get_listen_port(aux::transport ssl, aux::listen_socket_handle const& s) = 0; + virtual void get_peers(sha1_hash const& ih) = 0; + virtual void outgoing_get_peers(sha1_hash const& target + , sha1_hash const& sent_target, udp::endpoint const& ep) = 0; + virtual void announce(sha1_hash const& ih, address const& addr, int port) = 0; + virtual bool on_dht_request(string_view query + , dht::msg const& request, entry& response) = 0; + + protected: + ~dht_observer() = default; + }; +} +} + +#endif diff --git a/include/libtorrent/kademlia/dht_settings.hpp b/include/libtorrent/kademlia/dht_settings.hpp new file mode 100644 index 0000000..ca2409a --- /dev/null +++ b/include/libtorrent/kademlia/dht_settings.hpp @@ -0,0 +1,184 @@ +/* + +Copyright (c) 2017, 2019-2020, Arvid Norberg +Copyright (c) 2020, Mike Tzou +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DHT_SETTINGS_HPP_INCLUDED +#define TORRENT_DHT_SETTINGS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/entry.hpp" + +namespace libtorrent { +namespace dht { + +#if TORRENT_ABI_VERSION <= 2 + // this is deprecated. Use settings_pack and apply_settings on the session + // instead. + + // structure used to hold configuration options for the DHT + struct TORRENT_DEPRECATED_EXPORT dht_settings + { + // the maximum number of peers to send in a reply to ``get_peers`` + int max_peers_reply = 100; + + // the number of concurrent search request the node will send when + // announcing and refreshing the routing table. This parameter is called + // alpha in the kademlia paper + int search_branching = 5; + + // the maximum number of failed tries to contact a node before it is + // removed from the routing table. If there are known working nodes that + // are ready to replace a failing node, it will be replaced immediately, + // this limit is only used to clear out nodes that don't have any node + // that can replace them. + int max_fail_count = 20; + + // the total number of torrents to track from the DHT. This is simply an + // upper limit to make sure malicious DHT nodes cannot make us allocate + // an unbounded amount of memory. + int max_torrents = 2000; + + // max number of items the DHT will store + int max_dht_items = 700; + + // the max number of peers to store per torrent (for the DHT) + int max_peers = 500; + + // the max number of torrents to return in a torrent search query to the + // DHT + int max_torrent_search_reply = 20; + + // determines if the routing table entries should restrict entries to one + // per IP. This defaults to true, which helps mitigate some attacks on + // the DHT. It prevents adding multiple nodes with IPs with a very close + // CIDR distance. + // + // when set, nodes whose IP address that's in the same /24 (or /64 for + // IPv6) range in the same routing table bucket. This is an attempt to + // mitigate node ID spoofing attacks also restrict any IP to only have a + // single entry in the whole routing table + bool restrict_routing_ips = true; + + // determines if DHT searches should prevent adding nodes with IPs with + // very close CIDR distance. This also defaults to true and helps + // mitigate certain attacks on the DHT. + bool restrict_search_ips = true; + + // makes the first buckets in the DHT routing table fit 128, 64, 32 and + // 16 nodes respectively, as opposed to the standard size of 8. All other + // buckets have size 8 still. + bool extended_routing_table = true; + + // slightly changes the lookup behavior in terms of how many outstanding + // requests we keep. Instead of having branch factor be a hard limit, we + // always keep *branch factor* outstanding requests to the closest nodes. + // i.e. every time we get results back with closer nodes, we query them + // right away. It lowers the lookup times at the cost of more outstanding + // queries. + bool aggressive_lookups = true; + + // when set, perform lookups in a way that is slightly more expensive, + // but which minimizes the amount of information leaked about you. + bool privacy_lookups = false; + + // when set, node's whose IDs that are not correctly generated based on + // its external IP are ignored. When a query arrives from such node, an + // error message is returned with a message saying "invalid node ID". + bool enforce_node_id = false; + + // ignore DHT messages from parts of the internet we wouldn't expect to + // see any traffic from + bool ignore_dark_internet = true; + + // the number of seconds a DHT node is banned if it exceeds the rate + // limit. The rate limit is averaged over 10 seconds to allow for bursts + // above the limit. + int block_timeout = 5 * 60; + + // the max number of packets per second a DHT node is allowed to send + // without getting banned. + int block_ratelimit = 5; + + // when set, the other nodes won't keep this node in their routing + // tables, it's meant for low-power and/or ephemeral devices that + // cannot support the DHT, it is also useful for mobile devices which + // are sensitive to network traffic and battery life. + // this node no longer responds to 'query' messages, and will place a + // 'ro' key (value = 1) in the top-level message dictionary of outgoing + // query messages. + bool read_only = false; + + // the number of seconds a immutable/mutable item will be expired. + // default is 0, means never expires. + int item_lifetime = 0; + + // the number of bytes per second (on average) the DHT is allowed to send. + // If the incoming requests causes to many bytes to be sent in responses, + // incoming requests will be dropped until the quota has been replenished. + int upload_rate_limit = 8000; + + // the info-hashes sample recomputation interval (in seconds). + // The node will precompute a subset of the tracked info-hashes and return + // that instead of calculating it upon each request. The permissible range + // is between 0 and 21600 seconds (inclusive). + int sample_infohashes_interval = 21600; + + // the maximum number of elements in the sampled subset of info-hashes. + // If this number is too big, expect the DHT storage implementations + // to clamp it in order to allow UDP packets go through + int max_infohashes_sample_count = 20; + }; + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // internal + struct settings : dht_settings + { + // when this is true, nodes whose IDs are derived from their source IP + // according to BEP 42 (https://www.bittorrent.org/beps/bep_0042.html) are + // preferred in the routing table. + bool prefer_verified_node_ids = true; + }; + +TORRENT_EXTRA_EXPORT dht_settings read_dht_settings(bdecode_node const& e); +TORRENT_EXTRA_EXPORT entry save_dht_settings(dht_settings const& settings); + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif + +} +} + +#endif diff --git a/include/libtorrent/kademlia/dht_state.hpp b/include/libtorrent/kademlia/dht_state.hpp new file mode 100644 index 0000000..44438d7 --- /dev/null +++ b/include/libtorrent/kademlia/dht_state.hpp @@ -0,0 +1,81 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, 2019-2020, Arvid Norberg +Copyright (c) 2017, Steven Siloti +Copyright (c) 2020, Mike Tzou +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_DHT_STATE_HPP +#define LIBTORRENT_DHT_STATE_HPP + +#include +#include +#include + +#include + +#include +#include + +namespace libtorrent { + + struct bdecode_node; +} + +namespace libtorrent { +namespace dht { + + using node_ids_t = std::vector>; + // This structure helps to store and load the state + // of the ``dht_tracker``. + // At this moment the library is only a dual stack + // implementation of the DHT. See `BEP 32`_ + // + // .. _`BEP 32`: https://www.bittorrent.org/beps/bep_0032.html + struct TORRENT_EXPORT dht_state + { + node_ids_t nids; + + // the bootstrap nodes saved from the buckets node + std::vector nodes; + // the bootstrap nodes saved from the IPv6 buckets node + std::vector nodes6; + + void clear(); + }; + + TORRENT_EXTRA_EXPORT node_ids_t extract_node_ids(bdecode_node const& e, string_view key); + TORRENT_EXTRA_EXPORT dht_state read_dht_state(bdecode_node const& e); + TORRENT_EXTRA_EXPORT entry save_dht_state(dht_state const& state); +} +} + +#endif // LIBTORRENT_DHT_STATE_HPP diff --git a/include/libtorrent/kademlia/dht_storage.hpp b/include/libtorrent/kademlia/dht_storage.hpp new file mode 100644 index 0000000..84d7fdf --- /dev/null +++ b/include/libtorrent/kademlia/dht_storage.hpp @@ -0,0 +1,245 @@ +/* + +Copyright (c) 2015-2017, Alden Torres +Copyright (c) 2015-2019, Arvid Norberg +Copyright (c) 2016, Steven Siloti +Copyright (c) 2019, Mike Tzou +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DHT_STORAGE_HPP +#define TORRENT_DHT_STORAGE_HPP + +#include + +#include +#include + +#include +#include +#include +#include + +namespace libtorrent { + class entry; + struct settings_interface; +} + +namespace libtorrent { +namespace dht { + + // This structure hold the relevant counters for the storage + struct TORRENT_EXPORT dht_storage_counters + { + std::int32_t torrents = 0; + std::int32_t peers = 0; + std::int32_t immutable_data = 0; + std::int32_t mutable_data = 0; + + // This member function set the counters to zero. + void reset(); + }; + + // The DHT storage interface is a pure virtual class that can + // be implemented to customize how the data for the DHT is stored. + // + // The default storage implementation uses three maps in RAM to save + // the peers, mutable and immutable items and it's designed to + // provide a fast and fully compliant behavior of the BEPs. + // + // libtorrent comes with one built-in storage implementation: + // ``dht_default_storage`` (private non-accessible class). Its + // constructor function is called dht_default_storage_constructor(). + // You should know that if this storage becomes full of DHT items, + // the current implementation could degrade in performance. + struct TORRENT_EXPORT dht_storage_interface + { +#if TORRENT_ABI_VERSION == 1 + // This function returns the number of torrents tracked by + // the DHT at the moment. It's used to fill session_status. + // It's deprecated. + virtual size_t num_torrents() const = 0; + + // This function returns the sum of all of peers per torrent + // tracker byt the DHT at the moment. + // It's deprecated. + virtual size_t num_peers() const = 0; +#endif + + // This member function notifies the list of all node's ids + // of each DHT running inside libtorrent. It's advisable + // that the concrete implementation keeps a copy of this list + // for an eventual prioritization when deleting an element + // to make room for a new one. + virtual void update_node_ids(std::vector const& ids) = 0; + + // This function retrieve the peers tracked by the DHT + // corresponding to the given info_hash. You can specify if + // you want only seeds and/or you are scraping the data. + // + // For future implementers: + // If the torrent tracked contains a name, such a name + // must be stored as a string in peers["n"] + // + // If the scrape parameter is true, you should fill these keys: + // + // peers["BFpe"] + // with the standard bit representation of a + // 256 bloom filter containing the downloaders + // peers["BFsd"] + // with the standard bit representation of a + // 256 bloom filter containing the seeders + // + // If the scrape parameter is false, you should fill the + // key peers["values"] with a list containing a subset of + // peers tracked by the given info_hash. Such a list should + // consider the value of settings_pack::dht_max_peers_reply. + // If noseed is true only peers marked as no seed should be included. + // + // returns true if the maximum number of peers are stored + // for this info_hash. + virtual bool get_peers(sha1_hash const& info_hash + , bool noseed, bool scrape, address const& requester + , entry& peers) const = 0; + + // This function is named announce_peer for consistency with the + // upper layers, but has nothing to do with networking. Its only + // responsibility is store the peer in such a way that it's returned + // in the entry with the lookup_peers. + // + // The ``name`` parameter is the name of the torrent if provided in + // the announce_peer DHT message. The length of this value should + // have a maximum length in the final storage. The default + // implementation truncate the value for a maximum of 50 characters. + virtual void announce_peer(sha1_hash const& info_hash + , tcp::endpoint const& endp + , string_view name, bool seed) = 0; + + // This function retrieves the immutable item given its target hash. + // + // For future implementers: + // The value should be returned as an entry in the key item["v"]. + // + // returns true if the item is found and the data is returned + // inside the (entry) out parameter item. + virtual bool get_immutable_item(sha1_hash const& target + , entry& item) const = 0; + + // Store the item's data. This layer is only for storage. + // The authentication of the item is performed by the upper layer. + // + // For implementers: + // This data can be stored only if the target is not already + // present. The implementation should consider the value of + // settings_pack::dht_max_dht_items. + // + virtual void put_immutable_item(sha1_hash const& target + , span buf + , address const& addr) = 0; + + // This function retrieves the sequence number of a mutable item. + // + // returns true if the item is found and the data is returned + // inside the out parameter seq. + virtual bool get_mutable_item_seq(sha1_hash const& target + , sequence_number& seq) const = 0; + + // This function retrieves the mutable stored in the DHT. + // + // For implementers: + // The item sequence should be stored in the key item["seq"]. + // if force_fill is true or (0 <= seq and seq < item["seq"]) + // the following keys should be filled + // item["v"] - with the value no encoded. + // item["sig"] - with a string representation of the signature. + // item["k"] - with a string representation of the public key. + // + // returns true if the item is found and the data is returned + // inside the (entry) out parameter item. + virtual bool get_mutable_item(sha1_hash const& target + , sequence_number seq, bool force_fill + , entry& item) const = 0; + + // Store the item's data. This layer is only for storage. + // The authentication of the item is performed by the upper layer. + // + // For implementers: + // The sequence number should be checked if the item is already + // present. The implementation should consider the value of + // settings_pack::dht_max_dht_items. + // + virtual void put_mutable_item(sha1_hash const& target + , span buf + , signature const& sig + , sequence_number seq + , public_key const& pk + , span salt + , address const& addr) = 0; + + // This function retrieves a sample info-hashes + // + // For implementers: + // The info-hashes should be stored in ["samples"] (N x 20 bytes). + // the following keys should be filled + // item["interval"] - the subset refresh interval in seconds. + // item["num"] - number of info-hashes in storage. + // + // Internally, this function is allowed to lazily evaluate, cache + // and modify the actual sample to put in ``item`` + // + // returns the number of info-hashes in the sample. + virtual int get_infohashes_sample(entry& item) = 0; + + // This function is called periodically (non-constant frequency). + // + // For implementers: + // Use this functions for expire peers or items or any other + // storage cleanup. + virtual void tick() = 0; + + // return stats counters for the store + virtual dht_storage_counters counters() const = 0; + + // hidden + virtual ~dht_storage_interface() {} + }; + + using dht_storage_constructor_type + = std::function(settings_interface const& settings)>; + + // constructor for the default DHT storage. The DHT storage is responsible + // for maintaining peers and mutable and immutable items announced and + // stored/put to the DHT node. + TORRENT_EXPORT std::unique_ptr dht_default_storage_constructor( + settings_interface const& settings); + +} // namespace dht +} // namespace libtorrent + +#endif //TORRENT_DHT_STORAGE_HPP diff --git a/include/libtorrent/kademlia/dht_tracker.hpp b/include/libtorrent/kademlia/dht_tracker.hpp new file mode 100644 index 0000000..4fff75f --- /dev/null +++ b/include/libtorrent/kademlia/dht_tracker.hpp @@ -0,0 +1,230 @@ +/* + +Copyright (c) 2006-2008, 2010, 2014-2021, Arvid Norberg +Copyright (c) 2014-2015, 2017, Steven Siloti +Copyright (c) 2015-2017, Alden Torres +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2020, Fonic +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DHT_TRACKER +#define TORRENT_DHT_TRACKER + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace libtorrent { + + struct counters; +#if TORRENT_ABI_VERSION == 1 + struct session_status; +#endif +namespace aux { + struct session_settings; +} +} + +namespace libtorrent { +namespace dht { + + struct TORRENT_EXTRA_EXPORT dht_tracker final + : socket_manager + , std::enable_shared_from_this + { + using send_fun_t = std::function, error_code&, udp_send_flags_t)>; + + dht_tracker(dht_observer* observer + , io_context& ios + , send_fun_t send_fun + , aux::session_settings const& settings + , counters& cnt + , dht_storage_interface& storage + , dht_state&& state); + + // the dht_state must be moved in! + dht_tracker(dht_observer* observer + , io_context& ios + , send_fun_t const& send_fun + , aux::session_settings const& settings + , counters& cnt + , dht_storage_interface& storage + , dht_state const& state) = delete; + +#if defined(_MSC_VER) && _MSC_VER < 1910 + // workaround for a bug in msvc 14.0 + // it attempts to generate a copy constructor for some strange reason + // and fails because tracker_node is not copyable + dht_tracker(dht_tracker const&) = delete; +#endif + + void start(find_data::nodes_callback const& f); + void stop(); + + // tell the node to recalculate its node id based on the current + // understanding of its external address (which may have changed) + void update_node_id(aux::listen_socket_handle const& s); + + void new_socket(aux::listen_socket_handle const& s); + void delete_socket(aux::listen_socket_handle const& s); + + void add_node(udp::endpoint const& node); + void add_router_node(udp::endpoint const& node); + + dht_state state() const; + + void get_peers(sha1_hash const& ih + , std::function const&)> f); + void announce(sha1_hash const& ih, int listen_port, announce_flags_t flags + , std::function const&)> f); + + void sample_infohashes(udp::endpoint const& ep, sha1_hash const& target + , std::function + , std::vector>)> f); + + void get_item(sha1_hash const& target + , std::function cb); + + // key is a 32-byte binary string, the public key to look up. + // the salt is optional + void get_item(public_key const& key + , std::function cb + , std::string salt = std::string()); + + // for immutable_item. + // the callback function will be called when put operation is done. + // the int parameter indicates the success numbers of put operation. + void put_item(entry const& data + , std::function cb); + + // for mutable_item. + // the data_cb will be called when we get authoritative mutable_item, + // the cb is same as put immutable_item. + void put_item(public_key const& key + , std::function cb + , std::function data_cb, std::string salt = std::string()); + + // send an arbitrary DHT request directly to a node + void direct_request(udp::endpoint const& ep, entry& e + , std::function f); + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + void dht_status(session_status& s); +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + std::vector dht_status() const; + void update_stats_counters(counters& c) const; + + void incoming_error(error_code const& ec, udp::endpoint const& ep); + bool incoming_packet(aux::listen_socket_handle const& s + , udp::endpoint const& ep, span buf); + + std::vector> live_nodes(node_id const& nid); + + private: + struct tracker_node + { + tracker_node(io_context& ios + , aux::listen_socket_handle const& s, socket_manager* sock + , aux::session_settings const& settings + , node_id const& nid + , dht_observer* observer, counters& cnt + , get_foreign_node_t get_foreign_node + , dht_storage_interface& storage); + tracker_node(tracker_node const&) = delete; + tracker_node(tracker_node&&) = delete; + + node dht; + deadline_timer connection_timer; + }; + using tracker_nodes_t = std::map; + + std::shared_ptr self() + { return shared_from_this(); } + + void connection_timeout(aux::listen_socket_handle const& s, error_code const& e); + void refresh_timeout(error_code const& e); + void refresh_key(error_code const& e); + void update_storage_node_ids(); + node* get_node(node_id const& id, std::string const& family_name); + + // implements socket_manager + bool has_quota() override; + bool send_packet(aux::listen_socket_handle const& s, entry& e, udp::endpoint const& addr) override; + + // this is the bdecode_node DHT messages are parsed into. It's a member + // in order to avoid having to deallocate and re-allocate it for every + // message. + bdecode_node m_msg; + + counters& m_counters; + dht_storage_interface& m_storage; + dht_state m_state; // to be used only once + tracker_nodes_t m_nodes; + send_fun_t m_send_fun; + dht_observer* m_log; + + std::vector m_send_buf; + dos_blocker m_blocker; + + deadline_timer m_key_refresh_timer; + deadline_timer m_refresh_timer; + aux::session_settings const& m_settings; + + bool m_running; + + // used to resolve hostnames for nodes + udp::resolver m_host_resolver; + + // state for the send rate limit + int m_send_quota; + time_point m_last_tick; + + io_context& m_ioc; + }; +} // namespace dht +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/kademlia/direct_request.hpp b/include/libtorrent/kademlia/direct_request.hpp new file mode 100644 index 0000000..2908d89 --- /dev/null +++ b/include/libtorrent/kademlia/direct_request.hpp @@ -0,0 +1,98 @@ +/* + +Copyright (c) 2014-2015, Steven Siloti +Copyright (c) 2015-2016, 2018, Alden Torres +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2016-2017, 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DIRECT_REQUEST_HPP +#define TORRENT_DIRECT_REQUEST_HPP + +#include +#include + +namespace libtorrent { +namespace dht { + +struct direct_traversal : traversal_algorithm +{ + using message_callback = std::function; + + direct_traversal(node& node + , node_id const& target + , message_callback cb) + : traversal_algorithm(node, target) + , m_cb(std::move(cb)) + {} + + char const* name() const override { return "direct_traversal"; } + + void invoke_cb(msg const& m) + { + if (m_cb) + { + m_cb(m); + m_cb = nullptr; + done(); + } + } + +protected: + message_callback m_cb; +}; + +struct direct_observer : observer +{ + direct_observer(std::shared_ptr algo + , udp::endpoint const& ep, node_id const& id) + : observer(std::move(algo), ep, id) + {} + + void reply(msg const& m) override + { + flags |= flag_done; + static_cast(algorithm())->invoke_cb(m); + } + + void timeout() override + { + if (flags & flag_done) return; + flags |= flag_done; + bdecode_node e; + msg m(e, target_ep()); + static_cast(algorithm())->invoke_cb(m); + } +}; + +} // namespace dht +} // namespace libtorrent + +#endif //TORRENT_DIRECT_REQUEST_HPP diff --git a/include/libtorrent/kademlia/dos_blocker.hpp b/include/libtorrent/kademlia/dos_blocker.hpp new file mode 100644 index 0000000..4d81acf --- /dev/null +++ b/include/libtorrent/kademlia/dos_blocker.hpp @@ -0,0 +1,94 @@ +/* + +Copyright (c) 2010, 2013-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DHT_DOS_BLOCKER +#define TORRENT_DHT_DOS_BLOCKER + +#include "libtorrent/config.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent { +namespace dht { + + struct dht_logger; + + // this is a class that maintains a list of abusive DHT nodes, + // blocking their access to our DHT node. + struct TORRENT_EXTRA_EXPORT dos_blocker + { + dos_blocker(); + + // called every time we receive an incoming packet. Returns + // true if we should let the packet through, and false if + // it's blocked + bool incoming(address const& addr, time_point now, dht_logger* logger); + + void set_rate_limit(int l) + { + m_message_rate_limit = std::max(1, l); + } + + void set_block_timer(int t) + { + m_block_timeout = std::max(1, t); + } + + private: + + // used to ignore abusive dht nodes + struct node_ban_entry + { + node_ban_entry(): count(0) {} + address src; + time_point limit; + int count; + }; + + static constexpr int num_ban_nodes = 20; + + // the max number of packets we can receive per second from a node before + // we block it. + int m_message_rate_limit; + + // the number of seconds a node gets blocked for when it exceeds the rate + // limit + int m_block_timeout; + + node_ban_entry m_ban_nodes[num_ban_nodes]; + }; +} +} + +#endif diff --git a/include/libtorrent/kademlia/ed25519.hpp b/include/libtorrent/kademlia/ed25519.hpp new file mode 100644 index 0000000..c752459 --- /dev/null +++ b/include/libtorrent/kademlia/ed25519.hpp @@ -0,0 +1,105 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_ED25519_HPP +#define LIBTORRENT_ED25519_HPP + +#include +#include +#include + +#include +#include + +namespace libtorrent { +namespace dht { + + // See documentation of internal random_bytes + TORRENT_EXPORT std::array ed25519_create_seed(); + + // Creates a new key pair from the given seed. + // + // It's important to clarify that the seed completely determines + // the key pair. Then it's enough to save the seed and the + // public key as the key-pair in a buffer of 64 bytes. The standard + // is (32 bytes seed, 32 bytes public key). + // + // This function does work with a given seed, giving you a pair of + // (64 bytes private key, 32 bytes public key). It's a trade-off between + // space and CPU, saving in one format or another. + // + // The smaller format is not weaker by any means, in fact, it is only + // the seed (32 bytes) that determines the point in the curve. + TORRENT_EXPORT std::tuple ed25519_create_keypair( + std::array const& seed); + + // Creates a signature of the given message with the given key pair. + TORRENT_EXPORT signature ed25519_sign(span msg + , public_key const& pk, secret_key const& sk); + + // Verifies the signature on the given message using ``pk`` + TORRENT_EXPORT bool ed25519_verify(signature const& sig + , span msg, public_key const& pk); + + // Adds a scalar to the given key pair where scalar is a 32 byte buffer + // (possibly generated with `ed25519_create_seed`), generating a new key pair. + // + // You can calculate the public key sum without knowing the private key and + // vice versa by passing in null for the key you don't know. This is useful + // when a third party (an authoritative server for example) needs to enforce + // randomness on a key pair while only knowing the public key of the other + // side. + // + // Warning: the last bit of the scalar is ignored - if comparing scalars make + // sure to clear it with `scalar[31] &= 127`. + // + // see http://crypto.stackexchange.com/a/6215/4697 + // see test_ed25519 for a practical example + TORRENT_EXPORT public_key ed25519_add_scalar(public_key const& pk + , std::array const& scalar); + TORRENT_EXPORT secret_key ed25519_add_scalar(secret_key const& sk + , std::array const& scalar); + + // Performs a key exchange on the given public key and private key, producing a + // shared secret. It is recommended to hash the shared secret before using it. + // + // This is useful when two parties want to share a secret but both only knows + // their respective public keys. + // see test_ed25519 for a practical example + TORRENT_EXPORT std::array ed25519_key_exchange( + public_key const& pk, secret_key const& sk); + +} +} + +#endif // LIBTORRENT_ED25519_HPP diff --git a/include/libtorrent/kademlia/find_data.hpp b/include/libtorrent/kademlia/find_data.hpp new file mode 100644 index 0000000..72cbf54 --- /dev/null +++ b/include/libtorrent/kademlia/find_data.hpp @@ -0,0 +1,91 @@ +/* + +Copyright (c) 2006, Daniel Wallin +Copyright (c) 2006-2010, 2013-2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef FIND_DATA_050323_HPP +#define FIND_DATA_050323_HPP + +#include +#include +#include +#include + +#include +#include + +namespace libtorrent { +namespace dht { + +class node; + +// -------- find data ----------- + +struct find_data : traversal_algorithm +{ + using nodes_callback = std::function> const&)>; + + find_data(node& dht_node, node_id const& target + , nodes_callback ncallback); + + void got_write_token(node_id const& n, std::string write_token); + + void start() override; + + char const* name() const override; + +protected: + + void done() override; + observer_ptr new_observer(udp::endpoint const& ep + , node_id const& id) override; + + nodes_callback m_nodes_callback; + std::map m_write_tokens; + bool m_done; +}; + +struct find_data_observer : traversal_observer +{ + find_data_observer( + std::shared_ptr algorithm + , udp::endpoint const& ep, node_id const& id) + : traversal_observer(std::move(algorithm), ep, id) + {} + + void reply(msg const&) override; +}; + +} // namespace dht +} // namespace libtorrent + +#endif // FIND_DATA_050323_HPP diff --git a/include/libtorrent/kademlia/get_item.hpp b/include/libtorrent/kademlia/get_item.hpp new file mode 100644 index 0000000..b31b34a --- /dev/null +++ b/include/libtorrent/kademlia/get_item.hpp @@ -0,0 +1,98 @@ +/* + +Copyright (c) 2013, Steven Siloti +Copyright (c) 2013-2019, Arvid Norberg +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_GET_ITEM_HPP +#define LIBTORRENT_GET_ITEM_HPP + +#include +#include + +#include + +namespace libtorrent { +namespace dht { + +class get_item : public find_data +{ +public: + using data_callback = std::function; + + void got_data(bdecode_node const& v, + public_key const& pk, + sequence_number seq, + signature const& sig); + + // for immutable items + get_item(node& dht_node + , node_id const& target + , data_callback dcallback + , nodes_callback ncallback); + + // for mutable items + get_item(node& dht_node + , public_key const& pk + , span salt + , data_callback dcallback + , nodes_callback ncallback); + + char const* name() const override; + +protected: + observer_ptr new_observer(udp::endpoint const& ep + , node_id const& id) override; + bool invoke(observer_ptr o) override; + void done() override; + + data_callback m_data_callback; + item m_data; + bool m_immutable; +}; + +class get_item_observer : public find_data_observer +{ +public: + get_item_observer( + std::shared_ptr algorithm + , udp::endpoint const& ep, node_id const& id) + : find_data_observer(std::move(algorithm), ep, id) + {} + + void reply(msg const&) override; +}; + +} // namespace dht +} // namespace libtorrent + +#endif // LIBTORRENT_GET_ITEM_HPP diff --git a/include/libtorrent/kademlia/get_peers.hpp b/include/libtorrent/kademlia/get_peers.hpp new file mode 100644 index 0000000..817c4cd --- /dev/null +++ b/include/libtorrent/kademlia/get_peers.hpp @@ -0,0 +1,115 @@ +/* + +Copyright (c) 2006, Daniel Wallin +Copyright (c) 2013, 2017-2019, Arvid Norberg +Copyright (c) 2016, 2018, Alden Torres +Copyright (c) 2016, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_GET_PEERS_HPP +#define LIBTORRENT_GET_PEERS_HPP + +#include + +namespace libtorrent { +namespace dht { + +struct get_peers : find_data +{ + using data_callback = std::function const&)>; + + void got_peers(std::vector const& peers); + + get_peers(node& dht_node, node_id const& target + , data_callback dcallback + , nodes_callback ncallback + , bool noseeds); + + char const* name() const override; + +protected: + bool invoke(observer_ptr o) override; + observer_ptr new_observer(udp::endpoint const& ep + , node_id const& id) override; + + data_callback m_data_callback; + bool m_noseeds; +}; + +struct obfuscated_get_peers : get_peers +{ + obfuscated_get_peers(node& dht_node, node_id const& target + , data_callback dcallback + , nodes_callback ncallback + , bool noseeds); + + char const* name() const override; + +protected: + + observer_ptr new_observer(udp::endpoint const& ep, + node_id const& id) override; + bool invoke(observer_ptr o) override; + void done() override; +private: + // when set to false, we no longer obfuscate + // the target hash, and send regular get_peers + bool m_obfuscated; +}; + +struct get_peers_observer : find_data_observer +{ + get_peers_observer( + std::shared_ptr algorithm + , udp::endpoint const& ep, node_id const& id) + : find_data_observer(std::move(algorithm), ep, id) + {} + + void reply(msg const&) override; +#ifndef TORRENT_DISABLE_LOGGING +private: + void log_peers(msg const& m, bdecode_node const& r, int size) const; +#endif +}; + +struct obfuscated_get_peers_observer : traversal_observer +{ + obfuscated_get_peers_observer( + std::shared_ptr algorithm + , udp::endpoint const& ep, node_id const& id) + : traversal_observer(std::move(algorithm), ep, id) + {} + void reply(msg const&) override; +}; + +} // namespace dht +} // namespace libtorrent + +#endif // LIBTORRENT_GET_PEERS_HPP diff --git a/include/libtorrent/kademlia/io.hpp b/include/libtorrent/kademlia/io.hpp new file mode 100644 index 0000000..f3e7a93 --- /dev/null +++ b/include/libtorrent/kademlia/io.hpp @@ -0,0 +1,65 @@ +/* + +Copyright (c) 2016, Steven Siloti +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. +* Neither the name of the author nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef KADEMLIA_IO_HPP +#define KADEMLIA_IO_HPP + +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/socket_io.hpp" + +namespace libtorrent { +namespace dht { + + struct node_endpoint + { + node_id id; + udp::endpoint ep; + }; + + template + node_endpoint read_node_endpoint(udp protocol, InIt&& in) + { + node_endpoint ep; + std::copy(in, in + 20, ep.id.begin()); + in += 20; + if (protocol == udp::v6()) + ep.ep = aux::read_v6_endpoint(in); + else + ep.ep = aux::read_v4_endpoint(in); + return ep; + } + +} +} + +#endif diff --git a/include/libtorrent/kademlia/item.hpp b/include/libtorrent/kademlia/item.hpp new file mode 100644 index 0000000..d440009 --- /dev/null +++ b/include/libtorrent/kademlia/item.hpp @@ -0,0 +1,130 @@ +/* + +Copyright (c) 2013-2019, Steven Siloti +Copyright (c) 2013-2016, 2018-2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_ITEM_HPP +#define LIBTORRENT_ITEM_HPP + +#include +#include +#include +#include +#include + +namespace libtorrent { +namespace dht { + +// calculate the target hash for an immutable item. +TORRENT_EXTRA_EXPORT sha1_hash item_target_id(span v); + +// calculate the target hash for a mutable item. +TORRENT_EXTRA_EXPORT sha1_hash item_target_id(span salt + , public_key const& pk); + +TORRENT_EXTRA_EXPORT bool verify_mutable_item( + span v + , span salt + , sequence_number seq + , public_key const& pk + , signature const& sig); + +// TODO: since this is a public function, it should probably be moved +// out of this header and into one with other public functions. + +// given a byte range ``v`` and an optional byte range ``salt``, a +// sequence number, public key ``pk`` (must be 32 bytes) and a secret key +// ``sk`` (must be 64 bytes), this function produces a signature which +// is written into a 64 byte buffer pointed to by ``sig``. The caller +// is responsible for allocating the destination buffer that's passed in +// as the ``sig`` argument. Typically it would be allocated on the stack. +TORRENT_EXPORT signature sign_mutable_item( + span v + , span salt + , sequence_number seq + , public_key const& pk + , secret_key const& sk); + +class TORRENT_EXTRA_EXPORT item +{ +public: + item() {} + item(public_key const& pk, span salt); + explicit item(entry v); + item(entry v + , span salt + , sequence_number seq + , public_key const& pk + , secret_key const& sk); + explicit item(bdecode_node const& v); + + void assign(entry v); + void assign(entry v, span salt + , sequence_number seq + , public_key const& pk + , secret_key const& sk); + void assign(bdecode_node const& v); + bool assign(bdecode_node const& v, span salt + , sequence_number seq + , public_key const& pk + , signature const& sig); + void assign(entry v, span salt + , sequence_number seq + , public_key const& pk + , signature const& sig); + + void clear() { m_value = entry(); } + bool empty() const { return m_value.type() == entry::undefined_t; } + + bool is_mutable() const { return m_mutable; } + + entry const& value() const { return m_value; } + public_key const& pk() const + { return m_pk; } + signature const& sig() const + { return m_sig; } + sequence_number seq() const { return m_seq; } + std::string const& salt() const { return m_salt; } + +private: + entry m_value; + std::string m_salt; + public_key m_pk; + signature m_sig; + sequence_number m_seq{0}; + bool m_mutable = false; +}; + +} // namespace dht +} // namespace libtorrent + +#endif // LIBTORRENT_ITEM_HPP diff --git a/include/libtorrent/kademlia/msg.hpp b/include/libtorrent/kademlia/msg.hpp new file mode 100644 index 0000000..30220fe --- /dev/null +++ b/include/libtorrent/kademlia/msg.hpp @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2007, 2009, 2015, 2019, Arvid Norberg +Copyright (c) 2015, Steven Siloti +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_KADEMLIA_MSG_HPP +#define TORRENT_KADEMLIA_MSG_HPP + +#include "libtorrent/socket.hpp" +#include "libtorrent/span.hpp" + +namespace libtorrent { + +struct bdecode_node; + +namespace dht { + +struct msg +{ + msg(bdecode_node const& m, udp::endpoint const& ep): message(m), addr(ep) {} + + // explicitly disallow assignment, to silence msvc warning + msg& operator=(msg const&) = delete; + + // the message + bdecode_node const& message; + + // the address of the process sending or receiving + // the message. + udp::endpoint addr; +}; + +struct key_desc_t +{ + char const* name; + int type; + int size; + int flags; + + enum { + // this argument is optional, parsing will not + // fail if it's not present + optional = 1, + // for dictionaries, the following entries refer + // to child nodes to this node, up until and including + // the next item that has the last_child flag set. + // these flags are nestable + parse_children = 2, + // this is the last item in a child dictionary + last_child = 4, + // the size argument refers to that the size + // has to be divisible by the number, instead + // of having that exact size + size_divisible = 8 + }; +}; + +// TODO: move this to its own .hpp/.cpp pair? +TORRENT_EXTRA_EXPORT bool verify_message_impl(bdecode_node const& message, span desc + , span ret, span error); + +// verifies that a message has all the required +// entries and returns them in ret +template +bool verify_message(bdecode_node const& msg, key_desc_t const (&desc)[Size] + , bdecode_node (&ret)[Size], span error) +{ + return verify_message_impl(msg, desc, ret, error); +} + +} +} + +#endif diff --git a/include/libtorrent/kademlia/node.hpp b/include/libtorrent/kademlia/node.hpp new file mode 100644 index 0000000..234d051 --- /dev/null +++ b/include/libtorrent/kademlia/node.hpp @@ -0,0 +1,305 @@ +/* + +Copyright (c) 2006-2021, Arvid Norberg +Copyright (c) 2014-2017, Steven Siloti +Copyright (c) 2015-2017, Alden Torres +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2020, Fonic +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef NODE_HPP +#define NODE_HPP + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include // for udp::endpoint +#include +#include + +// for dht_lookup and dht_routing_bucket +#include + +namespace libtorrent { + struct counters; +} + +namespace libtorrent { +namespace dht { + +struct traversal_algorithm; +struct dht_observer; +struct msg; +struct settings; + +TORRENT_EXTRA_EXPORT entry write_nodes_entry(std::vector const& nodes); + +class announce_observer : public observer +{ +public: + announce_observer(std::shared_ptr algo + , udp::endpoint const& ep, node_id const& id) + : observer(std::move(algo), ep, id) + {} + + void reply(msg const&) override { flags |= flag_done; } +}; + +struct socket_manager +{ + virtual bool has_quota() = 0; + virtual bool send_packet(aux::listen_socket_handle const& s, entry& e, udp::endpoint const& addr) = 0; +protected: + ~socket_manager() = default; +}; + +// get the closest node to the id with the given family_name +using get_foreign_node_t = std::function; + +struct dht_status +{ + node_id our_id; + udp::endpoint local_endpoint; + std::vector table; + std::vector requests; +}; + +class TORRENT_EXTRA_EXPORT node +{ +public: + node(aux::listen_socket_handle const& sock, socket_manager* sock_man + , aux::session_settings const& settings + , node_id const& nid + , dht_observer* observer, counters& cnt + , get_foreign_node_t get_foreign_node + , dht_storage_interface& storage); + + ~node(); + + node(node const&) = delete; + node& operator=(node const&) = delete; + node(node&&) = delete; + node& operator=(node&&) = delete; + + void update_node_id(); + + void tick(); + void bootstrap(std::vector const& nodes + , find_data::nodes_callback const& f); + void add_router_node(udp::endpoint const& router); + + void unreachable(udp::endpoint const& ep); + void incoming(aux::listen_socket_handle const& s, msg const& m); + +#if TORRENT_ABI_VERSION == 1 + int num_torrents() const { return int(m_storage.num_torrents()); } + int num_peers() const { return int(m_storage.num_peers()); } +#endif + + int bucket_size(int bucket); + + node_id const& nid() const { return m_id; } + +#ifndef TORRENT_DISABLE_LOGGING + std::uint32_t search_id() { return m_search_id++; } +#endif + + std::tuple size() const { return m_table.size(); } + std::int64_t num_global_nodes() const + { return m_table.num_global_nodes(); } + +#if TORRENT_ABI_VERSION == 1 + int data_size() const { return int(m_storage.num_torrents()); } +#endif + + void get_peers(sha1_hash const& info_hash + , std::function const&)> dcallback + , std::function> const&)> ncallback + , announce_flags_t flags); + void announce(sha1_hash const& info_hash, int listen_port, announce_flags_t flags + , std::function const&)> f); + + void direct_request(udp::endpoint const& ep, entry& e + , std::function f); + + void get_item(sha1_hash const& target, std::function f); + void get_item(public_key const& pk, std::string const& salt, std::function f); + + void put_item(sha1_hash const& target, entry const& data, std::function f); + void put_item(public_key const& pk, std::string const& salt + , std::function f + , std::function data_cb); + + void sample_infohashes(udp::endpoint const& ep, sha1_hash const& target + , std::function + , std::vector>)> f); + + bool verify_token(string_view token, sha1_hash const& info_hash + , udp::endpoint const& addr) const; + + std::string generate_token(udp::endpoint const& addr, sha1_hash const& info_hash); + + // the returned time is the delay until connection_timeout() + // should be called again the next time + time_duration connection_timeout(); + + // generates a new secret number used to generate write tokens + void new_write_key(); + + // pings the given node, and adds it to + // the routing table if it response and if the + // bucket is not full. + void add_node(udp::endpoint const& node); + + int branch_factor() const; + + void add_traversal_algorithm(traversal_algorithm* a) + { + std::lock_guard l(m_mutex); + m_running_requests.insert(a); + } + + void remove_traversal_algorithm(traversal_algorithm* a) + { + std::lock_guard l(m_mutex); + m_running_requests.erase(a); + } + + dht_status status() const; + + std::tuple get_stats_counters() const; + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + void status(libtorrent::session_status& s); +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + + aux::session_settings const& settings() const { return m_settings; } + counters& stats_counters() const { return m_counters; } + + dht_observer* observer() const { return m_observer; } + + udp protocol() const { return m_protocol.protocol; } + char const* protocol_family_name() const { return m_protocol.family_name; } + char const* protocol_nodes_key() const { return m_protocol.nodes_key; } + + bool native_address(udp::endpoint const& ep) const + { return ep.protocol().family() == m_protocol.protocol.family(); } + bool native_address(tcp::endpoint const& ep) const + { return ep.protocol().family() == m_protocol.protocol.family(); } + bool native_address(address const& addr) const + { + return (addr.is_v4() && m_protocol.protocol == udp::v4()) + || (addr.is_v6() && m_protocol.protocol == udp::v6()); + } + +private: + + void send_single_refresh(udp::endpoint const& ep, int bucket + , node_id const& id = node_id()); + bool lookup_peers(sha1_hash const& info_hash, entry& reply + , bool noseed, bool scrape, address const& requester) const; + + aux::session_settings const& m_settings; + + mutable std::mutex m_mutex; + + // this list must be destructed after the rpc manager + // since it might have references to it + std::set m_running_requests; + + void incoming_request(msg const&, entry&); + + void write_nodes_entries(sha1_hash const& info_hash + , bdecode_node const& want, entry& r); + + node_id m_id; + +public: + routing_table m_table; + rpc_manager m_rpc; + aux::listen_socket_handle const m_sock; + +private: + + struct protocol_descriptor + { + udp protocol; + char const* family_name; + char const* nodes_key; + }; + + static protocol_descriptor const& map_protocol_to_descriptor(udp protocol); + + socket_manager* m_sock_man; + + get_foreign_node_t m_get_foreign_node; + + dht_observer* m_observer; + + protocol_descriptor const& m_protocol; + + time_point m_last_tracker_tick; + + // the last time we issued a bootstrap or a refresh on our own ID, to expand + // the routing table buckets close to us. + time_point m_last_self_refresh; + + // secret random numbers used to create write tokens + std::array m_secret[2]; + + counters& m_counters; + + dht_storage_interface& m_storage; + +#ifndef TORRENT_DISABLE_LOGGING + std::uint32_t m_search_id = 0; +#endif +}; + +} // namespace dht +} // namespace libtorrent + +#endif // NODE_HPP diff --git a/include/libtorrent/kademlia/node_entry.hpp b/include/libtorrent/kademlia/node_entry.hpp new file mode 100644 index 0000000..fba9ba9 --- /dev/null +++ b/include/libtorrent/kademlia/node_entry.hpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2006, 2008-2009, 2013-2016, 2019, Arvid Norberg +Copyright (c) 2015, Steven Siloti +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef KADEMLIA_NODE_ENTRY_HPP +#define KADEMLIA_NODE_ENTRY_HPP + +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/union_endpoint.hpp" +#include "libtorrent/time.hpp" // for time_point +#include "libtorrent/aux_/time.hpp" // for time_now + +namespace libtorrent { +namespace dht { + +struct TORRENT_EXTRA_EXPORT node_entry +{ + node_entry(node_id const& id_, udp::endpoint const& ep, int roundtriptime = 0xffff + , bool pinged = false); + explicit node_entry(udp::endpoint const& ep); + node_entry() = default; + void update_rtt(int new_rtt); + + bool pinged() const { return timeout_count != 0xff; } + void set_pinged() { if (timeout_count == 0xff) timeout_count = 0; } + void timed_out() { if (pinged() && timeout_count < 0xfe) ++timeout_count; } + int fail_count() const { return pinged() ? timeout_count : 0; } + void reset_fail_count() { if (pinged()) timeout_count = 0; } + udp::endpoint ep() const { return endpoint; } + bool confirmed() const { return timeout_count == 0; } + address addr() const { return endpoint.address(); } + int port() const { return endpoint.port; } + + // compares which node_entry is "better". Smaller is better + bool operator<(node_entry const& rhs) const + { + return std::make_tuple(!verified, rtt) < std::make_tuple(!rhs.verified, rhs.rtt); + } + +#ifndef TORRENT_DISABLE_LOGGING + time_point first_seen = aux::time_now(); +#endif + + // the time we last received a response for a request to this peer + time_point last_queried = min_time(); + + node_id id{nullptr}; + + union_endpoint endpoint; + + // the average RTT of this node + std::uint16_t rtt = 0xffff; + + // the number of times this node has failed to + // respond in a row + // 0xff is a special value to indicate we have not pinged this node yet + std::uint8_t timeout_count = 0xff; + + bool verified = false; +}; + +} // namespace dht +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/kademlia/node_id.hpp b/include/libtorrent/kademlia/node_id.hpp new file mode 100644 index 0000000..3185df0 --- /dev/null +++ b/include/libtorrent/kademlia/node_id.hpp @@ -0,0 +1,77 @@ +/* + +Copyright (c) 2006, 2008, 2013, 2015-2016, 2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef NODE_ID_HPP +#define NODE_ID_HPP + +#include +#include + +#include +#include +#include + +namespace libtorrent { +namespace dht { + +using node_id = libtorrent::sha1_hash; + +// returns the distance between the two nodes +// using the kademlia XOR-metric +TORRENT_EXTRA_EXPORT node_id distance(node_id const& n1, node_id const& n2); + +// returns true if: distance(n1, ref) < distance(n2, ref) +TORRENT_EXTRA_EXPORT bool compare_ref(node_id const& n1, node_id const& n2, node_id const& ref); + +// returns n in: 2^n <= distance(n1, n2) < 2^(n+1) +// useful for finding out which bucket a node belongs to +// the value that's returned is the number of trailing bits +// after the shared bit prefix of ``n1`` and ``n2``. +// if the first bits are different, that's 160. +TORRENT_EXTRA_EXPORT int distance_exp(node_id const& n1, node_id const& n2); +TORRENT_EXTRA_EXPORT int min_distance_exp(node_id const& n1, std::vector const& ids); + +TORRENT_EXTRA_EXPORT node_id generate_id(address const& external_ip); +TORRENT_EXTRA_EXPORT node_id generate_random_id(); +TORRENT_EXTRA_EXPORT void make_id_secret(node_id& in); +TORRENT_EXTRA_EXPORT node_id generate_secret_id(); +TORRENT_EXTRA_EXPORT bool verify_secret_id(node_id const& nid); +TORRENT_EXTRA_EXPORT node_id generate_id_impl(address const& ip_, std::uint32_t r); + +TORRENT_EXTRA_EXPORT bool verify_id(node_id const& nid, address const& source_ip); +TORRENT_EXTRA_EXPORT bool matching_prefix(node_id const& nid, int mask, int prefix, int offset); +TORRENT_EXTRA_EXPORT node_id generate_prefix_mask(int bits); + +} // namespace dht +} // namespace libtorrent + +#endif // NODE_ID_HPP diff --git a/include/libtorrent/kademlia/observer.hpp b/include/libtorrent/kademlia/observer.hpp new file mode 100644 index 0000000..0ef35a1 --- /dev/null +++ b/include/libtorrent/kademlia/observer.hpp @@ -0,0 +1,171 @@ +/* + +Copyright (c) 2007-2010, 2013-2019, Arvid Norberg +Copyright (c) 2014, Steven Siloti +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef OBSERVER_HPP +#define OBSERVER_HPP + +#include +#include + +#include +#include +#include +#include // for udp +#include + +namespace libtorrent { +namespace dht { + +struct dht_observer; +struct observer; +struct msg; +struct traversal_algorithm; + +using observer_flags_t = libtorrent::flags::bitfield_flag; + +struct TORRENT_EXTRA_EXPORT observer + : std::enable_shared_from_this +{ + observer(std::shared_ptr a + , udp::endpoint const& ep, node_id const& id) + : m_algorithm(std::move(a)) + , m_id(id) + { + TORRENT_ASSERT(!m_was_sent); + TORRENT_ASSERT(m_algorithm); + set_target(ep); + } + + observer(observer const&) = delete; + observer& operator=(observer const&) = delete; + + // defined in rpc_manager.cpp + virtual ~observer(); + + // this is called when a reply is received + virtual void reply(msg const& m) = 0; + + // this is called if no response has been received after + // a few seconds, before the request has timed out + void short_timeout(); + + bool has_short_timeout() const { return bool(flags & flag_short_timeout); } + + // this is called when no reply has been received within + // some timeout, or a reply with incorrect format. + virtual void timeout(); + + // if this is called the destructor should + // not invoke any new messages, and should + // only clean up. It means the rpc-manager + // is being destructed + void abort(); + + dht_observer* get_observer() const; + + traversal_algorithm* algorithm() const { return m_algorithm.get(); } + + time_point sent() const { return m_sent; } + + void set_target(udp::endpoint const& ep); + address target_addr() const; + udp::endpoint target_ep() const; + + void set_id(node_id const& id); + node_id const& id() const { return m_id; } + + // an entry that has the queried flag set will have incremented the m_invoke_count + // and is supposed to decrement it, once a response is received. It + // will also have sent its query to its node + static constexpr observer_flags_t flag_queried = 0_bit; + static constexpr observer_flags_t flag_initial = 1_bit; + static constexpr observer_flags_t flag_no_id = 2_bit; + // after a short timeout, we may increase the branch factor and set + // this flag. We still wait for the full timeout for a response. + // Incrementing the branch factor is a middle-ground of no longer + // having much faith in this node responding (so we allow another query + // to use its "slot"). When the request completes (either by receiving + // a response or timing out), we need to restore the branch factor by + // decrementing it. + static constexpr observer_flags_t flag_short_timeout = 3_bit; + // a node + static constexpr observer_flags_t flag_failed = 4_bit; + // This determines whether the m_addr union has the IPv4 or IPv6 address active + static constexpr observer_flags_t flag_ipv6_address = 5_bit; + // This means we have received a response from this node + static constexpr observer_flags_t flag_alive = 6_bit; + // the request has been cancelled + static constexpr observer_flags_t flag_done = 7_bit; + +protected: + + void done(); + +private: + + std::shared_ptr self() + { return shared_from_this(); } + + time_point m_sent; + + std::shared_ptr const m_algorithm; + + node_id m_id; + + union addr_t + { + address_v6::bytes_type v6; + address_v4::bytes_type v4; + } m_addr; + + std::uint16_t m_port = 0; + +public: + observer_flags_t flags{}; + +#if TORRENT_USE_ASSERTS + bool m_in_constructor = true; + bool m_was_sent = false; + bool m_was_abandoned = false; + bool m_in_use = true; +#endif +}; + +using observer_ptr = std::shared_ptr; + +} +} + +#endif diff --git a/include/libtorrent/kademlia/put_data.hpp b/include/libtorrent/kademlia/put_data.hpp new file mode 100644 index 0000000..25c2675 --- /dev/null +++ b/include/libtorrent/kademlia/put_data.hpp @@ -0,0 +1,93 @@ +/* + +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2016, 2018, Alden Torres +Copyright (c) 2016-2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PUT_DATA_HPP +#define TORRENT_PUT_DATA_HPP + +#include +#include +#include +#include + +#include + +namespace libtorrent { +namespace dht { + +struct msg; +class node; + +struct put_data: traversal_algorithm +{ + using put_callback = std::function; + + put_data(node& node, put_callback callback); + + char const* name() const override; + void start() override; + + void set_data(item&& data) { m_data = std::move(data); } + void set_data(item const& data) = delete; + + void set_targets(std::vector> const& targets); + +protected: + + void done() override; + bool invoke(observer_ptr o) override; + + put_callback m_put_callback; + item m_data; + bool m_done = false; +}; + +struct put_data_observer : traversal_observer +{ + put_data_observer( + std::shared_ptr algorithm + , udp::endpoint const& ep, node_id const& id, std::string token) + : traversal_observer(std::move(algorithm), ep, id) + , m_token(std::move(token)) + { + } + + void reply(msg const&) override { done(); } + + std::string m_token; +}; + +} // namespace dht +} // namespace libtorrent + +#endif // TORRENT_PUT_DATA_HPP diff --git a/include/libtorrent/kademlia/refresh.hpp b/include/libtorrent/kademlia/refresh.hpp new file mode 100644 index 0000000..62e426c --- /dev/null +++ b/include/libtorrent/kademlia/refresh.hpp @@ -0,0 +1,67 @@ +/* + +Copyright (c) 2006, Daniel Wallin +Copyright (c) 2006, 2008, 2010, 2013-2014, 2017-2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef REFRESH_050324_HPP +#define REFRESH_050324_HPP + +#include +#include + +namespace libtorrent { +namespace dht { + +class bootstrap : public get_peers +{ +public: + using done_callback = get_peers::nodes_callback; + + bootstrap(node& dht_node, node_id const& target + , done_callback const& callback); + char const* name() const override; + + observer_ptr new_observer(udp::endpoint const& ep + , node_id const& id) override; + +protected: + + bool invoke(observer_ptr o) override; + + void done() override; + +}; + +} // namespace dht +} // namespace libtorrent + +#endif // REFRESH_050324_HPP diff --git a/include/libtorrent/kademlia/routing_table.hpp b/include/libtorrent/kademlia/routing_table.hpp new file mode 100644 index 0000000..2503893 --- /dev/null +++ b/include/libtorrent/kademlia/routing_table.hpp @@ -0,0 +1,345 @@ +/* + +Copyright (c) 2006-2007, 2009-2019, 2021, Arvid Norberg +Copyright (c) 2015-2016, Steven Siloti +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef ROUTING_TABLE_HPP +#define ROUTING_TABLE_HPP + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace libtorrent { +namespace aux { + struct session_settings; +} +namespace dht { + +struct settings; +struct dht_logger; + +using bucket_t = aux::vector; + +struct routing_table_node +{ + bucket_t replacements; + bucket_t live_nodes; +}; + +struct ipv4_hash +{ + using argument_type = address_v4::bytes_type; + using result_type = std::size_t; + result_type operator()(argument_type const& ip) const + { + return std::hash()(*reinterpret_cast(&ip[0])); + } +}; + +struct ipv6_hash +{ + using argument_type = address_v6::bytes_type; + using result_type = std::size_t; + result_type operator()(argument_type const& ip) const + { + return std::hash()(*reinterpret_cast(&ip[0])); + } +}; + +struct TORRENT_EXTRA_EXPORT ip_set +{ + void insert(address const& addr); + bool exists(address const& addr) const; + void erase(address const& addr); + + void clear() + { + m_ip4s.clear(); + m_ip6s.clear(); + } + + bool operator==(ip_set const& rh) + { + return m_ip4s == rh.m_ip4s && m_ip6s == rh.m_ip6s; + } + + std::size_t size() const { return m_ip4s.size() + m_ip6s.size(); } + + // these must be multisets because there can be multiple routing table + // entries for a single IP when restrict_routing_ips is set to false + std::unordered_multiset m_ip4s; + std::unordered_multiset m_ip6s; +}; + +// Each routing table bucket represents node IDs with a certain number of bits +// of prefix in common with our own node ID. Each bucket fits 8 nodes (and +// sometimes more, closer to the top). In order to minimize the number of hops +// necessary to traverse the DHT, we want the nodes in our buckets to be spread +// out across all possible "sub-branches". This is what the "classify" refers +// to. The 3 (or more) bits following the shared bit prefix. +TORRENT_EXTRA_EXPORT std::uint8_t classify_prefix(int bucket_idx, bool last_bucket + , int bucket_size, node_id nid); + +TORRENT_EXTRA_EXPORT bool all_in_same_bucket(span b + , node_id const& id, int bucket_index); + +// differences in the implementation from the description in +// the paper: +// +// * Nodes are not marked as being stale, they keep a counter +// that tells how many times in a row they have failed. When +// a new node is to be inserted, the node that has failed +// the most times is replaced. If none of the nodes in the +// bucket has failed, then it is put in the replacement +// cache (just like in the paper). +// * The routing table bucket sizes are larger towards the "top" of the routing +// table. This is to get closer to the target in fewer round-trips. +// * Nodes with lower RTT are preferred and may replace nodes with higher RTT +// * Nodes that are "verified" (i.e. use a node-ID derived from their IP) are +// preferred and may replace nodes that are not verified. + +TORRENT_EXTRA_EXPORT bool mostly_verified_nodes(bucket_t const&); +TORRENT_EXTRA_EXPORT bool compare_ip_cidr(address const& lhs, address const& rhs); + +using find_nodes_flags_t = flags::bitfield_flag; + +class TORRENT_EXTRA_EXPORT routing_table +{ +public: + // TODO: 3 to improve memory locality and scanning performance, turn the + // routing table into a single vector with boundaries for the nodes instead. + // Perhaps replacement nodes should be in a separate vector. + using table_t = aux::vector; + + routing_table(node_id const& id, udp proto + , int bucket_size + , aux::session_settings const& settings + , dht_logger* log); + + routing_table(routing_table const&) = delete; + routing_table& operator=(routing_table const&) = delete; + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + void status(session_status& s) const; +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + + void status(std::vector& s) const; + + void node_failed(node_id const& id, udp::endpoint const& ep); + + // adds an endpoint that will never be added to + // the routing table + void add_router_node(udp::endpoint const& router); + + // iterates over the router nodes added + using router_iterator = std::set::const_iterator; + router_iterator begin() const { return m_router_nodes.begin(); } + router_iterator end() const { return m_router_nodes.end(); } + + enum add_node_status_t { + failed_to_add = 0, + node_added, + need_bucket_split + }; + add_node_status_t add_node_impl(node_entry e); + + bool add_node(node_entry const& e); + + // this function is called every time the node sees + // a sign of a node being alive. This node will either + // be inserted in the k-buckets or be moved to the top + // of its bucket. + bool node_seen(node_id const& id, udp::endpoint const& ep, int rtt); + + // this may add a node to the routing table and mark it as + // not pinged. If the bucket the node falls into is full, + // the node will be ignored. + void heard_about(node_id const& id, udp::endpoint const& ep); + + // change our node ID. This can be expensive since nodes must be moved around + // and potentially dropped + void update_node_id(node_id const& id); + + node_entry const* next_refresh(); + + // nodes that have not been pinged are considered failed by this flag + static constexpr find_nodes_flags_t include_failed = 0_bit; + + // fills the vector with the count nodes from our buckets that + // are nearest to the given id. + std::vector find_node(node_id const& target + , find_nodes_flags_t options, int count = 0); + void remove_node(node_entry* n, bucket_t* b); + + int bucket_size(int bucket) const + { + int num_buckets = int(m_buckets.size()); + if (num_buckets == 0) return 0; + if (bucket >= num_buckets) bucket = num_buckets - 1; + table_t::const_iterator i = m_buckets.begin(); + std::advance(i, bucket); + return int(i->live_nodes.size()); + } + + void for_each_node(std::function live_cb + , std::function replacements_cb) const; + + void for_each_node(std::function f) const + { for_each_node(f, f); } + + int bucket_size() const { return m_bucket_size; } + + // returns the number of nodes in the main buckets, number of nodes in the + // replacement buckets and the number of nodes in the main buckets that have + // been pinged and confirmed up + std::tuple size() const; + + std::int64_t num_global_nodes() const; + + // the number of bits down we have full buckets + // i.e. essentially the number of full buckets + // we have + int depth() const; + + int num_active_buckets() const { return int(m_buckets.size()); } + + int bucket_limit(int bucket) const; + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + bool is_full(int bucket) const; + + bool native_address(address const& addr) const + { + return (addr.is_v4() && m_protocol == udp::v4()) + || (addr.is_v6() && m_protocol == udp::v6()); + } + + bool native_endpoint(udp::endpoint const& ep) const + { return ep.protocol() == m_protocol; } + + node_id const& id() const + { return m_id; } + + table_t const& buckets() const + { return m_buckets; } + +private: + +#ifndef TORRENT_DISABLE_LOGGING + dht_logger* m_log; + void log_node_failed(node_id const& nid, node_entry const& ne) const; +#endif + + table_t::iterator find_bucket(node_id const& id); + void remove_node_internal(node_entry* n, bucket_t& b); + + void split_bucket(); + + // return a pointer the node_entry with the given endpoint + // or 0 if we don't have such a node. Both the address and the + // port has to match + std::tuple + find_node(udp::endpoint const& ep); + + // if the bucket is not full, try to fill it with nodes from the + // replacement list + void fill_from_replacements(table_t::iterator bucket); + + void prune_empty_bucket(); + + aux::session_settings const& m_settings; + + // (k-bucket, replacement cache) pairs + // the first entry is the bucket the furthest + // away from our own ID. Each time the bucket + // closest to us (m_buckets.back()) has more than + // bucket size nodes in it, another bucket is + // added to the end and it's split up between them + table_t m_buckets; + + node_id m_id; // our own node id + udp m_protocol; // protocol this table is for + + // the last seen depth (i.e. levels in the routing table) + // it's mutable because it's updated by depth(), which is const + mutable int m_depth; + + // the last time we refreshed our own bucket + // refreshed every 15 minutes + mutable time_point m_last_self_refresh; + + // this is a set of all the endpoints that have + // been identified as router nodes. They will + // be used in searches, but they will never + // be added to the routing table. + std::set m_router_nodes; + + // these are all the IPs that are in the routing + // table. It's used to only allow a single entry + // per IP in the whole table. + ip_set m_ips; + + // constant called k in paper + int const m_bucket_size; +}; + +TORRENT_EXTRA_EXPORT routing_table::add_node_status_t +replace_node_impl(node_entry const& e, bucket_t& b, ip_set& ips + , int bucket_index, int bucket_size_limit, bool last_bucket +#ifndef TORRENT_DISABLE_LOGGING + , dht_logger* log +#endif + ); + +} } // namespace libtorrent::dht + +#endif // ROUTING_TABLE_HPP diff --git a/include/libtorrent/kademlia/rpc_manager.hpp b/include/libtorrent/kademlia/rpc_manager.hpp new file mode 100644 index 0000000..77485b6 --- /dev/null +++ b/include/libtorrent/kademlia/rpc_manager.hpp @@ -0,0 +1,147 @@ +/* + +Copyright (c) 2006-2017, 2019-2020, Arvid Norberg +Copyright (c) 2015, Thomas Yuan +Copyright (c) 2016, Alden Torres +Copyright (c) 2016-2017, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef RPC_MANAGER_HPP +#define RPC_MANAGER_HPP + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace libtorrent { +class entry; +namespace aux { + struct session_settings; +} +} + +namespace libtorrent { +namespace dht { + +struct settings; +struct dht_logger; +struct socket_manager; + +struct TORRENT_EXTRA_EXPORT null_observer : observer +{ + null_observer(std::shared_ptr a + , udp::endpoint const& ep, node_id const& id) + : observer(std::move(a), ep, id) {} + void reply(msg const&) override { flags |= flag_done; } +}; + +class routing_table; + +class TORRENT_EXTRA_EXPORT rpc_manager +{ +public: + + rpc_manager(node_id const& our_id + , aux::session_settings const& settings + , routing_table& table + , aux::listen_socket_handle sock + , socket_manager* sock_man + , dht_logger* log); + ~rpc_manager(); + + void unreachable(udp::endpoint const& ep); + + // returns true if the node needs a refresh + // if so, id is assigned the node id to refresh + bool incoming(msg const&, node_id* id); + time_duration tick(); + + bool invoke(entry& e, udp::endpoint const& target + , observer_ptr o); + + void add_our_id(entry& e); + +#if TORRENT_USE_ASSERTS + size_t allocation_size() const; +#endif +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + template + std::shared_ptr allocate_observer(Args&&... args) + { + void* ptr = allocate_observer(); + if (ptr == nullptr) return std::shared_ptr(); + + auto deleter = [this](observer* o) + { + TORRENT_ASSERT(o->m_in_use); + o->~observer(); + free_observer(o); + }; + return std::shared_ptr(new (ptr) T(std::forward(args)...), deleter); + } + + int num_allocated_observers() const { return m_allocated_observers; } + + void update_node_id(node_id const& id) { m_our_id = id; } + +private: + + void* allocate_observer(); + void free_observer(void* ptr); + + mutable lt::aux::pool m_pool_allocator; + + std::unordered_multimap m_transactions; + + aux::listen_socket_handle m_sock; + socket_manager* m_sock_man; +#ifndef TORRENT_DISABLE_LOGGING + dht_logger* m_log; +#endif + aux::session_settings const& m_settings; + routing_table& m_table; + node_id m_our_id; + std::uint32_t m_allocated_observers:31; + std::uint32_t m_destructing:1; +}; + +} // namespace dht +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/kademlia/sample_infohashes.hpp b/include/libtorrent/kademlia/sample_infohashes.hpp new file mode 100644 index 0000000..95f1345 --- /dev/null +++ b/include/libtorrent/kademlia/sample_infohashes.hpp @@ -0,0 +1,84 @@ +/* + +Copyright (c) 2017, Alden Torres +Copyright (c) 2017, 2019-2020, Arvid Norberg +Copyright (c) 2020, Fonic +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SAMPLE_INFOHASHES_HPP +#define TORRENT_SAMPLE_INFOHASHES_HPP + +#include + +#include +#include + +namespace libtorrent { +namespace dht { + +class sample_infohashes final : public traversal_algorithm +{ +public: + + using data_callback = std::function + , std::vector>)>; + + sample_infohashes(node& dht_node + , node_id const& target + , data_callback dcallback); + + char const* name() const override; + + void got_samples(sha1_hash const& nid + , time_duration interval + , int num, std::vector samples + , std::vector> nodes); + +protected: + + data_callback m_data_callback; +}; + +class sample_infohashes_observer final : public traversal_observer +{ +public: + + sample_infohashes_observer(std::shared_ptr algorithm + , udp::endpoint const& ep, node_id const& id); + + void reply(msg const&) override; +}; + +} // namespace dht +} // namespace libtorrent + +#endif // TORRENT_SAMPLE_INFOHASHES_HPP diff --git a/include/libtorrent/kademlia/traversal_algorithm.hpp b/include/libtorrent/kademlia/traversal_algorithm.hpp new file mode 100644 index 0000000..71f0019 --- /dev/null +++ b/include/libtorrent/kademlia/traversal_algorithm.hpp @@ -0,0 +1,189 @@ +/* + +Copyright (c) 2006, Daniel Wallin +Copyright (c) 2006, 2008-2010, 2013-2019, Arvid Norberg +Copyright (c) 2015, Steven Siloti +Copyright (c) 2016, Alden Torres +Copyright (c) 2016-2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TRAVERSAL_ALGORITHM_050324_HPP +#define TRAVERSAL_ALGORITHM_050324_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace libtorrent { + +namespace dht { + +class node; +struct node_endpoint; + +using traversal_flags_t = libtorrent::flags::bitfield_flag; + +// this class may not be instantiated as a stack object +struct TORRENT_EXTRA_EXPORT traversal_algorithm + : std::enable_shared_from_this +{ + void traverse(node_id const& id, udp::endpoint const& addr); + void finished(observer_ptr o); + + static constexpr traversal_flags_t short_timeout = 0_bit; + + void failed(observer_ptr o, traversal_flags_t flags = {}); + virtual ~traversal_algorithm(); + void status(dht_lookup& l); + + virtual char const* name() const; + virtual void start(); + + node_id const& target() const { return m_target; } + + void resort_result(observer*); + void add_entry(node_id const& id, udp::endpoint const& addr, observer_flags_t flags); + + traversal_algorithm(node& dht_node, node_id const& target); + traversal_algorithm(traversal_algorithm const&) = delete; + traversal_algorithm& operator=(traversal_algorithm const&) = delete; + int invoke_count() const { TORRENT_ASSERT(m_invoke_count >= 0); return m_invoke_count; } + int branch_factor() const { TORRENT_ASSERT(m_branch_factor >= 0); return m_branch_factor; } + + node& get_node() const { return m_node; } + + void abort() { m_abort = true; } + +#ifndef TORRENT_DISABLE_LOGGING + std::uint32_t id() const { return m_id; } +#endif + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + +protected: + + std::shared_ptr self() + { return shared_from_this(); } + + // returns true if we're done + bool add_requests(); + + void add_router_entries(); + void init(); + + virtual void done(); + // should construct an algorithm dependent + // observer in ptr. + virtual observer_ptr new_observer(udp::endpoint const& ep + , node_id const& id); + + virtual bool invoke(observer_ptr) { return false; } + + int num_responses() const { return m_responses; } + int num_timeouts() const { return m_timeouts; } + + node& m_node; + + // this vector is sorted by node-id distance from our node id. Closer nodes + // are earlier in the vector. However, not the entire vector is necessarily + // sorted, the tail of the vector may contain nodes out-of-order. This is + // used when bootstrapping. The ``m_sorted_results`` member indicates how + // many of the first elements are sorted. + std::vector m_results; + + int num_sorted_results() const { return m_sorted_results; } + +private: + + node_id const m_target; + std::int8_t m_invoke_count = 0; + std::int8_t m_branch_factor = 3; + // the number of elements at the beginning of m_results that are sorted by + // node_id. + std::int8_t m_sorted_results = 0; + std::int16_t m_responses = 0; + std::int16_t m_timeouts = 0; + + // set to true when done() is called, and will prevent adding new results, as + // they would never be serviced and the whole traversal algorithm would stall + // and leak + bool m_done = false; + + // abort is set when we're cancelling the remaining lookups. Whenever a + // lookup completes, we won't issue another one + bool m_abort = false; + +#ifndef TORRENT_DISABLE_LOGGING + // this is a unique ID for this specific traversal_algorithm instance, + // just used for logging + std::uint32_t m_id; +#endif + + // the IP addresses of the nodes in m_results + std::set m_peer4_prefixes; + std::set m_peer6_prefixes; +#ifndef TORRENT_DISABLE_LOGGING + void log_timeout(observer_ptr const& o, char const* prefix) const; +#endif +#if TORRENT_USE_ASSERTS + bool m_initialized = false; +#endif +}; + +void look_for_nodes(char const* nodes_key, udp const& protocol + , bdecode_node const& r, std::function f); + +struct traversal_observer : observer +{ + traversal_observer( + std::shared_ptr algorithm + , udp::endpoint const& ep, node_id const& id) + : observer(std::move(algorithm), ep, id) + {} + + // parses out "nodes" and keeps traversing + void reply(msg const&) override; +}; + +} // namespace dht +} // namespace libtorrent + +#endif // TRAVERSAL_ALGORITHM_050324_HPP diff --git a/include/libtorrent/kademlia/types.hpp b/include/libtorrent/kademlia/types.hpp new file mode 100644 index 0000000..75b7ab0 --- /dev/null +++ b/include/libtorrent/kademlia/types.hpp @@ -0,0 +1,100 @@ +/* + +Copyright (c) 2016, Alden Torres +Copyright (c) 2016-2017, 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_TYPES_HPP +#define LIBTORRENT_TYPES_HPP + +#include +#include +#include + +namespace libtorrent { +namespace dht { + + struct public_key + { + public_key() = default; + explicit public_key(char const* b) + { std::copy(b, b + len, bytes.begin()); } + bool operator==(public_key const& rhs) const + { return bytes == rhs.bytes; } + static constexpr int len = 32; + std::array bytes; + }; + + struct secret_key + { + secret_key() = default; + explicit secret_key(char const* b) + { std::copy(b, b + len, bytes.begin()); } + bool operator==(secret_key const& rhs) const + { return bytes == rhs.bytes; } + static constexpr int len = 64; + std::array bytes; + }; + + struct signature + { + signature() = default; + explicit signature(char const* b) + { std::copy(b, b + len, bytes.begin()); } + bool operator==(signature const& rhs) const + { return bytes == rhs.bytes; } + static constexpr int len = 64; + std::array bytes; + }; + + struct sequence_number + { + sequence_number() : value(0) {} + explicit sequence_number(std::int64_t v) : value(v) {} + sequence_number(sequence_number const& sqn) = default; + bool operator<(sequence_number rhs) const + { return value < rhs.value; } + bool operator>(sequence_number rhs) const + { return value > rhs.value; } + sequence_number& operator=(sequence_number rhs) & + { value = rhs.value; return *this; } + bool operator<=(sequence_number rhs) const + { return value <= rhs.value; } + bool operator==(sequence_number const& rhs) const + { return value == rhs.value; } + sequence_number& operator++() + { ++value; return *this; } + std::int64_t value; + }; + +} // namespace dht +} // namespace libtorrent + +#endif // LIBTORRENT_TYPES_HPP diff --git a/include/libtorrent/libtorrent.hpp b/include/libtorrent/libtorrent.hpp new file mode 100644 index 0000000..756ffb2 --- /dev/null +++ b/include/libtorrent/libtorrent.hpp @@ -0,0 +1,168 @@ + +// This header is generated by tools/gen_convenience_header.py + +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/bloom_filter.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/choker.hpp" +#include "libtorrent/client_data.hpp" +#include "libtorrent/close_reason.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/copy_ptr.hpp" +#include "libtorrent/crc32c.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/disabled_disk_io.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/disk_observer.hpp" +#include "libtorrent/download_priority.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/error.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/extensions/smart_ban.hpp" +#include "libtorrent/extensions/ut_metadata.hpp" +#include "libtorrent/extensions/ut_pex.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/fingerprint.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/gzip.hpp" +#include "libtorrent/hash_picker.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/hex.hpp" +#include "libtorrent/http_connection.hpp" +#include "libtorrent/http_parser.hpp" +#include "libtorrent/http_seed_connection.hpp" +#include "libtorrent/http_stream.hpp" +#include "libtorrent/http_tracker_connection.hpp" +#include "libtorrent/i2p_stream.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/index_range.hpp" +#include "libtorrent/info_hash.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/ip_voter.hpp" +#include "libtorrent/kademlia/announce_flags.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" +#include "libtorrent/kademlia/dht_settings.hpp" +#include "libtorrent/kademlia/dht_state.hpp" +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/kademlia/dht_tracker.hpp" +#include "libtorrent/kademlia/direct_request.hpp" +#include "libtorrent/kademlia/dos_blocker.hpp" +#include "libtorrent/kademlia/ed25519.hpp" +#include "libtorrent/kademlia/find_data.hpp" +#include "libtorrent/kademlia/get_item.hpp" +#include "libtorrent/kademlia/get_peers.hpp" +#include "libtorrent/kademlia/io.hpp" +#include "libtorrent/kademlia/item.hpp" +#include "libtorrent/kademlia/msg.hpp" +#include "libtorrent/kademlia/node.hpp" +#include "libtorrent/kademlia/node_entry.hpp" +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/observer.hpp" +#include "libtorrent/kademlia/put_data.hpp" +#include "libtorrent/kademlia/refresh.hpp" +#include "libtorrent/kademlia/routing_table.hpp" +#include "libtorrent/kademlia/rpc_manager.hpp" +#include "libtorrent/kademlia/sample_infohashes.hpp" +#include "libtorrent/kademlia/traversal_algorithm.hpp" +#include "libtorrent/kademlia/types.hpp" +#include "libtorrent/link.hpp" +#include "libtorrent/lsd.hpp" +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/mmap_disk_io.hpp" +#include "libtorrent/mmap_storage.hpp" +#include "libtorrent/natpmp.hpp" +#include "libtorrent/netlink.hpp" +#include "libtorrent/operations.hpp" +#include "libtorrent/optional.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/part_file.hpp" +#include "libtorrent/pe_crypto.hpp" +#include "libtorrent/peer.hpp" +#include "libtorrent/peer_class.hpp" +#include "libtorrent/peer_class_set.hpp" +#include "libtorrent/peer_class_type_filter.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/peer_connection_handle.hpp" +#include "libtorrent/peer_connection_interface.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/peer_list.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/pex_flags.hpp" +#include "libtorrent/piece_block.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/platform_util.hpp" +#include "libtorrent/portmap.hpp" +#include "libtorrent/posix_disk_io.hpp" +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/puff.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/read_resume_data.hpp" +#include "libtorrent/request_blocks.hpp" +#include "libtorrent/resolve_links.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/session_handle.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/session_stats.hpp" +#include "libtorrent/session_status.hpp" +#include "libtorrent/session_types.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/sha1.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/sha256.hpp" +#include "libtorrent/sliding_average.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/socket_type.hpp" +#include "libtorrent/socks5_stream.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/ssl.hpp" +#include "libtorrent/ssl_stream.hpp" +#include "libtorrent/stack_allocator.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/tailqueue.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/torrent_flags.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/torrent_peer_allocator.hpp" +#include "libtorrent/torrent_status.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/udp_tracker_connection.hpp" +#include "libtorrent/union_endpoint.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/upnp.hpp" +#include "libtorrent/utf8.hpp" +#include "libtorrent/vector_utils.hpp" +#include "libtorrent/version.hpp" +#include "libtorrent/web_connection_base.hpp" +#include "libtorrent/web_peer_connection.hpp" +#include "libtorrent/write_resume_data.hpp" +#include "libtorrent/xml_parse.hpp" diff --git a/include/libtorrent/link.hpp b/include/libtorrent/link.hpp new file mode 100644 index 0000000..14d8dce --- /dev/null +++ b/include/libtorrent/link.hpp @@ -0,0 +1,83 @@ +/* + +Copyright (c) 2010, 2013-2019, Arvid Norberg +Copyright (c) 2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_LINK_HPP_INCLUDED +#define TORRENT_LINK_HPP_INCLUDED + +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/units.hpp" + +namespace libtorrent { + + using torrent_list_index_t = aux::strong_typedef; + + struct link + { + link() : index(-1) {} + // this is either -1 (not in the list) + // or the index of where in the list this + // element is found + int index; + + bool in_list() const { return index >= 0; } + + void clear() { index = -1; } + + template + void unlink(aux::vector& list + , torrent_list_index_t const link_index) + { + if (index == -1) return; + TORRENT_ASSERT(index >= 0 && index < int(list.size())); + int const last = int(list.size()) - 1; + if (index < last) + { + list[last]->m_links[link_index].index = index; + list[index] = list[last]; + } + list.resize(last); + index = -1; + } + + template + void insert(aux::vector& list, T* self) + { + if (index >= 0) return; + TORRENT_ASSERT(index == -1); + list.push_back(self); + index = int(list.size()) - 1; + } + }; +} + +#endif diff --git a/include/libtorrent/load_torrent.hpp b/include/libtorrent/load_torrent.hpp new file mode 100644 index 0000000..53586af --- /dev/null +++ b/include/libtorrent/load_torrent.hpp @@ -0,0 +1,69 @@ +/* + +Copyright (c) 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_LOAD_TORRENT_HPP_INCLUDED +#define TORRENT_LOAD_TORRENT_HPP_INCLUDED + +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/torrent_info.hpp" // for load_torrent_limits +#include "libtorrent/aux_/export.hpp" + +namespace libtorrent { + + // These functions load the content of a .torrent file into an + // add_torrent_params object. + // The immutable part of a torrent file (the info-dictionary) is stored in + // the ``ti`` field in the add_torrent_params object (as a torrent_info + // object). + // The returned object is suitable to be: + // + // * added to a session via add_torrent() or async_add_torrent() + // * saved as a .torrent_file via write_torrent_file() + // * turned into a magnet link via make_magnet_uri() + TORRENT_EXPORT add_torrent_params load_torrent_file( + std::string const& filename, load_torrent_limits const& cfg); + TORRENT_EXPORT add_torrent_params load_torrent_file( + std::string const& filename); + TORRENT_EXPORT add_torrent_params load_torrent_buffer( + span buffer, load_torrent_limits const& cfg); + TORRENT_EXPORT add_torrent_params load_torrent_buffer( + span buffer); + TORRENT_EXPORT add_torrent_params load_torrent_parsed( + bdecode_node const& torrent_file, load_torrent_limits const& cfg); + TORRENT_EXPORT add_torrent_params load_torrent_parsed( + bdecode_node const& torrent_file); + +} + +#endif diff --git a/include/libtorrent/lsd.hpp b/include/libtorrent/lsd.hpp new file mode 100644 index 0000000..f0d07f5 --- /dev/null +++ b/include/libtorrent/lsd.hpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2007, 2009, 2012, 2014-2015, 2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_LSD_HPP +#define TORRENT_LSD_HPP + +#include "libtorrent/socket.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/aux_/lsd.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/address.hpp" + +namespace libtorrent { + +struct lsd : std::enable_shared_from_this +{ + lsd(io_context& ios, aux::lsd_callback& cb + , address listen_address, address netmask); + ~lsd(); + + void start(error_code& ec); + + void announce(sha1_hash const& ih, int listen_port); + void close(); + +private: + + std::shared_ptr self() { return shared_from_this(); } + + void announce_impl(sha1_hash const& ih, int listen_port, int retry_count); + void resend_announce(error_code const& e, sha1_hash const& info_hash + , int listen_port, int retry_count); + void on_announce(error_code const& ec, std::size_t len); + + aux::lsd_callback& m_callback; + + address m_listen_address; + address m_netmask; + + udp::socket m_socket; + std::array m_buffer; + udp::endpoint m_remote; + +#ifndef TORRENT_DISABLE_LOGGING + bool should_log() const; + void debug_log(char const* fmt, ...) const TORRENT_FORMAT(2, 3); +#endif + + // used to resend udp packets in case + // they time out + deadline_timer m_broadcast_timer; + + // this is a random (presumably unique) + // ID for this LSD node. It is used to + // ignore our own broadcast messages. + // There's no point in adding ourselves + // as a peer + int m_cookie; + + bool m_disabled = false; +}; + +} + +#endif diff --git a/include/libtorrent/magnet_uri.hpp b/include/libtorrent/magnet_uri.hpp new file mode 100644 index 0000000..a7b66c8 --- /dev/null +++ b/include/libtorrent/magnet_uri.hpp @@ -0,0 +1,111 @@ +/* + +Copyright (c) 2007-2009, 2012-2013, 2016-2019, 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_MAGNET_URI_HPP_INCLUDED +#define TORRENT_MAGNET_URI_HPP_INCLUDED + +#include +#include "libtorrent/config.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/string_view.hpp" + +namespace libtorrent { + + struct torrent_handle; + struct session; + + // Generates a magnet URI from the specified torrent. + // + // Several fields from the add_torrent_params objects are recorded in the + // magnet link. In order to not include them, they have to be cleared before + // calling make_magnet_uri(). These fields are used: + // + // ``ti``, ``info_hashes``, ``url_seeds``, ``dht_nodes``, + // ``file_priorities``, ``trackers``, ``name``, ``peers``. + // + // Depending on what the use case for the resulting magnet link is, clearing + // ``peers`` and ``dht_nodes`` is probably a good idea if the add_torrent_params + // came from a running torrent. Those lists may be long and be ephemeral. + // + // If none of the ``info_hashes`` or ``ti`` fields are set, there is not + // info-hash available, and a magnet link cannot be created. In this case + // make_magnet_uri() returns an empty string. + // + // The recommended way to generate a magnet link from a torrent_handle is to + // call save_resume_data(), which will post a save_resume_data_alert + // containing an add_torrent_params object. This can then be passed to + // make_magnet_uri(). + // + // The overload that takes a torrent_handle will make blocking calls to + // query information about the torrent. If the torrent handle is invalid, + // an empty string is returned. + // + // For more information about magnet links, see magnet-links_. + TORRENT_EXPORT std::string make_magnet_uri(add_torrent_params const& atp); + TORRENT_EXPORT std::string make_magnet_uri(torrent_handle const& handle); + TORRENT_EXPORT std::string make_magnet_uri(torrent_info const& info); + +#if TORRENT_ABI_VERSION == 1 +#ifndef BOOST_NO_EXCEPTIONS + // deprecated in 0.14 + TORRENT_DEPRECATED_EXPORT + torrent_handle add_magnet_uri(session& ses, std::string const& uri + , std::string const& save_path + , storage_mode_t storage_mode = storage_mode_sparse + , bool paused = false + , void* userdata = nullptr); + + // deprecated in 0.16. Instead, pass in the magnet link as add_torrent_params::url + TORRENT_DEPRECATED_EXPORT + torrent_handle add_magnet_uri(session& ses, std::string const& uri + , add_torrent_params const& p); +#endif + + // deprecated in 0.16. Instead, pass in the magnet link as add_torrent_params::url + TORRENT_DEPRECATED_EXPORT + torrent_handle add_magnet_uri(session& ses, std::string const& uri + , add_torrent_params const& p, error_code& ec); +#endif // TORRENT_ABI_VERSION + + + // This function parses out information from the magnet link and populates the + // add_torrent_params object. The overload that does not take an + // ``error_code`` reference will throw a system_error on error + // The overload taking an ``add_torrent_params`` reference will fill in the + // fields specified in the magnet URI. + TORRENT_EXPORT add_torrent_params parse_magnet_uri(string_view uri, error_code& ec); + TORRENT_EXPORT add_torrent_params parse_magnet_uri(string_view uri); + TORRENT_EXPORT void parse_magnet_uri(string_view uri, add_torrent_params& p, error_code& ec); +} + +#endif diff --git a/include/libtorrent/mmap_disk_io.hpp b/include/libtorrent/mmap_disk_io.hpp new file mode 100644 index 0000000..b58ba53 --- /dev/null +++ b/include/libtorrent/mmap_disk_io.hpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2007-2018, Steven Siloti +Copyright (c) 2007, 2013-2016, 2019-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_IO_THREAD +#define TORRENT_DISK_IO_THREAD + +#include "libtorrent/config.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/io_context.hpp" + +namespace libtorrent { + +#if TORRENT_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE + + struct counters; + struct settings_interface; + + // constructs a memory mapped file disk I/O object. + TORRENT_EXPORT std::unique_ptr mmap_disk_io_constructor( + io_context& ios, settings_interface const&, counters& cnt); + +#endif // HAVE_MMAP || HAVE_MAP_VIEW_OF_FILE + +} + +#endif // TORRENT_DISK_IO_THREAD diff --git a/include/libtorrent/mmap_storage.hpp b/include/libtorrent/mmap_storage.hpp new file mode 100644 index 0000000..e9b6127 --- /dev/null +++ b/include/libtorrent/mmap_storage.hpp @@ -0,0 +1,232 @@ +/* + +Copyright (c) 2003, 2009, 2011, 2013-2022, Arvid Norberg +Copyright (c) 2003, Daniel Wallin +Copyright (c) 2016, 2018, Alden Torres +Copyright (c) 2018-2019, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STORAGE_HPP_INCLUDE +#define TORRENT_STORAGE_HPP_INCLUDE + +#include "libtorrent/config.hpp" + +#if TORRENT_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE + +#include +#include +#include + +#include "libtorrent/fwd.hpp" +#include "libtorrent/aux_/disk_job_fence.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/part_file.hpp" +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/open_mode.hpp" // for aux::open_mode_t +#include "libtorrent/disk_interface.hpp" // for disk_job_flags_t +#include "libtorrent/aux_/mmap.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + +namespace aux { + struct session_settings; + struct file_view_pool; +} + + struct TORRENT_EXTRA_EXPORT mmap_storage + : std::enable_shared_from_this + , aux::disk_job_fence + { + // constructs the mmap_storage based on the give file_storage (fs). + // ``mapped`` is an optional argument (it may be nullptr). If non-nullptr it + // represents the file mapping that have been made to the torrent before + // adding it. That's where files are supposed to be saved and looked for + // on disk. ``save_path`` is the root save folder for this torrent. + // ``file_view_pool`` is the cache of file mappings that the storage will use. + // All files it opens will ask the file_view_pool to open them. ``file_prio`` + // is a vector indicating the priority of files on startup. It may be + // an empty vector. Any file whose index is not represented by the vector + // (because the vector is too short) are assumed to have priority 1. + // this is used to treat files with priority 0 slightly differently. + mmap_storage(storage_params const& params, aux::file_view_pool&); + + // hidden + ~mmap_storage(); + mmap_storage(mmap_storage const&) = delete; + mmap_storage& operator=(mmap_storage const&) = delete; + + void abort_jobs(); + + bool has_any_file(storage_error&); + void set_file_priority(settings_interface const& + , aux::vector& prio + , storage_error&); + void rename_file(file_index_t index, std::string const& new_filename + , storage_error&); + void release_files(storage_error&); + void delete_files(remove_flags_t options, storage_error&); + status_t initialize(settings_interface const&, storage_error&); + std::pair move_storage(std::string save_path + , move_flags_t, storage_error&); + bool verify_resume_data(add_torrent_params const& rd + , aux::vector const& links + , storage_error&); + bool tick(); + + int read(settings_interface const&, span bufs + , piece_index_t piece, int offset, aux::open_mode_t mode + , disk_job_flags_t flags + , storage_error&); + int write(settings_interface const&, span bufs + , piece_index_t piece, int offset, aux::open_mode_t mode + , disk_job_flags_t flags + , storage_error&); + int hash(settings_interface const&, hasher& ph, std::ptrdiff_t len + , piece_index_t piece, int offset, aux::open_mode_t mode + , disk_job_flags_t flags, storage_error&); + int hash2(settings_interface const&, hasher256& ph, std::ptrdiff_t len + , piece_index_t piece, int offset, aux::open_mode_t mode + , disk_job_flags_t flags, storage_error&); + + // if the files in this storage are mapped, returns the mapped + // file_storage, otherwise returns the original file_storage object. + file_storage const& files() const { return m_mapped_files ? *m_mapped_files : m_files; } + + bool set_need_tick() + { + bool const prev = m_need_tick; + m_need_tick = true; + return prev; + } + + void do_tick() + { + m_need_tick = false; + tick(); + } + + void set_owner(std::shared_ptr const& tor) { m_torrent = tor; } + + storage_index_t storage_index() const { return m_storage_index; } + void set_storage_index(storage_index_t st) { m_storage_index = st; } + + private: + + bool m_need_tick = false; + bool m_use_mmap_writes = false; + + file_storage const& m_files; + + // the reason for this to be a void pointer + // is to avoid creating a dependency on the + // torrent. This shared_ptr is here only + // to keep the torrent object alive until + // the storage destructs. This is because + // the file_storage object is owned by the torrent. + std::shared_ptr m_torrent; + + storage_index_t m_storage_index{0}; + + void need_partfile(); + + std::unique_ptr m_mapped_files; + + // in order to avoid calling stat() on each file multiple times + // during startup, cache the results in here, and clear it all + // out once the torrent starts (to avoid getting stale results) + // each entry represents the size and timestamp of the file + mutable stat_cache m_stat_cache; + + // helper function to open a file in the file pool with the right mode + std::shared_ptr open_file(settings_interface const&, file_index_t + , aux::open_mode_t, storage_error&) const; + std::shared_ptr open_file_impl(settings_interface const& + , file_index_t, aux::open_mode_t, storage_error&) const; + + bool use_partfile(file_index_t index) const; + void use_partfile(file_index_t index, bool b); + + aux::vector m_file_priority; + std::string m_save_path; + std::string m_part_file_name; + + // this this is an array indexed by file-index. Each slot represents + // whether this file has the part-file enabled for it. This is used for + // backwards compatibility with pre-partfile versions of libtorrent. If + // this vector is empty, the default is that files *do* use the partfile. + // on startup, any 0-priority file that's found in it's original location + // is expected to be an old-style (pre-partfile) torrent storage, and + // those files have their slot set to false in this vector. + // note that the vector is *sparse*, it's only allocated if a file has its + // entry set to false, and only indices up to that entry. + aux::vector m_use_partfile; + + // the file pool is a member of the disk_io_thread + // to make all storage instances share the pool + aux::file_view_pool& m_pool; + + // used for skipped files + std::unique_ptr m_part_file; + + // this is a bitfield with one bit per file. A bit being set means + // we've written to that file previously. If we do write to a file + // whose bit is 0, we set the file size, to make the file allocated + // on disk (in full allocation mode) and just sparsely allocated in + // case of sparse allocation mode + mutable std::mutex m_file_created_mutex; + mutable typed_bitfield m_file_created; + +#if TORRENT_HAVE_MAP_VIEW_OF_FILE + // Windows has a race condition when unmapping a view while a new + // view or mapping object is being created in a different thread. + // The race can cause a page of written data to be zeroed out before + // it is written out to disk. To avoid the race these calls must be + // serialized on a per-file basis. See github issue #3842 for details. + + // This array stores a mutex for each file in the storage object + // It must be acquired before calling CreateFileMapping or UnmapViewOfFile + mutable std::shared_ptr m_file_open_unmap_lock; +#endif + + bool m_allocate_files; + }; + +} + +#endif // TORRENT_HAVE_MMAP || TORRENT_HAVE_MAP_VIEW_OF_FILE + +#endif // TORRENT_STORAGE_HPP_INCLUDED diff --git a/include/libtorrent/natpmp.hpp b/include/libtorrent/natpmp.hpp new file mode 100644 index 0000000..68ebf5b --- /dev/null +++ b/include/libtorrent/natpmp.hpp @@ -0,0 +1,220 @@ +/* + +Copyright (c) 2007-2010, 2015-2020, 2022, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2018, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_NATPMP_HPP +#define TORRENT_NATPMP_HPP + +#include "libtorrent/io_context.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/aux_/portmap.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/enum_net.hpp" // for ip_interface +#include "libtorrent/aux_/listen_socket_handle.hpp" + +namespace libtorrent { + +namespace errors { + // See RFC 6887 Section 7.4 + enum pcp_errors + { + pcp_success = 0, + pcp_unsupp_version, + pcp_not_authorized, + pcp_malformed_request, + pcp_unsupp_opcode, + pcp_unsupp_option, + pcp_malformed_option, + pcp_network_failure, + pcp_no_resources, + pcp_unsupp_protocol, + pcp_user_ex_quota, + pcp_cannot_provide_external, + pcp_address_mismatch, + pcp_excessive_remote_peers, + }; + + boost::system::error_code make_error_code(pcp_errors e); +} // namespace errors + + TORRENT_EXPORT boost::system::error_category& pcp_category(); +} // namespace libtorrent + +namespace boost { +namespace system { + template<> struct is_error_code_enum + { static const bool value = true; }; +} +} + +namespace libtorrent { + +struct TORRENT_EXTRA_EXPORT natpmp final + : std::enable_shared_from_this + , single_threaded +{ + natpmp(io_context& ios, aux::portmap_callback& cb, aux::listen_socket_handle ls); + + void start(ip_interface const& ip); + + // maps the ports, if a port is set to 0 + // it will not be mapped + port_mapping_t add_mapping(portmap_protocol p, int external_port, tcp::endpoint local_ep + , std::string const& device); + void delete_mapping(port_mapping_t mapping_index); + bool get_mapping(port_mapping_t mapping_index, int& local_port, int& external_port + , portmap_protocol& protocol) const; + + void close(); + +private: + static error_code from_result_code(int version, int result); + + std::shared_ptr self() { return shared_from_this(); } + + void update_mapping(port_mapping_t i); + void send_map_request(port_mapping_t i); + void send_get_ip_address_request(); + void on_resend_request(port_mapping_t i, error_code const& e); + void resend_request(port_mapping_t); + void on_reply(error_code const& e + , std::size_t bytes_transferred); + void try_next_mapping(port_mapping_t i); + void update_expiration_timer(); + void mapping_expired(error_code const& e, port_mapping_t i); + void close_impl(); + + void disable(error_code const& ec); + + enum protocol_version + { + version_natpmp = 0, + version_pcp = 2, + }; + + static char const* version_to_string(protocol_version version); + + // See RFC 6887 Section 19.2 + enum pcp_opcode + { + opcode_announce = 0, + opcode_map, + opcode_peer, + }; + + struct mapping_t : aux::base_mapping + { + // random identifier, used by PCP + std::array nonce; + + // only valid if the router supports PCP + address external_address; + + // the local port for this mapping. If this is set + // to 0, the mapping is not in use + int local_port = 0; + + // set to true when the first map request is sent + bool map_sent = false; + + // set to true while we're waiting for a response + bool outstanding_request = false; + }; + +#ifndef TORRENT_DISABLE_LOGGING + bool should_log() const; + void log(char const* fmt, ...) const TORRENT_FORMAT(2, 3); + void mapping_log(char const* op, mapping_t const& m) const; +#endif + + aux::portmap_callback& m_callback; + + protocol_version m_version = version_pcp; + + aux::vector m_mappings; + + // the endpoint to the nat router + udp::endpoint m_nat_endpoint; + + // this is the mapping that is currently + // being updated. It is -1 in case no + // mapping is being updated at the moment + port_mapping_t m_currently_mapping{-1}; + + // current retry count + int m_retry_count = 0; + + // used to receive responses in + // 1100 octets is the maximum size of a PCP packet + char m_response_buffer[1100]; + + // router external IP address + // this is only used if the router does not support PCP + // with PCP the external IP is stored with the mapping + address m_external_ip; + + // the endpoint we received the message from + udp::endpoint m_remote; + + // the udp socket used to communicate + // with the NAT router + udp::socket m_socket; + + // used to resend udp packets in case + // they time out + deadline_timer m_send_timer; + + // timer used to refresh mappings + deadline_timer m_refresh_timer; + + // the mapping index that will expire next + port_mapping_t m_next_refresh{-1}; + + io_context& m_ioc; + + aux::listen_socket_handle m_listen_handle; + + bool m_disabled = false; + + bool m_abort = false; +}; + +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/netlink.hpp b/include/libtorrent/netlink.hpp new file mode 100644 index 0000000..e1d9a26 --- /dev/null +++ b/include/libtorrent/netlink.hpp @@ -0,0 +1,203 @@ +/* + +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2016, Steven Siloti +Copyright (c) 2017, 2019, Arvid Norberg +Copyright (c) 2018, Eugene Shalygin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_NETLINK_HPP +#define TORRENT_NETLINK_HPP + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_NETLINK + +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + + template + class basic_nl_endpoint + { + public: + using protocol_type = Protocol; + using data_type = boost::asio::detail::socket_addr_type; + + basic_nl_endpoint() noexcept : basic_nl_endpoint(protocol_type(), 0, 0) {} + + basic_nl_endpoint(protocol_type netlink_family, std::uint32_t group, std::uint32_t pid = 0) + : m_proto(netlink_family) + { + std::memset(&m_sockaddr, 0, sizeof(sockaddr_nl)); + m_sockaddr.nl_family = AF_NETLINK; + m_sockaddr.nl_groups = group; + m_sockaddr.nl_pid = pid; + } + + basic_nl_endpoint(basic_nl_endpoint const& other) = default; + basic_nl_endpoint(basic_nl_endpoint&& other) noexcept = default; + + basic_nl_endpoint& operator=(basic_nl_endpoint const& other) + { + m_sockaddr = other.m_sockaddr; + return *this; + } + + basic_nl_endpoint& operator=(basic_nl_endpoint&& other) noexcept + { + m_sockaddr = other.m_sockaddr; + return *this; + } + + protocol_type protocol() const + { + return m_proto; + } + + data_type* data() + { + return reinterpret_cast(&m_sockaddr); + } + + const data_type* data() const + { + return reinterpret_cast(&m_sockaddr); + } + + std::size_t size() const + { + return sizeof(m_sockaddr); + } + + std::size_t capacity() const + { + return sizeof(m_sockaddr); + } + + // commented the comparison operators for now, until the + // same operators are implemented for sockaddr_nl + /* + friend bool operator==(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return l.m_sockaddr == r.m_sockaddr; + } + + friend bool operator!=(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return !(l.m_sockaddr == r.m_sockaddr); + } + + friend bool operator<(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return l.m_sockaddr < r.m_sockaddr; + } + + friend bool operator>(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return r.m_sockaddr < l.m_sockaddr; + } + + friend bool operator<=(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return !(r < l); + } + + friend bool operator>=(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return !(l < r); + } + */ + + private: + protocol_type m_proto; + sockaddr_nl m_sockaddr; + }; + + class netlink + { + public: + using endpoint = basic_nl_endpoint; + using socket = boost::asio::basic_raw_socket; + + netlink() : netlink(NETLINK_ROUTE) {} + + explicit netlink(int nl_family) + : m_nl_family(nl_family) + { + } + + int type() const + { + return SOCK_RAW; + } + + int protocol() const + { + return m_nl_family; + } + + int family() const + { + return AF_NETLINK; + } + + friend bool operator==(const netlink& l, const netlink& r) + { + return l.m_nl_family == r.m_nl_family; + } + + friend bool operator!=(const netlink& l, const netlink& r) + { + return l.m_nl_family != r.m_nl_family; + } + + private: + int m_nl_family; + }; + +} + +#endif // TORRENT_USE_NETLINK + +#endif diff --git a/include/libtorrent/operations.hpp b/include/libtorrent/operations.hpp new file mode 100644 index 0000000..5dfb607 --- /dev/null +++ b/include/libtorrent/operations.hpp @@ -0,0 +1,265 @@ +/* + +Copyright (c) 2015, 2017-2021, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_OPERATIONS_HPP_INCLUDED +#define TORRENT_OPERATIONS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include + +namespace libtorrent { + + // these constants are used to identify the operation that failed, causing a + // peer to disconnect + enum class operation_t : std::uint8_t + { + // the error was unexpected and it is unknown which operation caused it + unknown, + + // this is used when the bittorrent logic + // determines to disconnect + bittorrent, + + // a call to iocontrol failed + iocontrol, + + // a call to ``getpeername()`` failed (querying the remote IP of a + // connection) + getpeername, + + // a call to getname failed (querying the local IP of a + // connection) + getname, + + // an attempt to allocate a receive buffer failed + alloc_recvbuf, + + // an attempt to allocate a send buffer failed + alloc_sndbuf, + + // writing to a file failed + file_write, + + // reading from a file failed + file_read, + + // a non-read and non-write file operation failed + file, + + // a socket write operation failed + sock_write, + + // a socket read operation failed + sock_read, + + // a call to open(), to create a socket socket failed + sock_open, + + // a call to bind() on a socket failed + sock_bind, + + // an attempt to query the number of bytes available to read from a socket + // failed + available, + + // a call related to bittorrent protocol encryption failed + encryption, + + // an attempt to connect a socket failed + connect, + + // establishing an SSL connection failed + ssl_handshake, + + // a connection failed to satisfy the bind interface setting + get_interface, + + // a call to listen() on a socket + sock_listen, + + // a call to the ioctl to bind a socket to a specific network device or + // adapter + sock_bind_to_device, + + // a call to accept() on a socket + sock_accept, + + // convert a string into a valid network address + parse_address, + + // enumeration network devices or adapters + enum_if, + + // invoking stat() on a file + file_stat, + + // copying a file + file_copy, + + // allocating storage for a file + file_fallocate, + + // creating a hard link + file_hard_link, + + // removing a file + file_remove, + + // renaming a file + file_rename, + + // opening a file + file_open, + + // creating a directory + mkdir, + + // check fast resume data against files on disk + check_resume, + + // an unknown exception + exception, + + // allocate space for a piece in the cache + alloc_cache_piece, + + // move a part-file + partfile_move, + + // read from a part file + partfile_read, + + // write to a part-file + partfile_write, + + // a hostname lookup + hostname_lookup, + + // create or read a symlink + symlink, + + // handshake with a peer or server + handshake, + + // set socket option + sock_option, + + // enumeration of network routes + enum_route, + + // moving read/write position in a file, operation_t::hostname_lookup + file_seek, + + // an async wait operation on a timer + timer, + + // call to mmap() (or windows counterpart) + file_mmap, + + // call to ftruncate() (or SetEndOfFile() on windows) + file_truncate, + }; + + // maps an operation id (from peer_error_alert and peer_disconnected_alert) + // to its name. See operation_t for the constants + TORRENT_EXPORT char const* operation_name(operation_t op); + +#if TORRENT_ABI_VERSION == 1 + enum deprecated_operation_t : std::uint8_t + { + // the error was unexpected and it is unknown which operation caused it + op_unknown TORRENT_DEPRECATED_ENUM, + + // this is used when the bittorrent logic + // determines to disconnect + op_bittorrent TORRENT_DEPRECATED_ENUM , + + // a call to ``iocontrol()`` failed + op_iocontrol TORRENT_DEPRECATED_ENUM, + + // a call to ``getpeername()`` failed (querying the remote IP of a + // connection) + op_getpeername TORRENT_DEPRECATED_ENUM, + + // a call to ``getsockname()`` failed (querying the local IP of a + // connection) + op_getname TORRENT_DEPRECATED_ENUM, + + // an attempt to allocate a receive buffer failed + op_alloc_recvbuf TORRENT_DEPRECATED_ENUM, + + // an attempt to allocate a send buffer failed + op_alloc_sndbuf TORRENT_DEPRECATED_ENUM, + + // writing to a file failed + op_file_write TORRENT_DEPRECATED_ENUM, + + // reading from a file failed + op_file_read TORRENT_DEPRECATED_ENUM, + + // a non-read and non-write file operation failed + op_file TORRENT_DEPRECATED_ENUM, + + // a socket write operation failed + op_sock_write TORRENT_DEPRECATED_ENUM, + + // a socket read operation failed + op_sock_read TORRENT_DEPRECATED_ENUM, + + // a call to open(), to create a socket socket failed + op_sock_open TORRENT_DEPRECATED_ENUM, + + // a call to bind() on a socket failed + op_sock_bind TORRENT_DEPRECATED_ENUM, + + // an attempt to query the number of bytes available to read from a socket + // failed + op_available TORRENT_DEPRECATED_ENUM, + + // a call related to bittorrent protocol encryption failed + op_encryption TORRENT_DEPRECATED_ENUM, + + // an attempt to connect a socket failed + op_connect TORRENT_DEPRECATED_ENUM, + + // establishing an SSL connection failed + op_ssl_handshake TORRENT_DEPRECATED_ENUM, + + // a connection failed to satisfy the bind interface setting + op_get_interface TORRENT_DEPRECATED_ENUM, + }; +#endif + +} + +#endif // TORRENT_OPERATIONS_HPP_INCLUDED diff --git a/include/libtorrent/optional.hpp b/include/libtorrent/optional.hpp new file mode 100644 index 0000000..bf85715 --- /dev/null +++ b/include/libtorrent/optional.hpp @@ -0,0 +1,51 @@ +/* + +Copyright (c) 2017, 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + + +#ifndef TORRENT_OPTIONAL_HPP_INCLUDED +#define TORRENT_OPTIONAL_HPP_INCLUDED + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + + template + T value_or(boost::optional opt, U def) + { + return opt ? *opt : T(def); + } +} + +#endif + diff --git a/include/libtorrent/parse_url.hpp b/include/libtorrent/parse_url.hpp new file mode 100644 index 0000000..b5f5b9e --- /dev/null +++ b/include/libtorrent/parse_url.hpp @@ -0,0 +1,66 @@ +/* + +Copyright (c) 2008-2009, 2014, 2016-2017, 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PARSE_URL_HPP_INCLUDED +#define TORRENT_PARSE_URL_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include +#include + +#include "libtorrent/error_code.hpp" +#include "libtorrent/string_view.hpp" + +namespace libtorrent { + + // returns protocol, auth, hostname, port, path + TORRENT_EXTRA_EXPORT std::tuple + parse_url_components(std::string url, error_code& ec); + + // split a URL in its base and path parts + TORRENT_EXTRA_EXPORT std::tuple + split_url(std::string url, error_code& ec); + + // returns true if the hostname contains any IDNA (internationalized domain + // name) labels. + TORRENT_EXTRA_EXPORT bool is_idna(string_view hostname); + + // the query string is the part of the URL immediately following "?", i.e. + // the query string arguments. This function returns true if any of the + // arguments are "info_hash", "port", "key", "event", "uploaded", + // "downloaded", "left" or "corrupt". + TORRENT_EXTRA_EXPORT bool has_tracker_query_string(string_view query_string); +} + +#endif diff --git a/include/libtorrent/part_file.hpp b/include/libtorrent/part_file.hpp new file mode 100644 index 0000000..e4a0d5d --- /dev/null +++ b/include/libtorrent/part_file.hpp @@ -0,0 +1,138 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, d-komarov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PART_FILE_HPP_INCLUDE +#define TORRENT_PART_FILE_HPP_INCLUDE + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/aux_/open_mode.hpp" +#include "libtorrent/aux_/storage_utils.hpp" // for iovec_t + +namespace libtorrent { + + using slot_index_t = aux::strong_typedef; + + struct TORRENT_EXTRA_EXPORT part_file + { + // create a part file at ``path``, that can hold ``num_pieces`` pieces. + // each piece being ``piece_size`` number of bytes + part_file(std::string path, std::string name, int num_pieces, int piece_size); + ~part_file(); + + int write(span buf, piece_index_t piece, int offset, error_code& ec); + int read(span buf, piece_index_t piece, int offset, error_code& ec); + int hash(hasher& ph, std::ptrdiff_t len, piece_index_t piece, int offset, error_code& ec); + int hash2(hasher256& ph, std::ptrdiff_t len, piece_index_t piece, int offset, error_code& ec); + + // free the slot the given piece is stored in. We no longer need to store this + // piece in the part file + void free_piece(piece_index_t piece); + + void move_partfile(std::string const& path, error_code& ec); + + // the function is called for every block of data belonging to the + // specified range that's in the part_file. The first parameter is the + // offset within the range + void export_file(std::function)> f + , std::int64_t offset, std::int64_t size, error_code& ec); + + // flush the metadata + void flush_metadata(error_code& ec); + + private: + + aux::file_handle open_file(aux::open_mode_t mode, error_code& ec); + void flush_metadata_impl(error_code& ec); + + std::int64_t slot_offset(slot_index_t const slot) const + { + return static_cast(slot) * static_cast(m_piece_size) + + m_header_size; + } + + template + int do_hash(Hasher& ph, std::ptrdiff_t len, piece_index_t piece, int offset, error_code& ec); + + std::string m_path; + std::string const m_name; + + // allocate a slot and return the slot index + slot_index_t allocate_slot(piece_index_t piece); + + // this mutex must be held while accessing the data + // structure. Not while reading or writing from the file though! + // it's important to support multithreading + std::mutex m_mutex; + + // this is a list of unallocated slots in the part file + // within the m_num_allocated range + std::vector m_free_slots; + + // this is the number of slots allocated + slot_index_t m_num_allocated{0}; + + // the max number of pieces in the torrent this part file is + // backing + int const m_max_pieces; + + // number of bytes each piece contains + int const m_piece_size; + + // this is the size of the part_file header, it is added + // to offsets when calculating the offset to read and write + // payload data from + int const m_header_size; + + // if this is true, the metadata in memory has changed since + // we last saved or read it from disk. It means that we + // need to flush the metadata before closing the file + bool m_dirty_metadata = false; + + // maps a piece index to the part-file slot it is stored in + std::unordered_map m_piece_map; + }; +} + +#endif diff --git a/include/libtorrent/pe_crypto.hpp b/include/libtorrent/pe_crypto.hpp new file mode 100644 index 0000000..3c2280e --- /dev/null +++ b/include/libtorrent/pe_crypto.hpp @@ -0,0 +1,162 @@ +/* + +Copyright (c) 2007, Un Shyam +Copyright (c) 2007-2009, 2011-2012, 2014-2019, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PE_CRYPTO_HPP_INCLUDED +#define TORRENT_PE_CRYPTO_HPP_INCLUDED + +#if !defined TORRENT_DISABLE_ENCRYPTION + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/aux_/receive_buffer.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/aux_/array.hpp" + +#include +#include +#include + +namespace libtorrent { + + namespace mp = boost::multiprecision; + + using key_t = mp::number>; + + TORRENT_EXTRA_EXPORT std::array export_key(key_t const& k); + + // RC4 state from libtomcrypt + struct rc4 { + int x; + int y; + aux::array buf; + }; + + // TODO: 3 dh_key_exchange should probably move into its own file + class TORRENT_EXTRA_EXPORT dh_key_exchange + { + public: + dh_key_exchange(); + + // Get local public key + key_t const& get_local_key() const { return m_dh_local_key; } + + // read remote_pubkey, generate and store shared secret in + // m_dh_shared_secret. + void compute_secret(std::uint8_t const* remote_pubkey); + void compute_secret(key_t const& remote_pubkey); + + key_t const& get_secret() const { return m_dh_shared_secret; } + + sha1_hash const& get_hash_xor_mask() const { return m_xor_mask; } + + private: + + key_t m_dh_local_key; + key_t m_dh_local_secret; + key_t m_dh_shared_secret; + sha1_hash m_xor_mask; + }; + + struct TORRENT_EXTRA_EXPORT encryption_handler + { + std::tuple>> + encrypt(span> iovec); + + int decrypt(aux::crypto_receive_buffer& recv_buffer + , std::size_t& bytes_transferred); + + bool switch_send_crypto(std::shared_ptr crypto + , int pending_encryption); + + void switch_recv_crypto(std::shared_ptr crypto + , aux::crypto_receive_buffer& recv_buffer); + + bool is_send_plaintext() const + { + return m_send_barriers.empty() || m_send_barriers.back().next != INT_MAX; + } + + bool is_recv_plaintext() const + { + return m_dec_handler.get() == nullptr; + } + + private: + struct barrier + { + barrier(std::shared_ptr plugin, int n) + : enc_handler(plugin), next(n) {} + std::shared_ptr enc_handler; + // number of bytes to next barrier + int next; + }; + std::list m_send_barriers; + std::shared_ptr m_dec_handler; + }; + + struct TORRENT_EXTRA_EXPORT rc4_handler : crypto_plugin + { + public: + rc4_handler(); + + // Input keys must be 20 bytes + void set_incoming_key(span key) override; + void set_outgoing_key(span key) override; + + std::tuple>> + encrypt(span> buf) override; + + std::tuple decrypt(span> buf) override; + + private: + rc4 m_rc4_incoming; + rc4 m_rc4_outgoing; + + // determines whether or not encryption and decryption is enabled + bool m_encrypt; + bool m_decrypt; + }; + +} // namespace libtorrent + +#endif // TORRENT_DISABLE_ENCRYPTION + +#endif // TORRENT_PE_CRYPTO_HPP_INCLUDED diff --git a/include/libtorrent/peer.hpp b/include/libtorrent/peer.hpp new file mode 100644 index 0000000..a00bf9a --- /dev/null +++ b/include/libtorrent/peer.hpp @@ -0,0 +1,76 @@ +/* + +Copyright (c) 2003-2004, 2006, 2012, 2014-2017, 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_HPP_INCLUDED +#define TORRENT_PEER_HPP_INCLUDED + +#include + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/address.hpp" + +namespace libtorrent { + + struct TORRENT_EXTRA_EXPORT peer_entry + { + std::string hostname; + peer_id pid; + std::uint16_t port; + + bool operator==(const peer_entry& p) const + { return pid == p.pid; } + + bool operator<(const peer_entry& p) const + { return pid < p.pid; } + }; + + struct ipv4_peer_entry + { + address_v4::bytes_type ip; + std::uint16_t port; + }; + + struct ipv6_peer_entry + { + address_v6::bytes_type ip; + std::uint16_t port; + }; + +#if TORRENT_USE_I2P + struct i2p_peer_entry + { + sha256_hash destination; + }; +#endif +} + +#endif // TORRENT_PEER_HPP_INCLUDED diff --git a/include/libtorrent/peer_class.hpp b/include/libtorrent/peer_class.hpp new file mode 100644 index 0000000..75402a9 --- /dev/null +++ b/include/libtorrent/peer_class.hpp @@ -0,0 +1,159 @@ +/* + +Copyright (c) 2014-2018, 2020, Arvid Norberg +Copyright (c) 2016, 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CLASS_HPP_INCLUDED +#define TORRENT_PEER_CLASS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/bandwidth_limit.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/deque.hpp" + +#include +#include +#include +#include + +namespace libtorrent { + + using peer_class_t = aux::strong_typedef; + + // holds settings for a peer class. Used in set_peer_class() and + // get_peer_class() calls. + struct TORRENT_EXPORT peer_class_info + { + // ``ignore_unchoke_slots`` determines whether peers should always + // unchoke a peer, regardless of the choking algorithm, or if it should + // honor the unchoke slot limits. It's used for local peers by default. + // If *any* of the peer classes a peer belongs to has this set to true, + // that peer will be unchoked at all times. + bool ignore_unchoke_slots; + + // adjusts the connection limit (global and per torrent) that applies to + // this peer class. By default, local peers are allowed to exceed the + // normal connection limit for instance. This is specified as a percent + // factor. 100 makes the peer class apply normally to the limit. 200 + // means as long as there are fewer connections than twice the limit, we + // accept this peer. This factor applies both to the global connection + // limit and the per-torrent limit. Note that if not used carefully one + // peer class can potentially completely starve out all other over time. + int connection_limit_factor; + + // not used by libtorrent. It's intended as a potentially user-facing + // identifier of this peer class. + std::string label; + + // transfer rates limits for the whole peer class. They are specified in + // bytes per second and apply to the sum of all peers that are members of + // this class. + int upload_limit; + int download_limit; + + // relative priorities used by the bandwidth allocator in the rate + // limiter. If no rate limits are in use, the priority is not used + // either. Priorities start at 1 (0 is not a valid priority) and may not + // exceed 255. + int upload_priority; + int download_priority; + }; + + struct TORRENT_EXTRA_EXPORT peer_class + { + friend struct peer_class_pool; + + explicit peer_class(std::string l) + : ignore_unchoke_slots(false) + , connection_limit_factor(100) + , label(std::move(l)) + , in_use(true) + , references(1) + { + priority[0] = 1; + priority[1] = 1; + } + + void clear() + { + in_use = false; + label.clear(); + } + + void set_info(peer_class_info const* pci); + void get_info(peer_class_info* pci) const; + + void set_upload_limit(int limit); + void set_download_limit(int limit); + + // the bandwidth channels, upload and download + // keeps track of the current quotas + aux::bandwidth_channel channel[2]; + + bool ignore_unchoke_slots; + int connection_limit_factor; + + // priority for bandwidth allocation + // in rate limiter. One for upload and one + // for download + int priority[2]; + + // the name of this peer class + std::string label; + + private: + // this is set to false when this slot is not in use for a peer_class + bool in_use; + + int references; + }; + + struct TORRENT_EXTRA_EXPORT peer_class_pool + { + peer_class_t new_peer_class(std::string label); + void decref(peer_class_t c); + void incref(peer_class_t c); + peer_class* at(peer_class_t c); + peer_class const* at(peer_class_t c) const; + + private: + + // state for peer classes (a peer can belong to multiple classes) + // this can control + aux::deque m_peer_classes; + + // indices in m_peer_classes that are no longer used + std::vector m_free_list; + }; +} + +#endif // TORRENT_PEER_CLASS_HPP_INCLUDED diff --git a/include/libtorrent/peer_class_set.hpp b/include/libtorrent/peer_class_set.hpp new file mode 100644 index 0000000..0e2646c --- /dev/null +++ b/include/libtorrent/peer_class_set.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2010, 2013-2015, 2019, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CLASS_SET_HPP_INCLUDED +#define TORRENT_PEER_CLASS_SET_HPP_INCLUDED + +#include "libtorrent/peer_class.hpp" +#include "libtorrent/aux_/array.hpp" + +namespace libtorrent { + + // this represents an object that can have many peer classes applied + // to it. Most notably, peer connections and torrents derive from this. + struct TORRENT_EXTRA_EXPORT peer_class_set + { + peer_class_set() : m_size(0) {} + void add_class(peer_class_pool& pool, peer_class_t c); + bool has_class(peer_class_t c) const; + void remove_class(peer_class_pool& pool, peer_class_t c); + int num_classes() const { return m_size; } + peer_class_t class_at(int i) const + { + TORRENT_ASSERT(i >= 0 && i < int(m_size)); + return m_class[i]; + } + + private: + + // the number of elements used in the m_class array + std::int8_t m_size; + + // if this object belongs to any peer-class, this vector contains all + // class IDs. Each ID refers to a an entry in m_ses.m_peer_classes which + // holds the metadata about the class. Classes affect bandwidth limits + // among other things + aux::array m_class; + }; +} + +#endif // TORRENT_PEER_CLASS_SET_HPP_INCLUDED diff --git a/include/libtorrent/peer_class_type_filter.hpp b/include/libtorrent/peer_class_type_filter.hpp new file mode 100644 index 0000000..bc6815b --- /dev/null +++ b/include/libtorrent/peer_class_type_filter.hpp @@ -0,0 +1,146 @@ +/* + +Copyright (c) 2014, 2016-2017, 2019, Arvid Norberg +Copyright (c) 2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CLASS_TYPE_FILTER_HPP_INCLUDED +#define TORRENT_PEER_CLASS_TYPE_FILTER_HPP_INCLUDED + +#include +#include + +#include "aux_/export.hpp" +#include "peer_class.hpp" // for peer_class_t + +namespace libtorrent { + + // ``peer_class_type_filter`` is a simple container for rules for adding and subtracting + // peer-classes from peers. It is applied *after* the peer class filter is applied (which + // is based on the peer's IP address). + struct TORRENT_EXPORT peer_class_type_filter + { + // hidden + peer_class_type_filter() + { + m_peer_class_type_mask.fill(0xffffffff); + m_peer_class_type.fill(0); + } + + enum socket_type_t : std::uint8_t + { + // these match the socket types from socket_type.hpp + // shifted one down + tcp_socket = 0, + utp_socket, + ssl_tcp_socket, + ssl_utp_socket, + i2p_socket, + num_socket_types + }; + + // ``add()`` and ``remove()`` adds and removes a peer class to be added + // to new peers based on socket type. + void add(socket_type_t const st, peer_class_t const peer_class) + { + TORRENT_ASSERT(peer_class < peer_class_t{32}); + if (peer_class > peer_class_t{31}) return; + + TORRENT_ASSERT(st < num_socket_types); + if (st >= num_socket_types) return; + m_peer_class_type[st] |= 1 << static_cast(peer_class); + } + void remove(socket_type_t const st, peer_class_t const peer_class) + { + TORRENT_ASSERT(peer_class < peer_class_t{32}); + if (peer_class > peer_class_t{31}) return; + + TORRENT_ASSERT(st < num_socket_types); + if (st >= num_socket_types) return; + m_peer_class_type[st] &= ~(1 << static_cast(peer_class)); + } + + // ``disallow()`` and ``allow()`` adds and removes a peer class to be + // removed from new peers based on socket type. + // + // The ``peer_class`` argument cannot be greater than 31. The bitmasks representing + // peer classes in the ``peer_class_type_filter`` are 32 bits. + void disallow(socket_type_t const st, peer_class_t const peer_class) + { + TORRENT_ASSERT(peer_class < peer_class_t{32}); + if (peer_class > peer_class_t{31}) return; + + TORRENT_ASSERT(st < num_socket_types); + if (st >= num_socket_types) return; + m_peer_class_type_mask[st] &= ~(1 << static_cast(peer_class)); + } + void allow(socket_type_t const st, peer_class_t const peer_class) + { + TORRENT_ASSERT(peer_class < peer_class_t{32}); + if (peer_class > peer_class_t{31}) return; + + TORRENT_ASSERT(st < num_socket_types); + if (st >= num_socket_types) return; + m_peer_class_type_mask[st] |= 1 << static_cast(peer_class); + } + + // takes a bitmask of peer classes and returns a new bitmask of + // peer classes after the rules have been applied, based on the socket type argument + // (``st``). + std::uint32_t apply(socket_type_t const st, std::uint32_t peer_class_mask) + { + TORRENT_ASSERT(st < num_socket_types); + if (st >= num_socket_types) return peer_class_mask; + + // filter peer classes based on type + peer_class_mask &= m_peer_class_type_mask[st]; + // add peer classes based on type + peer_class_mask |= m_peer_class_type[st]; + return peer_class_mask; + } + + friend bool operator==(peer_class_type_filter const& lhs + , peer_class_type_filter const& rhs) + { + return lhs.m_peer_class_type_mask == rhs.m_peer_class_type_mask + && lhs.m_peer_class_type == rhs.m_peer_class_type; + } + + private: + // maps socket type to a bitmask that's used to filter out + // (mask) bits from the m_peer_class_filter. + std::array m_peer_class_type_mask; + // peer class bitfield added based on socket type + std::array m_peer_class_type; + }; + +} + +#endif diff --git a/include/libtorrent/peer_connection.hpp b/include/libtorrent/peer_connection.hpp new file mode 100644 index 0000000..e27de87 --- /dev/null +++ b/include/libtorrent/peer_connection.hpp @@ -0,0 +1,1262 @@ +/* + +Copyright (c) 2016, Falcosc +Copyright (c) 2003-2022, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016-2018, 2020, Alden Torres +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2017-2018, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CONNECTION_HPP_INCLUDED +#define TORRENT_PEER_CONNECTION_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/aux_/bandwidth_limit.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/chained_buffer.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/aux_/bandwidth_socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/sliding_average.hpp" +#include "libtorrent/peer_class.hpp" +#include "libtorrent/peer_class_set.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/disk_observer.hpp" +#include "libtorrent/peer_connection_interface.hpp" +#include "libtorrent/socket.hpp" // for tcp::endpoint +#include "libtorrent/io_context.hpp" +#include "libtorrent/aux_/receive_buffer.hpp" +#include "libtorrent/aux_/allocating_handler.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/piece_block.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/piece_picker.hpp" // for picker_options_t +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/socket_type.hpp" + +#include +#include +#include +#include +#include // for std::forward +#include // for make_tuple +#include +#include + +namespace libtorrent { + + struct torrent; + struct torrent_peer; + struct disk_interface; + +#ifndef TORRENT_DISABLE_EXTENSIONS + struct peer_plugin; +#endif + +namespace aux { + + struct session_interface; + + struct min_value_t {}; + static const min_value_t min_value{}; + + struct relative_time + { + relative_time() : m_time_diff(0) {} + explicit relative_time(min_value_t) : m_time_diff(std::numeric_limits::min()) {} + void set(time_point const reference, time_point const new_value) noexcept + { + m_time_diff = duration_cast(new_value - reference); + } + + time_point get(time_point reference) const noexcept + { + return reference + m_time_diff; + } + private: + milliseconds32 m_time_diff; + }; + + template + T clamp_assign(int const v) + { + auto const limit = std::numeric_limits::max(); + if (v < 0) return 0; + if (v > int(limit)) return limit; + return static_cast(v); + } +} + + struct pending_block + { + pending_block(piece_block const& b) // NOLINT + : block(b), send_buffer_offset(not_in_buffer), not_wanted(false) + , timed_out(false), busy(false) + {} + + piece_block block; + + static constexpr std::uint32_t not_in_buffer = 0x1fffffff; + + // the number of bytes into the send buffer this request is. Every time + // some portion of the send buffer is transmitted, this offset is + // decremented by the number of bytes sent. once this drops below 0, the + // request_time field is set to the current time. + // if the request has not been written to the send buffer, this field + // remains not_in_buffer. + std::uint32_t send_buffer_offset:29; + + // if any of these are set to true, this block + // is not allocated + // in the piece picker anymore, and open for + // other peers to pick. This may be caused by + // it either timing out or being received + // unexpectedly from the peer + std::uint32_t not_wanted:1; + std::uint32_t timed_out:1; + + // the busy flag is set if the block was + // requested from another peer when this + // request was queued. We only allow a single + // busy request at a time in each peer's queue + std::uint32_t busy:1; + + bool operator==(pending_block const& b) const + { + return b.block == block + && b.not_wanted == not_wanted + && b.timed_out == timed_out; + } + }; + + // argument pack passed to peer_connection constructor + struct peer_connection_args + { + aux::session_interface* ses; + aux::session_settings const* sett; + counters* stats_counters; + disk_interface* disk_thread; + io_context* ios; + std::weak_ptr tor; + aux::socket_type s; + tcp::endpoint endp; + torrent_peer* peerinfo; + peer_id our_peer_id; + }; + + struct TORRENT_EXTRA_EXPORT peer_connection_hot_members + { + // if tor is set, this is an outgoing connection + peer_connection_hot_members( + std::weak_ptr t + , aux::session_interface& ses + , aux::session_settings const& sett) + : m_torrent(std::move(t)) + , m_ses(ses) + , m_settings(sett) + , m_disconnecting(false) + , m_connecting(!m_torrent.expired()) + , m_endgame_mode(false) + , m_snubbed(false) + , m_interesting(false) + , m_choked(true) + , m_ignore_stats(false) + {} + + // explicitly disallow assignment, to silence msvc warning + peer_connection_hot_members& operator=(peer_connection_hot_members const&) = delete; + + protected: + + // the pieces the other end have + typed_bitfield m_have_piece; + + // this is the torrent this connection is + // associated with. If the connection is an + // incoming connection, this is set to zero + // until the info_hash is received. Then it's + // set to the torrent it belongs to. + + // TODO: make this a raw pointer (to save size in + // the first cache line) and make the constructor + // take a raw pointer. torrent objects should always + // outlive their peers + std::weak_ptr m_torrent; + + public: + + // a back reference to the session + // the peer belongs to. + aux::session_interface& m_ses; + + // settings that apply to this peer + aux::session_settings const& m_settings; + + protected: + + // this is true if this connection has been added + // to the list of connections that will be closed. + bool m_disconnecting:1; + + // this is true until this socket has become + // writable for the first time (i.e. the + // connection completed). While connecting + // the timeout will not be triggered. This is + // because windows XP SP2 may delay connection + // attempts, which means that the connection + // may not even have been attempted when the + // time out is reached. + bool m_connecting:1; + + // this is set to true if the last time we tried to + // pick a piece to download, we could only find + // blocks that were already requested from other + // peers. In this case, we should not try to pick + // another piece until the last one we requested is done + bool m_endgame_mode:1; + + // set to true when a piece request times out. The + // result is that the desired pending queue size + // is set to 1 + bool m_snubbed:1; + + // the peer has pieces we are interested in + bool m_interesting:1; + + // we have choked the upload to the peer + bool m_choked:1; + + // when this is set, the transfer stats for this connection + // is not included in the torrent or session stats + bool m_ignore_stats:1; + }; + + enum class connection_type : std::uint8_t + { + bittorrent, + url_seed, + http_seed + }; + + using request_flags_t = flags::bitfield_flag; + + struct TORRENT_EXTRA_EXPORT peer_connection + : peer_connection_hot_members + , aux::bandwidth_socket + , peer_class_set + , disk_observer + , peer_connection_interface + , std::enable_shared_from_this + { + friend struct invariant_access; + friend struct torrent; + friend struct cork; + + // explicitly disallow assignment, to silence msvc warning + peer_connection& operator=(peer_connection const&) = delete; + + void on_exception(std::exception const& e); + void on_error(error_code const& ec); + + virtual connection_type type() const = 0; + + enum channels + { + upload_channel, + download_channel, + num_channels + }; + + explicit peer_connection(peer_connection_args& pack); + + // this function is called after it has been constructed and properly + // reference counted. It is safe to call self() in this function + // and schedule events with references to itself (that is not safe to + // do in the constructor). + virtual void start(); + + ~peer_connection() override; + + void set_peer_info(torrent_peer* pi) override + { + TORRENT_ASSERT(m_peer_info == nullptr || pi == nullptr ); + TORRENT_ASSERT(pi != nullptr || m_disconnect_started); + m_peer_info = pi; + } + + torrent_peer* peer_info_struct() const override + { return m_peer_info; } + + // this is called when the peer object is created, in case + // it was let in by the connections limit slack. This means + // the peer needs to, as soon as the handshake is done, either + // disconnect itself or another peer. + void peer_exceeds_limit() + { m_exceeded_limit = true; } + + // this is called if this peer causes another peer + // to be disconnected, in which case it has fulfilled + // its requirement. + void peer_disconnected_other() + { m_exceeded_limit = false; } + + void send_allowed_set(); + +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extension(std::shared_ptr); + peer_plugin const* find_plugin(string_view type); +#endif + + // this function is called once the torrent associated + // with this peer connection has retrieved the meta- + // data. If the torrent was spawned with metadata + // this is called from the constructor. + void init(); + + // this is called when the metadata is retrieved + // and the files has been checked + virtual void on_metadata() {} + + void on_metadata_impl(); + + void picker_options(picker_options_t o) { m_picker_options = o; } + + int prefer_contiguous_blocks() const + { + if (on_parole()) return 1; + return int(m_prefer_contiguous_blocks); + } + + bool on_parole() const; + + picker_options_t picker_options() const; + + void prefer_contiguous_blocks(int const num) + { + m_prefer_contiguous_blocks = aux::clamp_assign(num); + } + + bool request_large_blocks() const + { return m_request_large_blocks; } + + void request_large_blocks(bool b) + { m_request_large_blocks = b; } + + void set_endgame(bool b); + bool endgame() const { return m_endgame_mode; } + + bool no_download() const { return m_no_download; } + void no_download(bool b) { m_no_download = b; } + + bool ignore_stats() const { return m_ignore_stats; } + void ignore_stats(bool b) { m_ignore_stats = b; } + + std::uint32_t peer_rank() const; + + void fast_reconnect(bool r); + bool fast_reconnect() const override { return m_fast_reconnect; } + + // this is called when we receive a new piece + // (and it has passed the hash check) + void received_piece(piece_index_t index); + + // this adds an announcement in the announcement queue + // it will let the peer know that we have the given piece + void announce_piece(piece_index_t index); + +#ifndef TORRENT_DISABLE_SUPERSEEDING + // this will tell the peer to announce the given piece + // and only allow it to request that piece + void superseed_piece(piece_index_t replace_piece, piece_index_t new_piece); + bool super_seeded_piece(piece_index_t index) const + { + return m_superseed_piece[0] == index + || m_superseed_piece[1] == index; + } +#endif + + // tells if this connection has data it want to send + // and has enough upload bandwidth quota left to send it. + bool can_write() const; + bool can_read(); + + bool is_seed() const; + int num_have_pieces() const { return m_num_pieces; } + +#ifndef TORRENT_DISABLE_SHARE_MODE + void set_share_mode(bool); + bool share_mode() const { return m_share_mode; } +#endif + + void set_upload_only(bool); + bool upload_only() const { return m_upload_only || is_seed() || m_have_all; } + + void set_holepunch_mode() override; + + // will send a keep-alive message to the peer + void keep_alive(); + + peer_id const& pid() const override { return m_peer_id; } + void set_pid(peer_id const& peer_id) { m_peer_id = peer_id; } + bool has_piece(piece_index_t i) const; + + std::vector const& download_queue() const; + std::vector const& request_queue() const; + std::vector const& upload_queue() const; + + void clear_request_queue(); + void clear_download_queue(); + + // estimate of how long it will take until we have + // received all piece requests that we have sent + // if extra_bytes is specified, it will include those + // bytes as if they've been requested + time_duration download_queue_time(int extra_bytes = 0) const; + + bool is_interesting() const { return m_interesting; } + bool is_choked() const override { return m_choked; } + + bool is_peer_interested() const { return m_peer_interested; } + bool has_peer_choked() const { return m_peer_choked; } + + void choke_this_peer(); + void maybe_unchoke_this_peer(); + + void update_interest(); + + void get_peer_info(peer_info& p) const override; + + // returns the torrent this connection is a part of + // may be zero if the connection is an incoming connection + // and it hasn't received enough information to determine + // which torrent it should be associated with + std::weak_ptr associated_torrent() const + { return m_torrent; } + + // get the info hash associated with this peer + // this will be a sha1 hash or truncated sha256 hash depending + // on which protocol version this connection is using + sha1_hash associated_info_hash() const; + + stat const& statistics() const override { return m_statistics; } + void add_stat(std::int64_t downloaded, std::int64_t uploaded) override; + void sent_bytes(int bytes_payload, int bytes_protocol); + void received_bytes(int bytes_payload, int bytes_protocol); + void trancieve_ip_packet(int bytes, bool ipv6); + void sent_syn(bool ipv6); + void received_synack(bool ipv6); + + // is called once every second by the main loop + void second_tick(int tick_interval_ms); + + aux::socket_type const& get_socket() const { return m_socket; } + aux::socket_type& get_socket() { return m_socket; } + tcp::endpoint const& remote() const override { return m_remote; } + tcp::endpoint local_endpoint() const override { return m_local; } + +#if TORRENT_USE_I2P + std::string const& destination() const override; + std::string const& local_i2p_endpoint() const override; +#endif + + typed_bitfield const& get_bitfield() const; + std::vector const& allowed_fast(); + std::vector const& suggested_pieces() const { return m_suggested_pieces; } + + time_point connected_time() const { return m_connect; } + time_point last_received() const { return m_last_receive.get(m_connect); } + + // this will cause this peer_connection to be disconnected. + void disconnect(error_code const& ec + , operation_t op, disconnect_severity_t = peer_connection_interface::normal) override; + + // called when a connect attempt fails (not when an + // established connection fails) + void connect_failed(error_code const& e); + bool is_disconnecting() const override { return m_disconnecting; } + + // this is called when the connection attempt has succeeded + // and the peer_connection is supposed to set m_connecting + // to false, and stop monitor writability + void on_connection_complete(error_code const& e); + + // returns true if this connection is still waiting to + // finish the connection attempt + bool is_connecting() const { return m_connecting; } + + // trust management. + virtual void received_valid_data(piece_index_t index); + // returns false if the peer should not be + // disconnected + virtual bool received_invalid_data(piece_index_t index, bool single_peer); + + // a connection is local if it was initiated by us. + // if it was an incoming connection, it is remote + bool is_outgoing() const final { return m_outgoing; } + + bool received_listen_port() const { return m_received_listen_port; } + void received_listen_port() + { m_received_listen_port = true; } + + bool on_local_network() const; + bool ignore_unchoke_slots() const; + + bool failed() const override { return m_failed; } + + int desired_queue_size() const + { + // this peer is in end-game mode we only want + // one outstanding request + return (m_endgame_mode || m_snubbed) ? 1 : m_desired_queue_size; + } + + // compares this connection against the given connection + // for which one is more eligible for an unchoke. + // returns true if this is more eligible + + int download_payload_rate() const { return m_statistics.download_payload_rate(); } + + // resets the byte counters that are used to measure + // the number of bytes transferred within unchoke cycles + void reset_choke_counters(); + + // if this peer connection is useless (neither party is + // interested in the other), disconnect it + // returns true if the connection was disconnected + bool disconnect_if_redundant(); + +#ifndef TORRENT_DISABLE_LOGGING + bool should_log(peer_log_alert::direction_t direction) const final; + void peer_log(peer_log_alert::direction_t direction + , char const* event, char const* fmt, ...) const noexcept final TORRENT_FORMAT(4,5); + void peer_log(peer_log_alert::direction_t direction + , char const* event) const noexcept; +#endif + + // the message handlers are called + // each time a recv() returns some new + // data, the last time it will be called + // is when the entire packet has been + // received, then it will no longer + // be called. i.e. most handlers need + // to check how much of the packet they + // have received before any processing + void incoming_keepalive(); + void incoming_choke(); + void incoming_unchoke(); + void incoming_interested(); + void incoming_not_interested(); + void incoming_have(piece_index_t piece_index); + void incoming_dont_have(piece_index_t piece_index); + void incoming_bitfield(typed_bitfield const& bits); + void incoming_request(peer_request const& r); + void incoming_piece(peer_request const& p, char const* data); + void incoming_piece_fragment(int bytes); + void start_receive_piece(peer_request const& r); + void incoming_cancel(peer_request const& r); + + bool can_disconnect(error_code const& ec) const; + void incoming_dht_port(int listen_port); + + void incoming_reject_request(peer_request const& r); + void incoming_have_all(); + void incoming_have_none(); + void incoming_allowed_fast(piece_index_t index); + void incoming_suggest(piece_index_t index); + + void set_has_metadata(bool m) { m_has_metadata = m; } + bool has_metadata() const { return m_has_metadata; } + + // the following functions appends messages + // to the send buffer + bool send_choke(); + bool send_unchoke(); + void send_interested(); + void send_not_interested(); + void send_suggest(piece_index_t piece); + void send_upload_only(bool enabled); + + void snub_peer(); + // reject any request in the request + // queue from this piece + void reject_piece(piece_index_t index); + + bool can_request_time_critical() const; + + // returns true if the specified block was actually made time-critical. + // if the block was already time-critical, it returns false. + bool make_time_critical(piece_block const& block); + + static constexpr request_flags_t time_critical = 0_bit; + static constexpr request_flags_t busy = 1_bit; + + // adds a block to the request queue + // returns true if successful, false otherwise + bool add_request(piece_block const& b, request_flags_t flags = {}); + + // clears the request queue and sends cancels for all messages + // in the download queue + void cancel_all_requests(); + + // removes a block from the request queue or download queue + // sends a cancel message if appropriate + // refills the request queue, and possibly ignoring pieces requested + // by peers in the ignore list (to avoid recursion) + // if force is true, the blocks is also freed from the piece + // picker, allowing another peer to request it immediately + void cancel_request(piece_block const& b, bool force = false); + void send_block_requests(); + void send_block_requests_impl(); + + void assign_bandwidth(int channel, int amount) override; + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + // is true until we can be sure that the other end + // speaks our protocol (be it bittorrent or http). + virtual bool in_handshake() const = 0; + + // returns the block currently being + // downloaded. And the progress of that + // block. If the peer isn't downloading + // a piece for the moment, implementers + // must return an object with the piece_index + // value invalid (the default constructor). + virtual piece_block_progress downloading_piece_progress() const; + + void send_buffer(span buf); + void setup_send(); + + template + void append_send_buffer(Holder buffer, int size) + { + TORRENT_ASSERT(is_single_thread()); + m_send_buffer.append_buffer(std::move(buffer), size); + } + + int outstanding_bytes() const { return m_outstanding_bytes; } + + int send_buffer_size() const + { return m_send_buffer.size(); } + + int send_buffer_capacity() const + { return m_send_buffer.capacity(); } + + void max_out_request_queue(int s); + int max_out_request_queue() const; + + std::time_t last_seen_complete() const { return m_last_seen_complete; } + void set_last_seen_complete(int ago) { m_last_seen_complete = aux::posix_time() - ago; } + + std::int64_t uploaded_in_last_round() const + { return m_statistics.total_payload_upload() - m_uploaded_at_last_round; } + + std::int64_t downloaded_in_last_round() const + { return m_statistics.total_payload_download() - m_downloaded_at_last_round; } + + std::int64_t uploaded_since_unchoked() const + { return m_statistics.total_payload_upload() - m_uploaded_at_last_unchoke; } + + // the time we last unchoked this peer + time_point time_of_last_unchoke() const + { return m_last_unchoke.get(m_connect); } + + // called when the disk write buffer is drained again, and we can + // start downloading payload again + void on_disk() override; + + int num_reading_bytes() const { return m_reading_bytes; } + + void setup_receive(); + + std::shared_ptr self() + { + TORRENT_ASSERT(!m_destructed); + TORRENT_ASSERT(m_in_use == 1337); + TORRENT_ASSERT(!m_in_constructor); + return shared_from_this(); + } + + counters& stats_counters() const { return m_counters; } + + int get_priority(int channel) const; + + protected: + + virtual void get_specific_peer_info(peer_info& p) const = 0; + + virtual void write_choke() = 0; + virtual void write_unchoke() = 0; + virtual void write_interested() = 0; + virtual void write_not_interested() = 0; + virtual void write_request(peer_request const& r) = 0; + virtual void write_cancel(peer_request const& r) = 0; + virtual void write_have(piece_index_t index) = 0; + virtual void write_dont_have(piece_index_t index) = 0; + virtual void write_keepalive() = 0; + virtual void write_piece(peer_request const& r, disk_buffer_holder buffer) = 0; + virtual void write_suggest(piece_index_t piece) = 0; + virtual void write_bitfield() = 0; + + virtual void write_reject_request(peer_request const& r) = 0; + virtual void write_allow_fast(piece_index_t piece) = 0; + virtual void write_upload_only(bool enabled) = 0; + + virtual void on_connected() = 0; + virtual void on_tick() {} + + // implemented by concrete connection classes + virtual void on_receive(error_code const& error + , std::size_t bytes_transferred) = 0; + virtual void on_sent(error_code const& error + , std::size_t bytes_transferred) = 0; + + void send_piece_suggestions(int num); + + virtual + std::tuple>> + hit_send_barrier(span> /* iovec */) + { + return std::make_tuple(INT_MAX + , span>()); + } + + void attach_to_torrent(info_hash_t const& ih); + + bool validate_piece_request(peer_request const& p) const; + + void update_desired_queue_size(); + + void set_send_barrier(int bytes) + { + TORRENT_ASSERT(bytes == INT_MAX || bytes <= send_buffer_size()); + m_send_barrier = bytes; + } + + int get_send_barrier() const { return m_send_barrier; } + + virtual int timeout() const; + + io_context& get_context() { return m_ios; } + + private: + + // callbacks for data being sent or received + void on_send_data(error_code const& error + , std::size_t bytes_transferred); + void on_receive_data(error_code const& error + , std::size_t bytes_transferred); + + void account_received_bytes(int bytes_transferred); + + void do_update_interest(); + void fill_send_buffer(); + void on_disk_read_complete(disk_buffer_holder buffer + , storage_error const& error, peer_request const&, time_point issue_time); + void on_disk_write_complete(storage_error const& error + , peer_request const&, std::shared_ptr); + void on_seed_mode_hashed(piece_index_t piece + , sha1_hash const& piece_hash, aux::vector const& block_hashes + , storage_error const& error); + + // this is for a future per-block request feature +#if 0 + void on_hash2_complete(storage_error const& error, peer_request const& r + , sha256_hash const& hash); +#endif + int request_timeout() const; + void check_graceful_pause(); + + int wanted_transfer(int channel); + int request_bandwidth(int channel, int bytes = 0); + + aux::socket_type m_socket; + + // the queue of blocks we have requested + // from this peer + aux::vector m_download_queue; + + // the queue of requests we have got + // from this peer that haven't been issued + // to the disk thread yet + aux::vector m_requests; + + // this peer's peer info struct. This may + // be 0, in case the connection is incoming + // and hasn't been added to a torrent yet. + torrent_peer* m_peer_info; + + // stats counters + counters& m_counters; + + // the number of pieces this peer + // has. Must be the same as + // std::count(m_have_piece.begin(), + // m_have_piece.end(), true) + int m_num_pieces; + + public: + // upload and download channel state + // enum from peer_info::bw_state + bandwidth_state_flags_t m_channel_state[2]; + + protected: + aux::receive_buffer m_recv_buffer; + + // number of bytes this peer can send and receive + int m_quota[2]; + + // the blocks we have reserved in the piece + // picker and will request from this peer. + std::vector m_request_queue; + + // this is the limit on the number of outstanding requests + // we have to this peer. This is initialized to the settings + // in the settings_pack. But it may be lowered + // if the peer is known to require a smaller limit (like BitComet). + // or if the extended handshake sets a limit. + // web seeds also has a limit on the queue size. + std::uint16_t m_max_out_request_queue; + + // this is the peer we're actually talking to + // it may not necessarily be the peer we're + // connected to, in case we use a proxy + tcp::endpoint m_remote; + + public: + aux::chained_buffer m_send_buffer; + private: + + // the disk thread to use to issue disk jobs to + disk_interface& m_disk_thread; + + // io service + io_context& m_ios; + + protected: +#ifndef TORRENT_DISABLE_EXTENSIONS + std::list> m_extensions; +#endif + private: + + // the average time between incoming pieces. Or, if there is no + // outstanding request, the time since the piece was requested. It + // is essentially an estimate of the time it will take to completely + // receive a payload message after it has been requested. + sliding_average m_request_time; + + // keep the io_context running as long as we + // have peer connections + executor_work_guard m_work; + + // the time when we last got a part of a + // piece packet from this peer + aux::relative_time m_last_piece; + + // the time we sent a request to + // this peer the last time + aux::relative_time m_last_request; + + // the time we received the last + // piece request from the peer + aux::relative_time m_last_incoming_request{aux::min_value}; + + // the time when we unchoked this peer + aux::relative_time m_last_unchoke; + + // if we're unchoked by this peer, this + // was the time + aux::relative_time m_last_unchoked; + + // the time we last choked this peer. min_time() in + // case we never unchoked it + aux::relative_time m_last_choke{aux::min_value}; + + // timeouts + aux::relative_time m_last_receive; + aux::relative_time m_last_sent; + + // the last time we filled our send buffer with payload + // this is used for timeouts + aux::relative_time m_last_sent_payload; + + // the time when the first entry in the request queue was requested. Used + // for request timeout. it doesn't necessarily represent the time when a + // specific request was made. Since requests can be handled out-of-order, + // it represents whichever request the other end decided to respond to. + // Once we get that response, we set it to the current time. + // for more information, see the blog post at: + // http://blog.libtorrent.org/2011/11/block-request-time-outs/ + aux::relative_time m_requested; + + // the time when this peer sent us a not_interested message + // the last time. + aux::relative_time m_became_uninterested; + + // the time when we sent a not_interested message to + // this peer the last time. + aux::relative_time m_became_uninteresting; + + // the time when async_connect was called + // or when the incoming connection was established + time_point m_connect = aux::time_now(); + + // the total payload download bytes + // at the last unchoke round. This is used to + // measure the number of bytes transferred during + // an unchoke cycle, to unchoke peers the more bytes + // they sent us + std::int64_t m_downloaded_at_last_round = 0; + std::int64_t m_uploaded_at_last_round = 0; + + // this is the number of bytes we had uploaded the + // last time this peer was unchoked. This does not + // reset each unchoke interval/round. This is used to + // track upload across rounds, for the full duration of + // the peer being unchoked. Specifically, it's used + // for the round-robin unchoke algorithm. + std::int64_t m_uploaded_at_last_unchoke = 0; + + // the number of payload bytes downloaded last second tick + std::int32_t m_downloaded_last_second = 0; + + // the number of payload bytes uploaded last second tick + std::int32_t m_uploaded_last_second = 0; + + // the number of bytes that the other + // end has to send us in order to respond + // to all outstanding piece requests we + // have sent to it + int m_outstanding_bytes = 0; + + aux::handler_storage m_read_handler_storage; + aux::handler_storage m_write_handler_storage; + + // these are pieces we have recently sent suggests for to this peer. + // it just serves as a queue to remember what we've sent, to avoid + // re-sending suggests for the same piece + // i.e. outgoing suggest pieces + aux::vector m_suggest_pieces; + + // the pieces we will send to the peer + // if requested (regardless of choke state) + std::vector m_accept_fast; + + // a sent-piece counter for the allowed fast set + // to avoid exploitation. Each slot is a counter + // for one of the pieces from the allowed-fast set + aux::vector m_accept_fast_piece_cnt; + + // the pieces the peer will send us if + // requested (regardless of choke state) + std::vector m_allowed_fast; + + // pieces that has been suggested to be downloaded from this peer + // i.e. incoming suggestions + // TODO: 2 this should really be a circular buffer + aux::vector m_suggested_pieces; + + // the time when this peer last saw a complete copy + // of this torrent + time_t m_last_seen_complete = 0; + + // the block we're currently receiving. Or + // (-1, -1) if we're not receiving one + piece_block m_receiving_block = piece_block::invalid; + + // the local endpoint for this peer, i.e. our address + // and our port. If this is set for outgoing connections + // before the connection completes, it means we want to + // force the connection to be bound to the specified interface. + // if it ends up being bound to a different local IP, the connection + // is closed. + tcp::endpoint m_local; + + // remote peer's id + peer_id m_peer_id; + + protected: + + template + void wrap(Fun f, Args&&... a); + + // statistics about upload and download speeds + // and total amount of uploads and downloads for + // this peer + // TODO: factor this out into its own class with a virtual interface + // torrent and session should implement this interface + stat m_statistics; + + // the number of outstanding bytes expected + // to be received by extensions + int m_extension_outstanding_bytes = 0; + + // the number of time critical requests + // queued up in the m_request_queue that + // soon will be committed to the download + // queue. This is included in download_queue_time() + // so that it can be used while adding more + // requests and take the previous requests + // into account without submitting it all + // immediately + std::uint16_t m_queued_time_critical = 0; + + // the number of bytes we are currently reading + // from disk, that will be added to the send + // buffer as soon as they complete + int m_reading_bytes = 0; + + // options used for the piece picker. These flags will + // be augmented with flags controlled by other settings + // like sequential download etc. These are here to + // let plugins control flags that should always be set + picker_options_t m_picker_options{}; + + // the number of invalid piece-requests + // we have got from this peer. If the request + // queue gets empty, and there have been + // invalid requests, we can assume the + // peer is waiting for those pieces. + // we can then clear its download queue + // by sending choke, unchoke. + std::uint16_t m_num_invalid_requests = 0; + +#ifndef TORRENT_DISABLE_SUPERSEEDING + // if [0] is -1, super-seeding is not active. If it is >= 0 + // this is the piece that is available to this peer. Only + // these two pieces can be downloaded from us by this peer. + // This will remain the current piece for this peer until + // another peer sends us a have message for this piece + std::array m_superseed_piece = {{piece_index_t(-1), piece_index_t(-1)}}; +#endif + + // the number of bytes send to the disk-io + // thread that hasn't yet been completely written. + int m_outstanding_writing_bytes = 0; + + // max transfer rates seen on this peer + int m_download_rate_peak = 0; + int m_upload_rate_peak = 0; + + // stop sending data after this many bytes, INT_MAX = inf + int m_send_barrier = INT_MAX; + + // the number of request we should queue up + // at the remote end. + // TODO: 2 rename this target queue size + std::uint16_t m_desired_queue_size = 4; + + // if set to non-zero, this peer will always prefer + // to request entire n pieces, rather than blocks. + // where n is the value of this variable. + // if it is 0, the download rate limit setting + // will be used to determine if whole pieces + // are preferred. + std::uint16_t m_prefer_contiguous_blocks = 0; + + // this is the number of times this peer has had + // a request rejected because of a disk I/O failure. + // once this reaches a certain threshold, the + // peer is disconnected in order to avoid infinite + // loops of consistent failures + std::uint8_t m_disk_read_failures = 0; + + // this is used in seed mode whenever we trigger a hash check + // for a piece, before we read it. It's used to throttle + // the hash checks to just a few per peer at a time. + std::uint8_t m_outstanding_piece_verification:3; + + // is true if it was we that connected to the peer + // and false if we got an incoming connection + // could be considered: true = local, false = remote + bool m_outgoing:1; + + // is true if we learn the incoming connections listening + // during the extended handshake + bool m_received_listen_port:1; + + // if this is true, the disconnection + // timestamp is not updated when the connection + // is closed. This means the time until we can + // reconnect to this peer is shorter, and likely + // immediate. + bool m_fast_reconnect:1; + + // this is set to true if the connection timed + // out or closed the connection. In that + // case we will not try to reconnect to + // this peer + bool m_failed:1; + + // this is set to true if the connection attempt + // succeeded. i.e. the TCP 3-way handshake + bool m_connected:1; + + // if this is true, the blocks picked by the piece + // picker will be merged before passed to the + // request function. i.e. subsequent blocks are + // merged into larger blocks. This is used by + // the http-downloader, to request whole pieces + // at a time. + bool m_request_large_blocks:1; + +#ifndef TORRENT_DISABLE_SHARE_MODE + // set to true if this peer is in share mode + bool m_share_mode:1; +#endif + + // set to true when this peer has told us explicitly that it is only + // uploading. A seed is *implicitly* upload only, so this is not + // necessarily true. + bool m_upload_only:1; + + // this is set to true once the bitfield is received + bool m_bitfield_received:1; + + // if this is set to true, the client will not + // pick any pieces from this peer + bool m_no_download:1; + + // indicates that we want to request more blocks from this peer + bool m_deferred_send_block_requests:1; + + // set to true while we're trying to holepunch + bool m_holepunch_mode:1; + + // the other side has told us that it won't send anymore + // data to us for a while + bool m_peer_choked:1; + + // this is set to true when a have_all + // message is received. This information + // is used to fill the bitmask in init() + bool m_have_all:1; + + // other side says that it's interested in downloading + // from us. + bool m_peer_interested:1; + + // set to true when we should recalculate interest + // for this peer. Since this is a fairly expensive + // operation, it's delayed until the second_tick is + // fired, so that multiple events that wants to recalc + // interest are coalesced into only triggering it once + // the actual computation is done in do_update_interest(). + bool m_need_interest_update:1; + + // set to true if this peer has metadata, and false + // otherwise. + bool m_has_metadata:1; + + // this is set to true if this peer was accepted exceeding + // the connection limit. It means it has to disconnect + // itself, or some other peer, as soon as it's completed + // the handshake. We need to wait for the handshake in + // order to know which torrent it belongs to, to know which + // other peers to compare it to. + bool m_exceeded_limit:1; + + // this is slow-start at the bittorrent layer. It affects how we increase + // desired queue size (i.e. the number of outstanding requests we keep). + // While the underlying transport protocol is in slow-start, the number of + // outstanding requests need to increase at the same pace to keep up. + bool m_slow_start:1; + +#if TORRENT_USE_ASSERTS + public: + bool m_in_constructor = true; + bool m_disconnect_started = false; + bool m_initialized = false; + int m_in_use = 1337; + int m_received_in_piece = 0; + bool m_destructed = false; + // this is true while there is an outstanding + // async write job on the socket + bool m_socket_is_writing = false; + bool is_single_thread() const; +#endif + }; + + struct cork + { + explicit cork(peer_connection& p): m_pc(p) + { + if (m_pc.m_channel_state[peer_connection::upload_channel] & peer_info::bw_network) + return; + + // pretend that there's an outstanding send operation already, to + // prevent future calls to setup_send() from actually causing an + // async_send() to be issued. + m_pc.m_channel_state[peer_connection::upload_channel] |= peer_info::bw_network; + m_need_uncork = true; + } + cork(cork const&) = delete; + cork& operator=(cork const&) = delete; + + ~cork() + { + if (!m_need_uncork) return; + try { + m_pc.m_channel_state[peer_connection::upload_channel] &= ~peer_info::bw_network; + m_pc.setup_send(); + } + catch (std::bad_alloc const&) { + m_pc.disconnect(make_error_code(boost::system::errc::not_enough_memory) + , operation_t::sock_write); + } + catch (boost::system::system_error const& err) { + m_pc.disconnect(err.code(), operation_t::sock_write); + } + catch (...) { + m_pc.disconnect(make_error_code(boost::system::errc::not_enough_memory) + , operation_t::sock_write); + } + } + private: + peer_connection& m_pc; + bool m_need_uncork = false; + }; + +} + +#endif // TORRENT_PEER_CONNECTION_HPP_INCLUDED diff --git a/include/libtorrent/peer_connection_handle.hpp b/include/libtorrent/peer_connection_handle.hpp new file mode 100644 index 0000000..e0c75b7 --- /dev/null +++ b/include/libtorrent/peer_connection_handle.hpp @@ -0,0 +1,158 @@ +/* + +Copyright (c) 2015, 2017, Steven Siloti +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2016-2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CONNECTION_HANDLE_HPP_INCLUDED +#define TORRENT_PEER_CONNECTION_HANDLE_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/operations.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/peer_connection.hpp" // for connection_type +#include "libtorrent/error_code.hpp" + +namespace libtorrent { + +struct bt_peer_connection; + +// the peer_connection_handle class provides a handle to the internal peer +// connection object, to be used by plugins. This is a low level interface that +// may not be stable across libtorrent versions +struct TORRENT_EXPORT peer_connection_handle +{ + explicit peer_connection_handle(std::weak_ptr impl) + : m_connection(std::move(impl)) + {} + + connection_type type() const; + + void add_extension(std::shared_ptr); + peer_plugin const* find_plugin(string_view type) const; + + bool is_seed() const; + + bool upload_only() const; + + peer_id const& pid() const; + bool has_piece(piece_index_t i) const; + + bool is_interesting() const; + bool is_choked() const; + + bool is_peer_interested() const; + bool has_peer_choked() const; + + void choke_this_peer(); + void maybe_unchoke_this_peer(); + + void get_peer_info(peer_info& p) const; + + torrent_handle associated_torrent() const; + + tcp::endpoint const& remote() const; + tcp::endpoint local_endpoint() const; + + void disconnect(error_code const& ec, operation_t op + , disconnect_severity_t = peer_connection_interface::normal); + bool is_disconnecting() const; + bool is_connecting() const; + bool is_outgoing() const; + + bool on_local_network() const; + bool ignore_unchoke_slots() const; + + bool failed() const; + + bool should_log(peer_log_alert::direction_t direction) const; + void peer_log(peer_log_alert::direction_t direction + , char const* event, char const* fmt = "", ...) const TORRENT_FORMAT(4,5); + + bool can_disconnect(error_code const& ec) const; + + bool has_metadata() const; + + bool in_handshake() const; + + void send_buffer(char const* begin, int size); + + std::time_t last_seen_complete() const; + time_point time_of_last_unchoke() const; + + bool operator==(peer_connection_handle const& o) const + { return !lt(m_connection, o.m_connection) && !lt(o.m_connection, m_connection); } + bool operator!=(peer_connection_handle const& o) const + { return lt(m_connection, o.m_connection) || lt(o.m_connection, m_connection); } + bool operator<(peer_connection_handle const& o) const + { return lt(m_connection, o.m_connection); } + + std::shared_ptr native_handle() const + { + return m_connection.lock(); + } + +private: + std::weak_ptr m_connection; + + // copied from boost::weak_ptr + bool lt(std::weak_ptr const& a + , std::weak_ptr const& b) const + { + return a.owner_before(b); + } +}; + +// The bt_peer_connection_handle provides a handle to the internal bittorrent +// peer connection object to plugins. It's low level and may not be a stable API +// across libtorrent versions. +struct TORRENT_EXPORT bt_peer_connection_handle : peer_connection_handle +{ + explicit bt_peer_connection_handle(peer_connection_handle pc) + : peer_connection_handle(std::move(pc)) + {} + + bool packet_finished() const; + bool support_extensions() const; + + bool supports_encryption() const; + + void switch_send_crypto(std::shared_ptr crypto); + void switch_recv_crypto(std::shared_ptr crypto); + + std::shared_ptr native_handle() const; +}; + +} // namespace libtorrent + +#endif // TORRENT_PEER_CONNECTION_HANDLE_HPP_INCLUDED diff --git a/include/libtorrent/peer_connection_interface.hpp b/include/libtorrent/peer_connection_interface.hpp new file mode 100644 index 0000000..ef8b0fe --- /dev/null +++ b/include/libtorrent/peer_connection_interface.hpp @@ -0,0 +1,88 @@ +/* + +Copyright (c) 2014-2019, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CONNECTION_INTERFACE_HPP +#define TORRENT_PEER_CONNECTION_INTERFACE_HPP + +#include "libtorrent/fwd.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/operations.hpp" // for operation_t enum +#include "libtorrent/units.hpp" + +namespace libtorrent { + + struct torrent_peer; + class stat; + + using disconnect_severity_t = aux::strong_typedef; + + // TODO: make this interface smaller! + struct TORRENT_EXTRA_EXPORT peer_connection_interface + { + static constexpr disconnect_severity_t normal{0}; + static constexpr disconnect_severity_t failure{1}; + static constexpr disconnect_severity_t peer_error{2}; + +#if TORRENT_USE_I2P + virtual std::string const& destination() const = 0; + virtual std::string const& local_i2p_endpoint() const = 0; +#endif + virtual tcp::endpoint const& remote() const = 0; + virtual tcp::endpoint local_endpoint() const = 0; + virtual void disconnect(error_code const& ec + , operation_t op, disconnect_severity_t = peer_connection_interface::normal) = 0; + virtual peer_id const& pid() const = 0; + virtual peer_id our_pid() const = 0; + virtual void set_holepunch_mode() = 0; + virtual torrent_peer* peer_info_struct() const = 0; + virtual void set_peer_info(torrent_peer* pi) = 0; + virtual bool is_outgoing() const = 0; + virtual void add_stat(std::int64_t downloaded, std::int64_t uploaded) = 0; + virtual bool fast_reconnect() const = 0; + virtual bool is_choked() const = 0; + virtual bool failed() const = 0; + virtual stat const& statistics() const = 0; + virtual void get_peer_info(peer_info& p) const = 0; +#ifndef TORRENT_DISABLE_LOGGING + virtual bool should_log(peer_log_alert::direction_t direction) const = 0; + virtual void peer_log(peer_log_alert::direction_t direction + , char const* event, char const* fmt = "", ...) const noexcept TORRENT_FORMAT(4,5) = 0; +#endif + protected: + ~peer_connection_interface() {} + }; +} + +#endif diff --git a/include/libtorrent/peer_id.hpp b/include/libtorrent/peer_id.hpp new file mode 100644 index 0000000..0f3a480 --- /dev/null +++ b/include/libtorrent/peer_id.hpp @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2003, 2009, 2013, 2016-2017, 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_ID_HPP_INCLUDED +#define TORRENT_PEER_ID_HPP_INCLUDED + +#include "libtorrent/sha1_hash.hpp" + +namespace libtorrent { + + using peer_id = sha1_hash; +} + +#endif // TORRENT_PEER_ID_HPP_INCLUDED diff --git a/include/libtorrent/peer_info.hpp b/include/libtorrent/peer_info.hpp new file mode 100644 index 0000000..451b099 --- /dev/null +++ b/include/libtorrent/peer_info.hpp @@ -0,0 +1,481 @@ +/* + +Copyright (c) 2003-2011, 2013-2021, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016, Alden Torres +Copyright (c) 2019, Amir Abrams +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_INFO_HPP_INCLUDED +#define TORRENT_PEER_INFO_HPP_INCLUDED + +#include "libtorrent/socket.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/aux_/deprecated.hpp" + +namespace libtorrent { + + // flags for the peer_info::flags field. Indicates various states + // the peer may be in. These flags are not mutually exclusive, but + // not every combination of them makes sense either. + using peer_flags_t = flags::bitfield_flag; + + // the flags indicating which sources a peer can + // have come from. A peer may have been seen from + // multiple sources + using peer_source_flags_t = flags::bitfield_flag; + + // flags indicating what is blocking network transfers in up- and down + // direction + using bandwidth_state_flags_t = flags::bitfield_flag; + + using connection_type_t = flags::bitfield_flag; + +TORRENT_VERSION_NAMESPACE_2 + + // holds information and statistics about one peer + // that libtorrent is connected to + struct TORRENT_EXPORT peer_info + { + // hidden + peer_info(); + ~peer_info(); + peer_info(peer_info const&); + peer_info(peer_info&&); + peer_info& operator=(peer_info const&); + + // A human readable string describing the software at the other end of + // the connection. In some cases this information is not available, then + // it will contain a string that may give away something about which + // software is running in the other end. In the case of a web seed, the + // server type and version will be a part of this string. This is UTF-8 + // encoded. + std::string client; + + // a bitfield, with one bit per piece in the torrent. Each bit tells you + // if the peer has that piece (if it's set to 1) or if the peer miss that + // piece (set to 0). + typed_bitfield pieces; + + // the total number of bytes downloaded from and uploaded to this peer. + // These numbers do not include the protocol chatter, but only the + // payload data. + std::int64_t total_download; + std::int64_t total_upload; + + // the time since we last sent a request to this peer and since any + // transfer occurred with this peer + time_duration last_request; + time_duration last_active; + + // the time until all blocks in the request queue will be downloaded + time_duration download_queue_time; + +#if TORRENT_ABI_VERSION == 1 + using peer_flags_t = libtorrent::peer_flags_t; + using peer_source_flags = libtorrent::peer_source_flags_t; +#endif + + // **we** are interested in pieces from this peer. + static constexpr peer_flags_t interesting = 0_bit; + + // **we** have choked this peer. + static constexpr peer_flags_t choked = 1_bit; + + // the peer is interested in **us** + static constexpr peer_flags_t remote_interested = 2_bit; + + // the peer has choked **us**. + static constexpr peer_flags_t remote_choked = 3_bit; + + // means that this peer supports the + // `extension protocol`__. + // + // __ extension_protocol.html + static constexpr peer_flags_t supports_extensions = 4_bit; + + // The connection was initiated by us, the peer has a + // listen port open, and that port is the same as in the + // address of this peer. If this flag is not set, this + // peer connection was opened by this peer connecting to + // us. + static constexpr peer_flags_t outgoing_connection = 5_bit; + + // deprecated synonym for outgoing_connection + static constexpr peer_flags_t local_connection = 5_bit; + + // The connection is opened, and waiting for the + // handshake. Until the handshake is done, the peer + // cannot be identified. + static constexpr peer_flags_t handshake = 6_bit; + + // The connection is in a half-open state (i.e. it is + // being connected). + static constexpr peer_flags_t connecting = 7_bit; + +#if TORRENT_ABI_VERSION == 1 + // The connection is currently queued for a connection + // attempt. This may happen if there is a limit set on + // the number of half-open TCP connections. + TORRENT_DEPRECATED static constexpr peer_flags_t queued = 8_bit; +#endif + + // The peer has participated in a piece that failed the + // hash check, and is now "on parole", which means we're + // only requesting whole pieces from this peer until + // it either fails that piece or proves that it doesn't + // send bad data. + static constexpr peer_flags_t on_parole = 9_bit; + + // This peer is a seed (it has all the pieces). + static constexpr peer_flags_t seed = 10_bit; + + // This peer is subject to an optimistic unchoke. It has + // been unchoked for a while to see if it might unchoke + // us in return an earn an upload/unchoke slot. If it + // doesn't within some period of time, it will be choked + // and another peer will be optimistically unchoked. + static constexpr peer_flags_t optimistic_unchoke = 11_bit; + + // This peer has recently failed to send a block within + // the request timeout from when the request was sent. + // We're currently picking one block at a time from this + // peer. + static constexpr peer_flags_t snubbed = 12_bit; + + // This peer has either explicitly (with an extension) + // or implicitly (by becoming a seed) told us that it + // will not downloading anything more, regardless of + // which pieces we have. + static constexpr peer_flags_t upload_only = 13_bit; + + // This means the last time this peer picket a piece, + // it could not pick as many as it wanted because there + // were not enough free ones. i.e. all pieces this peer + // has were already requested from other peers. + static constexpr peer_flags_t endgame_mode = 14_bit; + + // This flag is set if the peer was in holepunch mode + // when the connection succeeded. This typically only + // happens if both peers are behind a NAT and the peers + // connect via the NAT holepunch mechanism. + static constexpr peer_flags_t holepunched = 15_bit; + + // indicates that this socket is running on top of the + // I2P transport. + static constexpr peer_flags_t i2p_socket = 16_bit; + + // indicates that this socket is a uTP socket + static constexpr peer_flags_t utp_socket = 17_bit; + + // indicates that this socket is running on top of an SSL + // (TLS) channel + static constexpr peer_flags_t ssl_socket = 18_bit; + + // this connection is obfuscated with RC4 + static constexpr peer_flags_t rc4_encrypted = 19_bit; + + // the handshake of this connection was obfuscated + // with a Diffie-Hellman exchange + static constexpr peer_flags_t plaintext_encrypted = 20_bit; + + // tells you in which state the peer is in. It is set to + // any combination of the peer_flags_t flags above. + peer_flags_t flags; + + // The peer was received from the tracker. + static constexpr peer_source_flags_t tracker = 0_bit; + + // The peer was received from the kademlia DHT. + static constexpr peer_source_flags_t dht = 1_bit; + + // The peer was received from the peer exchange + // extension. + static constexpr peer_source_flags_t pex = 2_bit; + + // The peer was received from the local service + // discovery (The peer is on the local network). + static constexpr peer_source_flags_t lsd = 3_bit; + + // The peer was added from the fast resume data. + static constexpr peer_source_flags_t resume_data = 4_bit; + + // we received an incoming connection from this peer + static constexpr peer_source_flags_t incoming = 5_bit; + + // a combination of flags describing from which sources this peer + // was received. A combination of the peer_source_flags_t above. + peer_source_flags_t source; + + // the current upload and download speed we have to and from this peer + // (including any protocol messages). updated about once per second + int up_speed; + int down_speed; + + // The transfer rates of payload data only updated about once per second + int payload_up_speed; + int payload_down_speed; + + // the peer's id as used in the bittorrent protocol. This id can be used + // to extract 'fingerprints' from the peer. Sometimes it can tell you + // which client the peer is using. See identify_client()_ + peer_id pid; + + // the number of bytes we have requested from this peer, but not yet + // received. + int queue_bytes; + + // the number of seconds until the current front piece request will time + // out. This timeout can be adjusted through + // ``settings_pack::request_timeout``. + // -1 means that there is not outstanding request. + int request_timeout; + + // the number of bytes allocated + // and used for the peer's send buffer, respectively. + int send_buffer_size; + int used_send_buffer; + + // the number of bytes + // allocated and used as receive buffer, respectively. + int receive_buffer_size; + int used_receive_buffer; + int receive_buffer_watermark; + + // the number of pieces this peer has participated in sending us that + // turned out to fail the hash check. + int num_hashfails; + + // this is the number of requests we have sent to this peer that we + // haven't got a response for yet + int download_queue_length; + + // the number of block requests that have timed out, and are still in the + // download queue + int timed_out_requests; + + // the number of busy requests in the download queue. A busy request is a + // request for a block we've also requested from a different peer + int busy_requests; + + // the number of requests messages that are currently in the send buffer + // waiting to be sent. + int requests_in_buffer; + + // the number of requests that is tried to be maintained (this is + // typically a function of download speed) + int target_dl_queue_length; + + // the number of piece-requests we have received from this peer + // that we haven't answered with a piece yet. + int upload_queue_length; + + // the number of times this peer has "failed". i.e. failed to connect or + // disconnected us. The failcount is decremented when we see this peer in + // a tracker response or peer exchange message. + int failcount; + + // You can know which piece, and which part of that piece, that is + // currently being downloaded from a specific peer by looking at these + // four members. ``downloading_piece_index`` is the index of the piece + // that is currently being downloaded. This may be set to -1 if there's + // currently no piece downloading from this peer. If it is >= 0, the + // other three members are valid. ``downloading_block_index`` is the + // index of the block (or sub-piece) that is being downloaded. + // ``downloading_progress`` is the number of bytes of this block we have + // received from the peer, and ``downloading_total`` is the total number + // of bytes in this block. + piece_index_t downloading_piece_index; + int downloading_block_index; + int downloading_progress; + int downloading_total; + +#if TORRENT_ABI_VERSION <= 2 + using connection_type_t = libtorrent::connection_type_t; +#endif + // Regular bittorrent connection + static constexpr connection_type_t standard_bittorrent = 0_bit; + + // HTTP connection using the `BEP 19`_ protocol + static constexpr connection_type_t web_seed = 1_bit; + + // HTTP connection using the `BEP 17`_ protocol + static constexpr connection_type_t http_seed = 2_bit; + + // the kind of connection this peer uses. See connection_type_t. + connection_type_t connection_type; + +#if TORRENT_ABI_VERSION == 1 + // an estimate of the rate this peer is downloading at, in + // bytes per second. + TORRENT_DEPRECATED int remote_dl_rate; +#endif + + // the number of bytes this peer has pending in the disk-io thread. + // Downloaded and waiting to be written to disk. This is what is capped + // by ``settings_pack::max_queued_disk_bytes``. + int pending_disk_bytes; + + // number of outstanding bytes to read + // from disk + int pending_disk_read_bytes; + + // the number of bytes this peer has been assigned to be allowed to send + // and receive until it has to request more quota from the bandwidth + // manager. + int send_quota; + int receive_quota; + + // an estimated round trip time to this peer, in milliseconds. It is + // estimated by timing the TCP ``connect()``. It may be 0 for + // incoming connections. + int rtt; + + // the number of pieces this peer has. + int num_pieces; + + // the highest download and upload rates seen on this connection. They + // are given in bytes per second. This number is reset to 0 on reconnect. + int download_rate_peak; + int upload_rate_peak; + + // the progress of the peer in the range [0, 1]. This is always 0 when + // floating point operations are disabled, instead use ``progress_ppm``. + float progress; // [0, 1] + + // indicates the download progress of the peer in the range [0, 1000000] + // (parts per million). + int progress_ppm; + +#if TORRENT_ABI_VERSION == 1 + // this is an estimation of the upload rate, to this peer, where it will + // unchoke us. This is a coarse estimation based on the rate at which + // we sent right before we were choked. This is primarily used for the + // bittyrant choking algorithm. + TORRENT_DEPRECATED int estimated_reciprocation_rate; +#endif + + // the IP-address to this peer. The type is an asio endpoint. For + // more info, see the asio_ documentation. This field is not valid for + // i2p peers. Instead use the i2p_destination() function. + // + // .. _asio: http://asio.sourceforge.net/asio-0.3.8/doc/asio/reference.html + tcp::endpoint ip; + + // the IP and port pair the socket is bound to locally. i.e. the IP + // address of the interface it's going out over. This may be useful for + // multi-homed clients with multiple interfaces to the internet. + // This field is not valid for i2p peers. + tcp::endpoint local_endpoint; + + // The peer is not waiting for any external events to + // send or receive data. + static constexpr bandwidth_state_flags_t bw_idle = 0_bit; + + // The peer is waiting for the rate limiter. + static constexpr bandwidth_state_flags_t bw_limit = 1_bit; + + // The peer has quota and is currently waiting for a + // network read or write operation to complete. This is + // the state all peers are in if there are no bandwidth + // limits. + static constexpr bandwidth_state_flags_t bw_network = 2_bit; + + // The peer is waiting for the disk I/O thread to catch + // up writing buffers to disk before downloading more. + static constexpr bandwidth_state_flags_t bw_disk = 4_bit; + + // bitmasks indicating what state this peer + // is in with regards to sending and receiving data. The states are + // defined as independent flags of type bandwidth_state_flags_t, in this + // class. + bandwidth_state_flags_t read_state; + bandwidth_state_flags_t write_state; + +#if TORRENT_USE_I2P + // If this peer is an i2p peer, this function returns the destination + // address of the peer + sha256_hash i2p_destination() const; + + // internal + void set_i2p_destination(sha256_hash dest); +#endif + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED static constexpr bandwidth_state_flags_t bw_torrent = bw_limit; + TORRENT_DEPRECATED static constexpr bandwidth_state_flags_t bw_global = bw_limit; + + // the number of bytes per second we are allowed to send to or receive + // from this peer. It may be -1 if there's no local limit on the peer. + // The global limit and the torrent limit may also be enforced. + TORRENT_DEPRECATED int upload_limit; + TORRENT_DEPRECATED int download_limit; + + // a measurement of the balancing of free download (that we get) and free + // upload that we give. Every peer gets a certain amount of free upload, + // but this member says how much *extra* free upload this peer has got. + // If it is a negative number it means that this was a peer from which we + // have got this amount of free download. + TORRENT_DEPRECATED std::int64_t load_balancing; +#endif + }; + +TORRENT_VERSION_NAMESPACE_2_END + +#if TORRENT_ABI_VERSION == 1 + // internal + struct TORRENT_EXTRA_EXPORT peer_list_entry + { + // internal + enum flags_t + { + banned = 1 + }; + + // internal + tcp::endpoint ip; + // internal + int flags; + // internal + std::uint8_t failcount; + // internal + std::uint8_t source; + }; +#endif + +} + +#endif // TORRENT_PEER_INFO_HPP_INCLUDED diff --git a/include/libtorrent/peer_list.hpp b/include/libtorrent/peer_list.hpp new file mode 100644 index 0000000..502919b --- /dev/null +++ b/include/libtorrent/peer_list.hpp @@ -0,0 +1,271 @@ +/* + +Copyright (c) 2003-2005, 2007-2009, 2011-2012, 2014-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2009, Daniel Wallin +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2016, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_POLICY_HPP_INCLUDED +#define TORRENT_POLICY_HPP_INCLUDED + +#include + +#include "libtorrent/fwd.hpp" +#include "libtorrent/string_util.hpp" // for allocate_string_copy +#include "libtorrent/request_blocks.hpp" // for source_rank + +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/aux_/invariant_check.hpp" +#include "libtorrent/ip_voter.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/peer_connection_interface.hpp" +#include "libtorrent/aux_/deque.hpp" +#include "libtorrent/peer_info.hpp" // for peer_source_flags_t +#include "libtorrent/string_view.hpp" +#include "libtorrent/pex_flags.hpp" + +namespace libtorrent { + + struct torrent_peer_allocator_interface; + + // this object is used to communicate torrent state and + // some configuration to the peer_list object. This make + // the peer_list type not depend on the torrent type directly. + struct torrent_state + { + bool is_finished = false; + bool allow_multiple_connections_per_ip = false; + + // this is set by peer_list::add_peer to either true or false + // true means the peer we just added was new, false means + // we already knew about the peer + bool first_time_seen = false; + + int max_peerlist_size = 1000; + int min_reconnect_time = 60; + + // the number of iterations over the peer list for this operation + int loop_counter = 0; + + // these are used only by find_connect_candidates in order + // to implement peer ranking. See: + // http://blog.libtorrent.org/2012/12/swarm-connectivity/ + external_ip ip; + int port = 0; + + // the number of times a peer must fail before it's no longer considered + // a connect candidate + int max_failcount = 3; + + // if any peer were removed during this call, they are returned in + // this vector. The caller would want to make sure there are no + // references to these torrent_peers anywhere + std::vector erased; + }; + + struct erase_peer_flags_tag; + using erase_peer_flags_t = flags::bitfield_flag; + + struct TORRENT_EXTRA_EXPORT peer_list : single_threaded + { + explicit peer_list(torrent_peer_allocator_interface& alloc); + ~peer_list(); + + void clear(); + + // not copyable + peer_list(peer_list const&) = delete; + peer_list& operator=(peer_list const&) = delete; + +#if TORRENT_USE_I2P + torrent_peer* add_i2p_peer(string_view destination + , peer_source_flags_t src, pex_flags_t flags + , torrent_state* state); +#endif + + // this is called once for every torrent_peer we get from + // the tracker, pex, lsd or dht. + torrent_peer* add_peer(tcp::endpoint const& remote + , peer_source_flags_t, pex_flags_t, torrent_state*); + + // false means duplicate connection + bool update_peer_port(int port, torrent_peer* p + , peer_source_flags_t src + , torrent_state* state); + + // called when an incoming connection is accepted + // false means the connection was refused or failed + bool new_connection(peer_connection_interface& c, int session_time, torrent_state* state); + + // the given connection was just closed + void connection_closed(const peer_connection_interface& c + , int session_time, torrent_state* state); + + bool ban_peer(torrent_peer* p); + void set_connection(torrent_peer* p, peer_connection_interface* c); + void set_failcount(torrent_peer* p, int f); + void inc_failcount(torrent_peer* p); + + void apply_ip_filter(ip_filter const& filter, torrent_state* state + , std::vector
    & banned); + void apply_port_filter(port_filter const& filter, torrent_state* state + , std::vector& banned); + + void set_seed(torrent_peer* p, bool s); + + // this clears all cached peer priorities. It's called when + // our external IP changes + void clear_peer_prio(); + +#if TORRENT_USE_ASSERTS + bool has_connection(const peer_connection_interface* p); +#endif +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + int num_peers() const { return int(m_peers.size()); } + int num_candidate_cache() const { return int(m_candidate_cache.size()); } + + using peers_t = aux::deque; + using iterator = peers_t::iterator; + using const_iterator = peers_t::const_iterator; + iterator begin() { return m_peers.begin(); } + iterator end() { return m_peers.end(); } + const_iterator begin() const { return m_peers.begin(); } + const_iterator end() const { return m_peers.end(); } + + std::pair find_peers(address const& a) + { +#if TORRENT_USE_I2P + if (a == address()) + return std::pair(m_peers.end(), m_peers.end()); +#endif + return std::equal_range( + m_peers.begin(), m_peers.end(), a, peer_address_compare()); + } + + std::pair find_peers(address const& a) const + { + return std::equal_range( + m_peers.begin(), m_peers.end(), a, peer_address_compare()); + } + + torrent_peer* connect_one_peer(int session_time, torrent_state* state); + + bool has_peer(torrent_peer const* p) const; + + int num_seeds() const { return int(m_num_seeds); } + int num_connect_candidates() const { return m_num_connect_candidates; } + + void erase_peer(torrent_peer* p, torrent_state* state); + void erase_peer(iterator i, torrent_state* state); + + void set_max_failcount(torrent_state* st); + + private: + + void recalculate_connect_candidates(torrent_state* state); + + void update_connect_candidates(int delta); + + void update_peer(torrent_peer* p, peer_source_flags_t src + , pex_flags_t flags, tcp::endpoint const& remote); + bool insert_peer(torrent_peer* p, iterator iter + , pex_flags_t flags, torrent_state* state); + + void find_connect_candidates(std::vector& peers + , int session_time, torrent_state* state); + + bool is_connect_candidate(torrent_peer const& p) const; + bool is_erase_candidate(torrent_peer const& p) const; + bool is_force_erase_candidate(torrent_peer const& pe) const; + bool should_erase_immediately(torrent_peer const& p) const; + + static constexpr erase_peer_flags_t force_erase = 1_bit; + void erase_peers(torrent_state* state, erase_peer_flags_t flags = {}); + + peers_t m_peers; + + // this should be nullptr for the most part. It's set + // to point to a valid torrent_peer object if that + // object needs to be kept alive. If we ever feel + // like removing a torrent_peer from m_peers, we + // first check if the peer matches this one, and + // if so, don't delete it. + torrent_peer* m_locked_peer; + + // the peer allocator, as stored from the constructor + // this must be available in the destructor to free all peers + torrent_peer_allocator_interface& m_peer_allocator; + + // the number of seeds in the torrent_peer list + std::uint32_t m_num_seeds:31; + + // this was the state of the torrent the + // last time we recalculated the number of + // connect candidates. Since seeds (or upload + // only) peers are not connect candidates + // when we're finished, the set depends on + // this state. Every time m_torrent->is_finished() + // is different from this state, we need to + // recalculate the connect candidates. + std::uint32_t m_finished:1; + + // since the torrent_peer list can grow too large + // to scan all of it, start at this index + int m_round_robin = 0; + + // a list of good connect candidates + std::vector m_candidate_cache; + + // The number of peers in our torrent_peer list + // that are connect candidates. i.e. they're + // not already connected and they have not + // yet reached their max try count and they + // have the connectable state (we have a listen + // port for them). + int m_num_connect_candidates = 0; + + // if a peer has failed this many times or more, we don't consider + // it a connect candidate anymore. + int m_max_failcount = 3; + }; + +} + +#endif // TORRENT_POLICY_HPP_INCLUDED diff --git a/include/libtorrent/peer_request.hpp b/include/libtorrent/peer_request.hpp new file mode 100644 index 0000000..a96970b --- /dev/null +++ b/include/libtorrent/peer_request.hpp @@ -0,0 +1,59 @@ +/* + +Copyright (c) 2004, 2009, 2013-2014, 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_REQUEST_HPP_INCLUDED +#define TORRENT_PEER_REQUEST_HPP_INCLUDED + +#include "libtorrent/units.hpp" + +namespace libtorrent { + + // represents a byte range within a piece. Internally this is is used for + // incoming piece requests. + struct TORRENT_EXPORT peer_request + { + // The index of the piece in which the range starts. + piece_index_t piece; + // The byte offset within that piece where the range starts. + int start; + // The size of the range, in bytes. + int length; + + // returns true if the right hand side peer_request refers to the same + // range as this does. + bool operator==(peer_request const& r) const + { return piece == r.piece && start == r.start && length == r.length; } + }; +} + +#endif // TORRENT_PEER_REQUEST_HPP_INCLUDED diff --git a/include/libtorrent/performance_counters.hpp b/include/libtorrent/performance_counters.hpp new file mode 100644 index 0000000..db79c0e --- /dev/null +++ b/include/libtorrent/performance_counters.hpp @@ -0,0 +1,500 @@ +/* + +Copyright (c) 2014-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2019, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PERFORMANCE_COUNTERS_HPP_INCLUDED +#define TORRENT_PERFORMANCE_COUNTERS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/array.hpp" + +#include +#include +#include + +namespace libtorrent { + + struct TORRENT_EXPORT counters + { + // internal + enum stats_counter_t + { + // the number of peers that were disconnected this + // tick due to protocol error + error_peers, + disconnected_peers, + eof_peers, + connreset_peers, + connrefused_peers, + connaborted_peers, + notconnected_peers, + perm_peers, + buffer_peers, + unreachable_peers, + broken_pipe_peers, + addrinuse_peers, + no_access_peers, + invalid_arg_peers, + aborted_peers, + + piece_requests, + max_piece_requests, + invalid_piece_requests, + choked_piece_requests, + cancelled_piece_requests, + piece_rejects, + error_incoming_peers, + error_outgoing_peers, + error_rc4_peers, + error_encrypted_peers, + error_tcp_peers, + error_utp_peers, + + // the number of times the piece picker was + // successfully invoked, split by the reason + // it was invoked + reject_piece_picks, + unchoke_piece_picks, + incoming_redundant_piece_picks, + incoming_piece_picks, + end_game_piece_picks, + snubbed_piece_picks, + interesting_piece_picks, + hash_fail_piece_picks, + + // these counters indicate which parts + // of the piece picker CPU is spent in + piece_picker_partial_loops, + piece_picker_suggest_loops, + piece_picker_sequential_loops, + piece_picker_reverse_rare_loops, + piece_picker_rare_loops, + piece_picker_rand_start_loops, + piece_picker_rand_loops, + piece_picker_busy_loops, + + // reasons to disconnect peers + connect_timeouts, + uninteresting_peers, + timeout_peers, + no_memory_peers, + too_many_peers, + transport_timeout_peers, + num_banned_peers, + banned_for_hash_failure, + + // connection attempts (not necessarily successful) + connection_attempts, + // the number of iterations over the peer list when finding + // a connect candidate + connection_attempt_loops, + + // the number of peer connection attempts made as high + // priority connections for new torrents + boost_connection_attempts, + + // calls to torrent::connect_to_peer() that failed + missed_connection_attempts, + + // calls to peer_list::connect_one_peer() resulting in + // no peer candidate being found + no_peer_connection_attempts, + + // successful incoming connections (not rejected for any reason) + incoming_connections, + + // counts events where the network + // thread wakes up + on_read_counter, + on_write_counter, + on_tick_counter, + on_lsd_counter, + on_lsd_peer_counter, + on_udp_counter, + on_accept_counter, + on_disk_queue_counter, + on_disk_counter, + + // bittorrent message counters + // how about dont-have, share-mode, upload-only + num_incoming_choke, + num_incoming_unchoke, + num_incoming_interested, + num_incoming_not_interested, + num_incoming_have, + num_incoming_bitfield, + num_incoming_request, + num_incoming_piece, + num_incoming_cancel, + num_incoming_dht_port, + num_incoming_suggest, + num_incoming_have_all, + num_incoming_have_none, + num_incoming_reject, + num_incoming_allowed_fast, + num_incoming_ext_handshake, + num_incoming_pex, + num_incoming_metadata, + num_incoming_extended, + + num_outgoing_choke, + num_outgoing_unchoke, + num_outgoing_interested, + num_outgoing_not_interested, + num_outgoing_have, + num_outgoing_bitfield, + num_outgoing_request, + num_outgoing_piece, + num_outgoing_cancel, + num_outgoing_dht_port, + num_outgoing_suggest, + num_outgoing_have_all, + num_outgoing_have_none, + num_outgoing_reject, + num_outgoing_allowed_fast, + num_outgoing_ext_handshake, + num_outgoing_pex, + num_outgoing_metadata, + num_outgoing_extended, + num_outgoing_hash_request, + num_outgoing_hashes, + num_outgoing_hash_reject, + + num_piece_passed, + num_piece_failed, + + num_have_pieces, + num_total_pieces_added, + + num_blocks_written, + num_blocks_read, + num_blocks_hashed, + num_write_ops, + num_read_ops, + num_read_back, + + disk_read_time, + disk_write_time, + disk_hash_time, + disk_job_time, + + waste_piece_timed_out, + waste_piece_cancelled, + waste_piece_unknown, + waste_piece_seed, + waste_piece_end_game, + waste_piece_closing, + + sent_payload_bytes, + sent_bytes, + sent_ip_overhead_bytes, + sent_tracker_bytes, + recv_payload_bytes, + recv_bytes, + recv_ip_overhead_bytes, + recv_tracker_bytes, + + recv_failed_bytes, + recv_redundant_bytes, + + dht_messages_in, + dht_messages_in_dropped, + dht_messages_out, + dht_messages_out_dropped, + dht_bytes_in, + dht_bytes_out, + + dht_ping_in, + dht_ping_out, + dht_find_node_in, + dht_find_node_out, + dht_get_peers_in, + dht_get_peers_out, + dht_announce_peer_in, + dht_announce_peer_out, + dht_get_in, + dht_get_out, + dht_put_in, + dht_put_out, + dht_sample_infohashes_in, + dht_sample_infohashes_out, + + dht_invalid_announce, + dht_invalid_get_peers, + dht_invalid_find_node, + dht_invalid_put, + dht_invalid_get, + dht_invalid_sample_infohashes, + + // uTP counters. + utp_packet_loss, + utp_timeout, + utp_packets_in, + utp_packets_out, + utp_fast_retransmit, + utp_packet_resend, + utp_samples_above_target, + utp_samples_below_target, + utp_payload_pkts_in, + utp_payload_pkts_out, + utp_invalid_pkts_in, + utp_redundant_pkts_in, + + // the buffer sizes accepted by + // socket send calls. The larger + // the more efficient. The size is + // 1 << n, where n is the number + // at the end of the counter name + + // 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, + // 16384, 32768, 65536, 131072, 262144, 524288, 1048576 + socket_send_size3, + socket_send_size4, + socket_send_size5, + socket_send_size6, + socket_send_size7, + socket_send_size8, + socket_send_size9, + socket_send_size10, + socket_send_size11, + socket_send_size12, + socket_send_size13, + socket_send_size14, + socket_send_size15, + socket_send_size16, + socket_send_size17, + socket_send_size18, + socket_send_size19, + socket_send_size20, + + // the buffer sizes returned by + // socket recv calls. The larger + // the more efficient. The size is + // 1 << n, where n is the number + // at the end of the counter name + + // 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, + // 16384, 32768, 65536, 131072, 262144, 524288, 1048576 + socket_recv_size3, + socket_recv_size4, + socket_recv_size5, + socket_recv_size6, + socket_recv_size7, + socket_recv_size8, + socket_recv_size9, + socket_recv_size10, + socket_recv_size11, + socket_recv_size12, + socket_recv_size13, + socket_recv_size14, + socket_recv_size15, + socket_recv_size16, + socket_recv_size17, + socket_recv_size18, + socket_recv_size19, + socket_recv_size20, + + num_stats_counters + }; + + // == ALL FOLLOWING ARE GAUGES == + + // it is important that all gauges have a higher index than counters. + // This assumption is relied upon in other parts of the code + + // internal + enum stats_gauge_t + { + num_checking_torrents = num_stats_counters, + num_stopped_torrents, + num_upload_only_torrents, // upload_only means finished + num_downloading_torrents, + num_seeding_torrents, + num_queued_seeding_torrents, + num_queued_download_torrents, + num_error_torrents, + + // the number of torrents that don't have the + // IP filter applied to them. + non_filter_torrents, + + // these counter indices deliberately + // match the order of socket type IDs + // defined in socket_type.hpp. + num_tcp_peers, + num_socks5_peers, + num_http_proxy_peers, + num_utp_peers, + num_i2p_peers, + num_ssl_peers, + num_ssl_socks5_peers, + num_ssl_http_proxy_peers, + num_ssl_utp_peers, + + // the number of peer connections that are half-open (i.e. in the + // process of completing a connection attempt) and fully connected. + // These states are mutually exclusive (a connection cannot be in both + // states simultaneously). + num_peers_half_open, + num_peers_connected, + + // the number of peers interested in us (``up_interested``) and peers + // we are interested in (``down_interested``). + num_peers_up_interested, + num_peers_down_interested, + + // the total number of unchoked peers (``up_unchoked_all``), the number + // of peers unchoked via the optimistic unchoke + // (``up_unchoked_optimistic``) and peers unchoked via the + // reciprocation (regular) unchoke mechanism (``up_unchoked``). + // and the number of peers that have unchoked us (``down_unchoked``). + num_peers_up_unchoked_all, + num_peers_up_unchoked_optimistic, + num_peers_up_unchoked, + num_peers_down_unchoked, + + // the number of peers with at least one piece request pending, + // downloading (``down_requests``) or uploading (``up_requests``) + num_peers_up_requests, + num_peers_down_requests, + + // the number of peers that have at least one outstanding disk request, + // either reading (``up_disk``) or writing (``down_disk``). + num_peers_up_disk, + num_peers_down_disk, + + // the number of peers in end-game mode. End game mode is where there + // are no blocks that we have not sent any requests to download. In this + // mode, blocks are allowed to be requested from more than one peer at + // at time. + num_peers_end_game, + request_latency, + + disk_blocks_in_use, + queued_disk_jobs, + num_running_disk_jobs, + num_read_jobs, + num_write_jobs, + num_jobs, + num_writing_threads, + num_running_threads, + blocked_disk_jobs, + queued_write_bytes, + num_unchoke_slots, + + num_fenced_read, + num_fenced_write, + num_fenced_hash, + num_fenced_move_storage, + num_fenced_release_files, + num_fenced_delete_files, + num_fenced_check_fastresume, + num_fenced_save_resume_data, + num_fenced_rename_file, + num_fenced_stop_torrent, + num_fenced_flush_piece, + num_fenced_flush_hashed, + num_fenced_flush_storage, + num_fenced_file_priority, + num_fenced_load_torrent, + num_fenced_clear_piece, + num_fenced_tick_storage, + + dht_nodes, + dht_node_cache, + dht_torrents, + dht_peers, + dht_immutable_data, + dht_mutable_data, + dht_allocated_observers, + + has_incoming_connections, + + limiter_up_queue, + limiter_down_queue, + limiter_up_bytes, + limiter_down_bytes, + + // the number of uTP connections in each respective state + // these must be defined in the same order as the state_t enum + // in utp_stream + num_utp_idle, + num_utp_syn_sent, + num_utp_connected, + num_utp_fin_sent, + num_utp_close_wait, + num_utp_deleted, + + num_outstanding_accept, + + num_queued_tracker_announces, + + num_counters, + num_gauges_counters = num_counters - num_stats_counters + }; +#ifdef ATOMIC_LLONG_LOCK_FREE +#define TORRENT_COUNTER_NOEXCEPT noexcept +#else +#define TORRENT_COUNTER_NOEXCEPT +#endif + + counters() TORRENT_COUNTER_NOEXCEPT; + + counters(counters const&) TORRENT_COUNTER_NOEXCEPT; + counters& operator=(counters const&) & TORRENT_COUNTER_NOEXCEPT; + + // returns the new value + std::int64_t inc_stats_counter(int c, std::int64_t value = 1) TORRENT_COUNTER_NOEXCEPT; + std::int64_t operator[](int i) const TORRENT_COUNTER_NOEXCEPT; + + void set_value(int c, std::int64_t value) TORRENT_COUNTER_NOEXCEPT; + void blend_stats_counter(int c, std::int64_t value, int ratio) TORRENT_COUNTER_NOEXCEPT; + + private: + + // TODO: some space could be saved here by making gauges 32 bits + // TODO: restore these to regular integers. Instead have one copy + // of the counters per thread and collect them at convenient + // synchronization points +#ifdef ATOMIC_LLONG_LOCK_FREE + aux::array, num_counters> m_stats_counter; +#else + // if the atomic type isn't lock-free, use a single lock instead, for + // the whole array + mutable std::mutex m_mutex; + aux::array m_stats_counter; +#endif + }; +} + +#endif diff --git a/include/libtorrent/pex_flags.hpp b/include/libtorrent/pex_flags.hpp new file mode 100644 index 0000000..6310f3b --- /dev/null +++ b/include/libtorrent/pex_flags.hpp @@ -0,0 +1,65 @@ +/* + +Copyright (c) 2017-2020, Arvid Norberg +Copyright (c) 2018, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEX_FLAGS_HPP_INCLUDE +#define TORRENT_PEX_FLAGS_HPP_INCLUDE + +#include + +#include "libtorrent/flags.hpp" + +namespace libtorrent { + + using pex_flags_t = flags::bitfield_flag; + + // the peer supports protocol encryption + constexpr pex_flags_t pex_encryption = 0_bit; + + // the peer is a seed + constexpr pex_flags_t pex_seed = 1_bit; + + // the peer supports the uTP, transport protocol over UDP. + constexpr pex_flags_t pex_utp = 2_bit; + + // the peer supports the holepunch extension If this flag is received from a + // peer, it can be used as a rendezvous point in case direct connections to + // the peer fail + constexpr pex_flags_t pex_holepunch = 3_bit; + + // protocol v2 + // this is not a standard flag, it is only used internally + constexpr pex_flags_t pex_lt_v2 = 7_bit; +} + +#endif + diff --git a/include/libtorrent/piece_block.hpp b/include/libtorrent/piece_block.hpp new file mode 100644 index 0000000..41256ab --- /dev/null +++ b/include/libtorrent/piece_block.hpp @@ -0,0 +1,69 @@ +/* + +Copyright (c) 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PIECE_BLOCK_HPP_INCLUDED +#define TORRENT_PIECE_BLOCK_HPP_INCLUDED + +#include "libtorrent/units.hpp" + +namespace libtorrent { + + struct TORRENT_EXPORT piece_block + { + static const piece_block invalid; + + piece_block() = default; + piece_block(piece_index_t p_index, int b_index) + : piece_index(p_index) + , block_index(b_index) + { + } + piece_index_t piece_index {0}; + int block_index = 0; + + bool operator<(piece_block const& b) const + { + if (piece_index < b.piece_index) return true; + if (piece_index == b.piece_index) return block_index < b.block_index; + return false; + } + + bool operator==(piece_block const& b) const + { return piece_index == b.piece_index && block_index == b.block_index; } + + bool operator!=(piece_block const& b) const + { return piece_index != b.piece_index || block_index != b.block_index; } + }; +} +#endif diff --git a/include/libtorrent/piece_block_progress.hpp b/include/libtorrent/piece_block_progress.hpp new file mode 100644 index 0000000..16c6a53 --- /dev/null +++ b/include/libtorrent/piece_block_progress.hpp @@ -0,0 +1,61 @@ +/* + +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2005, 2014, 2016-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PIECE_BLOCK_PROGRESS_HPP_INCLUDED +#define TORRENT_PIECE_BLOCK_PROGRESS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/units.hpp" + +namespace libtorrent { + + struct piece_block_progress + { + static constexpr piece_index_t invalid_index{-1}; + + // the piece and block index + // determines exactly which + // part of the torrent that + // is currently being downloaded + piece_index_t piece_index{invalid_index}; + int block_index; + // the number of bytes we have received + // of this block + int bytes_downloaded; + // the number of bytes in the block + int full_block_bytes; + }; +} + +#endif // TORRENT_PIECE_BLOCK_PROGRESS_HPP_INCLUDED diff --git a/include/libtorrent/piece_picker.hpp b/include/libtorrent/piece_picker.hpp new file mode 100644 index 0000000..761a297 --- /dev/null +++ b/include/libtorrent/piece_picker.hpp @@ -0,0 +1,925 @@ +/* + +Copyright (c) 2003-2021, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2019, Steven Siloti +Copyright (c) 2021, Denis Kuzmenok +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef TORRENT_PIECE_PICKER_HPP_INCLUDED +#define TORRENT_PIECE_PICKER_HPP_INCLUDED + +// heavy weight reference counting invariant checks +//#define TORRENT_DEBUG_REFCOUNTS + +#include "libtorrent/config.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/piece_block.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/array.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/alert_types.hpp" // for picker_flags_t +#include "libtorrent/download_priority.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/index_range.hpp" + +namespace libtorrent { + + struct torrent; + struct peer_connection; + template + struct typed_bitfield; + struct counters; + struct torrent_peer; + + using prio_index_t = aux::strong_typedef; + using picker_options_t = flags::bitfield_flag; + using download_queue_t = aux::strong_typedef; + using piece_extent_t = aux::strong_typedef; + + struct piece_count + { + // the number of pieces included in the "set" + int num_pieces; + // the number of bytes, out of those pieces, that are pad + // files + std::int64_t pad_bytes; + // true if the last piece is part of the set + bool last_piece; + }; + + // the piece picker tracks: + // 1. The blocks in which pieces we have sent requests for + // 2. Which peers we sent the requests to + // 3. The availability of each piece + // 4. The priority of each piece + // 5. Which blocks and pieces have been written to disk (versus being in the cache) + // 6. Which pieces have passed the hash check (i.e. we have them) + // 7. Cursors for sequential download + // 8. The number of pad-bytes in each piece + // All this in a data structure to make it cheap to pick the next piece to + // request from a peer. + // If we "have" a piece, it means it has passed the hash check. If a piece + // has been "flushed", it means it's been stored to disk. + // When saving resume data, we only care about "flushed" pieces. + struct TORRENT_EXTRA_EXPORT piece_picker + { + // only defined when TORRENT_PICKER_LOG is defined, used for debugging + // unit tests + friend void print_pieces(piece_picker const& p); + + public: + + enum + { + // the number of priority levels + priority_levels = 8, + // priority factor + prio_factor = 3, + // max blocks per piece + // there are counters in downloading_piece that only have 15 bits to + // count blocks per piece, that's restricting this + max_blocks_per_piece = (1 << 15) - 1 + }; + + struct block_info + { + block_info(): num_peers(0), state(state_none) {} + // the peer this block was requested or + // downloaded from. + torrent_peer* peer = nullptr; + // the number of peers that has this block in their + // download or request queues + unsigned num_peers:14; + // the state of this block + enum { state_none, state_requested, state_writing, state_finished }; + unsigned state:2; +#if TORRENT_USE_ASSERTS + // to allow verifying the invariant of blocks belonging to the right piece + piece_index_t piece_index{-1}; + std::set peers; +#endif + }; + + // pick rarest first + static constexpr picker_options_t rarest_first = 0_bit; + + // pick the most common first, or the last pieces if sequential + static constexpr picker_options_t reverse = 1_bit; + + // only pick pieces exclusively requested from this peer + static constexpr picker_options_t on_parole = 2_bit; + + // always pick partial pieces before any other piece + static constexpr picker_options_t prioritize_partials = 3_bit; + + // pick pieces in sequential order + static constexpr picker_options_t sequential = 4_bit; + + // 5_bit is available + + // only expands pieces (when prefer contiguous blocks is set) + // within properly aligned ranges, not the largest possible + // range of pieces. + static constexpr picker_options_t align_expanded_pieces = 6_bit; + + // this will create an affinity to pick pieces in extents of 4 MiB, in an + // attempt to improve disk I/O by picking ranges of pieces (if pieces are + // small) + static constexpr picker_options_t piece_extent_affinity = 7_bit; + + struct downloading_piece + { + downloading_piece() + : finished(0) + , passed_hash_check(0) + , writing(0) + , locked(0) + , requested(0) + , hashing(0) {} + + bool operator<(downloading_piece const& rhs) const { return index < rhs.index; } + + // the index of the piece + piece_index_t index{(std::numeric_limits::max)()}; + + // info about each block in this piece. this is an index into the + // m_block_info array, when multiplied by blocks_per_piece. + // The blocks_per_piece following entries contain information about + // all blocks in this piece. + std::uint16_t info_idx{(std::numeric_limits::max)()}; + + // the number of blocks in the finished state + std::uint16_t finished:15; + + // set to true when the hash check job + // returns with a valid hash for this piece. + // we might not 'have' the piece yet though, + // since it might not have been written to + // disk. This is not set of locked is + // set. + std::uint16_t passed_hash_check:1; + + // the number of blocks in the writing state + std::uint16_t writing:15; + + // when this is set, blocks from this piece may + // not be picked. This is used when the hash check + // fails or writing to the disk fails, while waiting + // to synchronize the disk thread and clear out any + // remaining state. Once this synchronization is + // done, restore_piece() is called to clear the + // locked flag. + std::uint16_t locked:1; + + // the number of blocks in the requested state + std::uint16_t requested:15; + +#if 1 + // set to 1 if there is an outstanding hash request for this piece + std::uint16_t hashing:1; + + // this is for a future per-block request feature +#else + // available for future use + std::uint16_t unused:1; + + // number of outstanding hash jobs for this piece + std::uint16_t hashing:15; + + // available for future use + std::uint16_t unused2:1; +#endif + }; + + piece_picker(std::int64_t total_size, int piece_size); + + void get_availability(aux::vector& avail) const; + int get_availability(piece_index_t piece) const; + + // increases the peer count for the given piece + // (is used when a HAVE message is received) + void inc_refcount(piece_index_t, torrent_peer const*); + void dec_refcount(piece_index_t, torrent_peer const*); + + // increases the peer count for the given piece + // (is used when a BITFIELD message is received) + void inc_refcount(typed_bitfield const& bitmask + , const torrent_peer* peer); + // decreases the peer count for the given piece + // (used when a peer disconnects) + void dec_refcount(typed_bitfield const& bitmask + , const torrent_peer* peer); + + // these will increase and decrease the peer count + // of all pieces. They are used when seeds join + // or leave the swarm. + void inc_refcount_all(const torrent_peer* peer); + void dec_refcount_all(const torrent_peer* peer); + + // we have every piece. This is used when creating a piece picker for a + // seed + void we_have_all(); + + // A piece completes when it has passed the hash check *and* been + // completely written to disk. The piece picker no longer need to track + // the state of individual blocks + // The refcounter will indicate that + // we are not interested in this piece anymore + // (i.e. we don't have to maintain a refcount) + void piece_flushed(piece_index_t); + void we_dont_have(piece_index_t); + + // the lowest piece index we do not have + piece_index_t cursor() const { return m_cursor; } + + // one past the last piece we do not have. + piece_index_t reverse_cursor() const { return m_reverse_cursor; } + + // sets all pieces to dont-have + void resize(std::int64_t total_size, int piece_size); + int num_pieces() const { return int(m_piece_map.size()); } + + // returns true if we have the piece or if the piece + // has passed the hash check + bool have_piece(piece_index_t) const; + + // returns true if the piece has been completely downloaded and + // successfully flushed to disk (i.e. "finished"). + bool is_piece_flushed(piece_index_t) const; + + bool is_downloading(piece_index_t const index) const + { + TORRENT_ASSERT(index >= piece_index_t(0)); + TORRENT_ASSERT(index < m_piece_map.end_index()); + + piece_pos const& p = m_piece_map[index]; + return p.downloading(); + } + + // sets the priority of a piece. + // returns true if the priority was changed from 0 to non-0 + // or vice versa + bool set_piece_priority(piece_index_t, download_priority_t); + + // returns the priority for the piece at 'index' + download_priority_t piece_priority(piece_index_t) const; + + // returns the current piece priorities for all pieces + void piece_priorities(std::vector&) const; + + // This function returns a list of all blocks in pieces that this client + // has and that are interesting to download, in the + // ``interesting_blocks`` out-parameter. The blocks are returned in + // priority order. + // + // If the caller of this function decides to download a block, it must + // mark it as being downloaded itself, by using the + // mark_as_downloading() member function. THIS IS DONE BY THE + // peer_connection::send_request() MEMBER FUNCTION! + // + // ``pieces`` should be the bitfield of all pieces, indicating which + // pieces the client has, that can be requested from it. + // + // The last argument is the torrent_peer pointer for the peer that + // we'll download from. + // + // ``prefer_contiguous_blocks`` indicates how many blocks we would like + // to request contiguously. The blocks are not merged by the piece + // picker, but may be coalesced later by the caller. This feature is + // used by web_peer_connection to request larger blocks at a time to + // mitigate limited pipelining and lack of keep-alive (i.e. higher + // overhead per request). + picker_flags_t pick_pieces(typed_bitfield const& pieces + , std::vector& interesting_blocks, int num_blocks + , int prefer_contiguous_blocks, torrent_peer* peer + , picker_options_t options, std::vector const& suggested_pieces + , int num_peers + , counters& pc + ) const; + + // picks blocks from each of the pieces in the piece_list + // vector that is also in the piece bitmask. The blocks + // are added to interesting_blocks, and busy blocks are + // added to backup_blocks. num blocks is the number of + // blocks to be picked. Blocks are not picked from pieces + // that are being downloaded + int add_blocks(piece_index_t piece, typed_bitfield const& pieces + , std::vector& interesting_blocks + , std::vector& backup_blocks + , std::vector& backup_blocks2 + , int num_blocks, int prefer_contiguous_blocks + , torrent_peer* peer, std::vector& ignore + , picker_options_t options) const; + + // clears the peer pointer in all downloading pieces with this + // peer pointer + void clear_peer(torrent_peer* peer); + +#if TORRENT_USE_INVARIANT_CHECKS + // this is an invariant check + void check_peers(); +#endif + + // returns true if any client is currently downloading this + // piece-block, or if it's queued for downloading by some client + // or if it already has been successfully downloaded + bool is_requested(piece_block block) const; + // returns true if the block has been downloaded + bool is_downloaded(piece_block block) const; + // returns true if the block has been downloaded and written to disk + bool is_finished(piece_block block) const; + + // marks this piece-block as queued for downloading + // options are flags from options_t. + bool mark_as_downloading(piece_block block, torrent_peer* peer + , picker_options_t options = {}); + + // returns true if the block was marked as writing, + // and false if the block is already finished or writing + bool mark_as_writing(piece_block block, torrent_peer* peer); + + void started_hash_job(piece_index_t piece); + void completed_hash_job(piece_index_t piece); + + void mark_as_canceled(piece_block block, torrent_peer* peer); + void mark_as_finished(piece_block block, torrent_peer* peer); + + void set_pad_bytes(piece_index_t p, int bytes); + + // prevent blocks from being picked from this piece. + // to unlock the piece, call restore_piece() on it + void lock_piece(piece_index_t piece); + + void write_failed(piece_block block); + int num_peers(piece_block block) const; + + void piece_passed(piece_index_t); + + // returns information about the given piece + void piece_info(piece_index_t, piece_picker::downloading_piece& st) const; + + struct piece_stats_t + { + int peer_count; + int priority; + bool have; + bool downloading; + }; + + piece_stats_t piece_stats(piece_index_t) const; + + // if a piece had a hash-failure, it must be restored and + // made available for redownloading + void restore_piece(piece_index_t, span blocks = {}); + + // clears the given piece's download flag + // this means that this piece-block can be picked again + void abort_download(piece_block block, torrent_peer* peer = nullptr); + + // returns true if all blocks in this piece are finished + // or if we have the piece + bool is_piece_finished(piece_index_t) const; + + // returns true if at least one block in this piece is being hashed + // only valid for v2 torrents + bool is_hashing(piece_index_t piece) const; + + // returns the number of blocks there is in the given piece + int blocks_in_piece(piece_index_t) const; + + // return the peer pointers to all peers that participated in + // this piece + std::vector get_downloaders(piece_index_t) const; + + std::vector get_download_queue() const; + int get_download_queue_size() const; + + void get_download_queue_sizes(int* partial + , int* full, int* finished, int* zero_prio) const; + + torrent_peer* get_downloader(piece_block block) const; + + + // piece states + // + // have: ----------- + // pieces: # # # # # # # # # # # + // filtered: ------- + // pads blk: ^ ^ ^ + // + // want-have: * * * * + // want: * * * * * * * + // total-have: * * * * * * + // + // we only care about: + // 1. pieces we have (less pad blocks we have) + // 2. pieces we have AND want (less pad blocks we have and want) + // 3. pieces we want (less pad blocks we want) + + // number of pieces not filtered, as well as the number of + // blocks out of those pieces that are pad blocks. + // ``last_piece`` is set if the last piece is one of the + // pieces. + piece_count want() const; + + // number of pieces we have out of the ones we have not filtered + piece_count have_want() const; + + // number of pieces we have (regardless of whether they are filtered) + piece_count have() const; + + piece_count all_pieces() const; + + int pad_bytes_in_piece(piece_index_t const index) const; + + // number of pieces whose hash has passed (but haven't necessarily + // been flushed to disk yet) + int num_have() const { return m_num_have; } + + // return true if all the pieces we want have passed the hash check (but + // may not have been written to disk yet) + bool is_finished() const + { + // this expression warrants some explanation: + // if the number of pieces we *want* to download + // is less than or (more likely) equal to the number of pieces that + // have passed the hash check (discounting the pieces that have passed + // the check but then had their priority set to 0). Then we're + // finished. Note that any piece we *have* implies it's both passed the + // hash check *and* been written to disk. + // num_pieces() - m_num_filtered - m_num_have_filtered + // <= (m_num_have - m_num_have_filtered) + // this can be simplified. Note how m_num_have_filtered appears on both + // side of the equation. + // + return num_pieces() - m_num_filtered <= m_num_have; + } + + bool is_seeding() const { return m_num_have == num_pieces(); } + + // the number of pieces we want and don't have + int num_want_left() const { return num_pieces() - m_num_have - m_num_filtered + m_num_have_filtered; } + +#if TORRENT_USE_INVARIANT_CHECKS + void check_piece_state() const; + // used in debug mode + void verify_priority(prio_index_t start, prio_index_t end, int prio) const; + void verify_pick(span picked + , typed_bitfield const& bits) const; + + void check_peer_invariant(typed_bitfield const& have + , torrent_peer const* p) const; + void check_invariant(const torrent* t = nullptr) const; +#endif + + // functor that compares indices on downloading_pieces + struct has_index + { + explicit has_index(piece_index_t const i) : index(i) + { TORRENT_ASSERT(i >= piece_index_t(0)); } + bool operator()(downloading_piece const& p) const + { return p.index == index; } + piece_index_t const index; + }; + + int blocks_in_last_piece() const + { return m_blocks_in_last_piece; } + + std::pair distributed_copies() const; + + // return the array of block_info objects for a given downloading_piece. + // this array has blocks_per_piece elements in it + span blocks_for_piece(downloading_piece const& dp) const; + + private: + + // picks blocks only from downloading pieces + int add_blocks_downloading(downloading_piece const& dp + , typed_bitfield const& pieces + , std::vector& interesting_blocks + , std::vector& backup_blocks + , std::vector& backup_blocks2 + , int num_blocks, int prefer_contiguous_blocks + , torrent_peer* peer + , picker_options_t options) const; + + int block_size() const + { + TORRENT_ASSERT(m_piece_size > 0); + TORRENT_ASSERT(default_block_size > 0); + return (std::min)(m_piece_size, default_block_size); + } + int blocks_per_piece() const; + int piece_size(piece_index_t p) const; + + void account_have(piece_index_t); + void account_lost(piece_index_t); + + piece_extent_t extent_for(piece_index_t) const; + index_range extent_for(piece_extent_t) const; + + void record_downloading_piece(piece_index_t const p); + + std::int64_t num_pad_bytes() const { return m_num_pad_bytes; } + + span mutable_blocks_for_piece(downloading_piece const& dp); + + std::tuple requested_from( + piece_picker::downloading_piece const& p + , int num_blocks_in_piece, torrent_peer* peer) const; + + bool can_pick(piece_index_t piece, typed_bitfield const& bitmask) const; + bool is_piece_free(piece_index_t piece, typed_bitfield const& bitmask) const; + index_range + expand_piece(piece_index_t piece, int contiguous_blocks + , typed_bitfield const& have + , picker_options_t options) const; + + struct piece_pos + { + piece_pos() {} + piece_pos(int const peer_count_, int const index_) + : peer_count(static_cast(peer_count_)) + , download_state(static_cast(piece_pos::piece_open)) + , piece_priority(static_cast(default_priority)) + , index(index_) + { + TORRENT_ASSERT(peer_count_ >= 0); + TORRENT_ASSERT(peer_count_ < (std::numeric_limits::max)()); + TORRENT_ASSERT(index_ >= 0); + } + + // the piece is partially downloaded or requested + static constexpr download_queue_t piece_downloading{0}; + + // partial pieces where all blocks in the piece have been requested + static constexpr download_queue_t piece_full{1}; + // partial pieces where all blocks in the piece have been received + // and are either finished or writing + static constexpr download_queue_t piece_finished{2}; + // partial pieces whose priority is 0 + static constexpr download_queue_t piece_zero_prio{3}; + + // the states up to this point indicate the piece is being + // downloaded (or at least has a partially downloaded piece + // in one of the m_downloads buckets). + static constexpr download_queue_t num_download_categories{4}; + + // the piece is open to be picked + static constexpr download_queue_t piece_open{4}; + + // this is not a new download category/download list bucket. + // it still goes into the piece_downloading bucket. However, + // it indicates that this piece only has outstanding requests + // from reverse peers. This is to de-prioritize it somewhat + static constexpr download_queue_t piece_downloading_reverse{5}; + static constexpr download_queue_t piece_full_reverse{6}; + + // returns one of the valid download categories of state_t or + // piece_open if this piece is not being downloaded + download_queue_t download_queue() const + { + if (state() == piece_downloading_reverse) + return piece_downloading; + if (state() == piece_full_reverse) + return piece_full; + return state(); + } + + bool reverse() const + { + return state() == piece_downloading_reverse + || state() == piece_full_reverse; + } + + void unreverse() + { + if (state() == piece_downloading_reverse) + state(piece_downloading); + else if (state() == piece_full_reverse) + state(piece_full); + } + + void make_reverse() + { + if (state() == piece_downloading) + state(piece_downloading_reverse); + else if (state() == piece_full) + state(piece_full_reverse); + } + + // the number of peers that has this piece + // (availability) + std::uint32_t peer_count : 26; + + // one of the download_queue_t values. This indicates whether this piece + // is currently being downloaded or not, and what state it's in if + // it is. Specifically, as an optimization, pieces that have all blocks + // requested from them are separated out into separate lists to make + // lookups quicker. The main oddity is that whether a downloading piece + // has only been requested from peers that are reverse, that's + // recorded as piece_downloading_reverse, which really means the same + // as piece_downloading, it just saves space to also indicate that it + // has a bit lower priority. The reverse bit is only relevant if the + // state is piece_downloading. + std::uint32_t download_state : 3; + + // TODO: 2 having 8 priority levels is probably excessive. It should + // probably be changed to 3 levels + dont-download + + // is 0 if the piece is filtered (not to be downloaded) + // 1 is low priority + // 2 is low priority + // 3 is mid priority + // 4 is default priority + // 5 is mid priority + // 6 is high priority + // 7 is high priority + std::uint32_t piece_priority : 3; + + // index in to the piece_info vector + prio_index_t index; + +#ifdef TORRENT_DEBUG_REFCOUNTS + // all the peers that have this piece + std::set have_peers; +#endif + + // index is set to this to indicate that we have the + // piece. There is no entry for the piece in the + // buckets if this is the case. + static constexpr prio_index_t we_have_index{-1}; + + // the priority value that means the piece is filtered + static constexpr std::uint32_t filter_priority = 0; + + // the max number the peer count can hold + static constexpr std::uint32_t max_peer_count = 0xffff; + + bool have() const { return index == we_have_index; } + void set_have() { index = we_have_index; TORRENT_ASSERT(have()); } + void set_not_have() { index = prio_index_t(0); TORRENT_ASSERT(!have()); } + bool downloading() const { return state() != piece_open; } + + bool filtered() const { return piece_priority == filter_priority; } + + // this function returns the effective priority of the piece. It's + // actually the sort order of this piece compared to other pieces. A + // lower index means it will be picked before a piece with a higher + // index. + // The availability of the piece (the number of peers that have this + // piece) is fundamentally controlling the priority. It's multiplied + // by 3 to form 3 levels of priority for each availability. + // + // downloading pieces (not reverse) + // | open pieces (not downloading) + // | | downloading pieces (reverse peers) + // | | | + // +---+---+---+ + // | 0 | 1 | 2 | + // +---+---+---+ + // this '3' is called prio_factor + // + // the manually set priority takes precedence over the availability + // by multiplying availability by priority. + + int priority(piece_picker const* picker) const + { + // filtered pieces (prio = 0), pieces we have or pieces with + // availability = 0 should not be present in the piece list + // returning -1 indicates that they shouldn't. + if (filtered() || have() || peer_count + picker->m_seeds == 0 + || state() == piece_full + || state() == piece_finished) + return -1; + + TORRENT_ASSERT(piece_priority > 0); + + // this is to keep downloading pieces at higher priority than + // pieces that are not being downloaded, and to make reverse + // downloading pieces to be lower priority + int adjustment = -2; + if (reverse()) adjustment = -1; + else if (state() != piece_open) adjustment = -3; + + // the + 1 here is because peer_count count be 0, it m_seeds + // is > 0. We don't actually care about seeds (except for the + // first one) since the order of the pieces is unaffected. + int availability = int(peer_count) + 1; + TORRENT_ASSERT(availability > 0); + TORRENT_ASSERT(int(priority_levels - piece_priority) > 0); + + return availability * int(priority_levels - piece_priority) + * prio_factor + adjustment; + } + + bool operator!=(piece_pos const& p) const + { return index != p.index || peer_count != p.peer_count; } + + bool operator==(piece_pos const& p) const + { return index == p.index && peer_count == p.peer_count; } + + download_queue_t state() const { return download_queue_t(download_state); } + void state(download_queue_t q) { download_state = static_cast(q); } + }; + +#ifndef TORRENT_DEBUG_REFCOUNTS + static_assert(sizeof(piece_pos) == sizeof(char) * 8, "unexpected struct size"); +#endif + + bool partial_compare_rarest_first(downloading_piece const* lhs + , downloading_piece const* rhs) const; + + void break_one_seed(); + + void update_pieces() const; + + prio_index_t priority_begin(int prio) const; + prio_index_t priority_end(int prio) const; + + // fills in the range [start, end) of pieces in + // m_pieces that have priority 'prio' + std::pair priority_range(int prio) const; + + // adds the piece 'index' to m_pieces + void add(piece_index_t index); + // removes the piece with the given priority and the + // elem_index in the m_pieces vector + void remove(int priority, prio_index_t elem_index); + // updates the position of the piece with the given + // priority and the elem_index in the m_pieces vector + void update(int priority, prio_index_t elem_index); + // shuffles the given piece inside it's priority range + void shuffle(int priority, prio_index_t elem_index); + + std::vector::iterator add_download_piece(piece_index_t); + void erase_download_piece(std::vector::iterator i); + + std::vector::const_iterator find_dl_piece(download_queue_t, piece_index_t) const; + std::vector::iterator find_dl_piece(download_queue_t, piece_index_t); + + // returns an iterator to the downloading piece, whichever + // download list it may live in now + std::vector::iterator update_piece_state( + std::vector::iterator dp); + + private: + +#if TORRENT_USE_ASSERTS || TORRENT_USE_INVARIANT_CHECKS + index_range categories() const + { return {{}, piece_picker::piece_pos::num_download_categories}; } +#endif + + // the following vectors are mutable because they sometimes may + // be updated lazily, triggered by const functions + + // this maps indices to number of peers that has this piece and + // index into the m_piece_info vectors. + // piece_pos::we_have_index means that we have the piece, so it + // doesn't exist in the piece_info buckets + // pieces with the filtered flag set doesn't have entries in + // the m_piece_info buckets either + // TODO: should this be allocated lazily? + mutable aux::vector m_piece_map; + + // tracks the number of bytes in a specific piece that are part of a pad + // file. The padding is assumed to be at the end of the piece, and the + // blocks covered by the pad bytes are not picked by the piece picker + std::unordered_map m_pads_in_piece; + + // when the adjacent_piece affinity is enabled, this contains the most + // recent "extents" of adjacent pieces that have been requested from + // this is mutable because it's updated by functions to pick pieces, which + // are const. That's an efficient place to update it, since it's being + // traversed already. + mutable std::vector m_recent_extents; + + // the number of bytes of pad file set in this piece picker + std::int64_t m_num_pad_bytes = 0; + + // the number of pad blocks that we already have + std::int64_t m_have_pad_bytes = 0; + + // the number of pad blocks part of filtered pieces we don't have + std::int64_t m_filtered_pad_bytes = 0; + + // the number of pad blocks we have that are also filtered + std::int64_t m_have_filtered_pad_bytes = 0; + + // the number of seeds. These are not added to + // the availability counters of the pieces + int m_seeds = 0; + + // this vector contains all piece indices that are pickable + // sorted by priority. Pieces are in random random order + // among pieces with the same priority + mutable aux::vector m_pieces; + + // these are indices to the priority boundaries inside + // the m_pieces vector. priority 0 always start at + // 0, priority 1 starts at m_priority_boundaries[0] etc. + mutable aux::vector m_priority_boundaries; + + // each piece that's currently being downloaded has an entry in this list + // with block allocations. i.e. it says which parts of the piece that is + // being downloaded. This list is ordered by piece index to make lookups + // efficient there are as many buckets as there are piece states. See + // piece_pos::state_t. The only download state that does not have a + // corresponding downloading_piece vector is piece_open and + // piece_downloading_reverse (the latter uses the same as + // piece_downloading). + aux::array + , static_cast(piece_pos::num_download_categories) + , download_queue_t> m_downloads; + + // this holds the information of the blocks in partially downloaded + // pieces. the downloading_piece::info index point into this vector for + // its storage + aux::vector m_block_info; + + // these are block ranges in m_block_info that are free. The numbers + // in here, when multiplied by blocks_per_piece is the index to the + // first block in the range that's free to use by a new downloading_piece. + // this is a free-list. + std::vector m_free_block_infos; + + std::uint16_t m_blocks_in_last_piece = 0; + int m_piece_size = 0; + std::int64_t m_total_size = 0; + + // the number of filtered pieces that we don't already + // have. total_number_of_pieces - number_of_pieces_we_have + // - num_filtered is supposed to the number of pieces + // we still want to download + // TODO: it would be more intuitive to account "wanted" pieces + // instead of filtered + int m_num_filtered = 0; + + // the number of pieces we have that also are filtered + int m_num_have_filtered = 0; + + // we have all pieces in the range [0, m_cursor) + // m_cursor is the first piece we don't have + piece_index_t m_cursor{0}; + + // we have all pieces in the range [m_reverse_cursor, end) + // m_reverse_cursor is the first piece where we also have + // all the subsequent pieces + piece_index_t m_reverse_cursor{0}; + + // the number of pieces we have (i.e. passed hash check). + // This includes pieces that we have filtered but still have + int m_num_have = 0; + + // if this is set to true, it means update_pieces() + // has to be called before accessing m_pieces. + mutable bool m_dirty = false; + public: + + enum { max_pieces = (std::numeric_limits::max)() - 1 }; + + }; +} + +#endif // TORRENT_PIECE_PICKER_HPP_INCLUDED diff --git a/include/libtorrent/platform_util.hpp b/include/libtorrent/platform_util.hpp new file mode 100644 index 0000000..311008e --- /dev/null +++ b/include/libtorrent/platform_util.hpp @@ -0,0 +1,14 @@ +#ifndef TORRENT_PLATFORM_UTIL_HPP +#define TORRENT_PLATFORM_UTIL_HPP + +#include + +namespace libtorrent { + + int max_open_files(); + + void set_thread_name(char const* name); + +} + +#endif // TORRENT_PLATFORM_UTIL_HPP diff --git a/include/libtorrent/portmap.hpp b/include/libtorrent/portmap.hpp new file mode 100644 index 0000000..65a5c78 --- /dev/null +++ b/include/libtorrent/portmap.hpp @@ -0,0 +1,58 @@ +/* + +Copyright (c) 2017-2019, Arvid Norberg +Copyright (c) 2018, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PORTMAP_HPP_INCLUDED +#define TORRENT_PORTMAP_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/units.hpp" + +namespace libtorrent { + + enum class portmap_transport : std::uint8_t + { + // natpmp can be NAT-PMP or PCP + natpmp, upnp + }; + + enum class portmap_protocol : std::uint8_t + { + none, tcp, udp + }; + + // this type represents an index referring to a port mapping + using port_mapping_t = aux::strong_typedef; + +} + +#endif //TORRENT_PORTMAP_HPP_INCLUDED diff --git a/include/libtorrent/posix_disk_io.hpp b/include/libtorrent/posix_disk_io.hpp new file mode 100644 index 0000000..487ac41 --- /dev/null +++ b/include/libtorrent/posix_disk_io.hpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2016-2017, 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_POSIX_DISK_IO +#define TORRENT_POSIX_DISK_IO + +#include "libtorrent/config.hpp" +#include "libtorrent/io_context.hpp" + +#include + +namespace libtorrent { + + struct counters; + struct disk_interface; + struct settings_interface; + + // this is a simple posix disk I/O back-end, used for systems that don't + // have a 64 bit virtual address space or don't support memory mapped files. + // It's implemented using portable C file functions and is single-threaded. + TORRENT_EXPORT std::unique_ptr posix_disk_io_constructor( + io_context& ios, settings_interface const&, counters& cnt); +} + +#endif + diff --git a/include/libtorrent/proxy_base.hpp b/include/libtorrent/proxy_base.hpp new file mode 100644 index 0000000..0563474 --- /dev/null +++ b/include/libtorrent/proxy_base.hpp @@ -0,0 +1,377 @@ +/* + +Copyright (c) 2007-2011, 2013-2015, 2017-2020, Arvid Norberg +Copyright (c) 2016, 2019, Alden Torres +Copyright (c) 2017, Jan Berkel +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PROXY_BASE_HPP_INCLUDED +#define TORRENT_PROXY_BASE_HPP_INCLUDED + +#include "libtorrent/io.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" + +namespace libtorrent { + +struct proxy_base +{ + using next_layer_type = tcp::socket; + using lowest_layer_type = tcp::socket::lowest_layer_type; + using endpoint_type = tcp::socket::endpoint_type; + using protocol_type = tcp::socket::protocol_type; + + explicit proxy_base(io_context& io_context); + ~proxy_base(); + proxy_base(proxy_base&&) noexcept = default; + proxy_base& operator=(proxy_base&&) = default; + proxy_base(proxy_base const&) = delete; + proxy_base& operator=(proxy_base const&) = delete; + + void set_proxy(std::string hostname, int port) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_hostname = std::move(hostname); + m_port = port; + } + + using executor_type = tcp::socket::executor_type; + executor_type get_executor() { return m_sock.get_executor(); } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler handler) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.async_read_some(buffers, std::move(handler)); + } + + template + std::size_t read_some(Mutable_Buffers const& buffers, error_code& ec) + { + TORRENT_ASSERT(m_magic == 0x1337); + return m_sock.read_some(buffers, ec); + } + + template + std::size_t write_some(Const_Buffers const& buffers, error_code& ec) + { + TORRENT_ASSERT(m_magic == 0x1337); + return m_sock.write_some(buffers, ec); + } + + std::size_t available(error_code& ec) const + { return m_sock.available(ec); } + +#ifndef BOOST_NO_EXCEPTIONS + std::size_t available() const + { return m_sock.available(); } + + template + std::size_t read_some(Mutable_Buffers const& buffers) + { + TORRENT_ASSERT(m_magic == 0x1337); + return m_sock.read_some(buffers); + } + + template + std::size_t write_some(Const_Buffers const& buffers) + { + TORRENT_ASSERT(m_magic == 0x1337); + return m_sock.write_some(buffers); + } + + template + void io_control(IO_Control_Command& ioc) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.io_control(ioc); + } +#endif + + template + void io_control(IO_Control_Command& ioc, error_code& ec) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.io_control(ioc, ec); + } + + template + void async_write_some(Const_Buffers const& buffers, Handler handler) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.async_write_some(buffers, std::move(handler)); + } + +#if BOOST_VERSION >= 106600 && !defined TORRENT_BUILD_SIMULATOR + // Compatibility with the async_wait method introduced in boost 1.66 + + static constexpr auto wait_read = tcp::socket::wait_read; + static constexpr auto wait_write = tcp::socket::wait_write; + static constexpr auto wait_error = tcp::socket::wait_error; + + template + void async_wait(tcp::socket::wait_type type, Handler handler) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.async_wait(type, std::move(handler)); + } +#endif + +#ifndef BOOST_NO_EXCEPTIONS + void non_blocking(bool b) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.non_blocking(b); + } +#endif + + void non_blocking(bool b, error_code& ec) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.non_blocking(b, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void set_option(SettableSocketOption const& opt) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.set_option(opt); + } +#endif + + template + void set_option(SettableSocketOption const& opt, error_code& ec) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.set_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void get_option(GettableSocketOption& opt) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.get_option(opt); + } +#endif + + template + void get_option(GettableSocketOption& opt, error_code& ec) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.get_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void bind(endpoint_type const& /* endpoint */) + { + TORRENT_ASSERT(m_magic == 0x1337); +// m_sock.bind(endpoint); + } +#endif + + void cancel() + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.cancel(); + } + + void cancel(error_code& ec) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_sock.cancel(ec); + } + + void bind(endpoint_type const& /* endpoint */, error_code& /* ec */) + { + TORRENT_ASSERT(m_magic == 0x1337); + // the reason why we ignore binds here is because we don't + // (necessarily) yet know what address family the proxy + // will resolve to, and binding to the wrong one would + // break our connection attempt later. The caller here + // doesn't necessarily know that we're proxying, so this + // bind address is based on the final endpoint, not the + // proxy. + // TODO: it would be nice to remember the bind port and bind once we know where the proxy is +// m_sock.bind(endpoint, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void open(protocol_type const&) + { + TORRENT_ASSERT(m_magic == 0x1337); +// m_sock.open(p); + } +#endif + + void open(protocol_type const&, error_code&) + { + TORRENT_ASSERT(m_magic == 0x1337); + // we need to ignore this for the same reason as stated + // for ignoring bind() +// m_sock.open(p, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void close() + { + TORRENT_ASSERT(m_magic == 0x1337); + m_remote_endpoint = endpoint_type(); + m_sock.close(); + m_resolver.cancel(); + } +#endif + + void close(error_code& ec) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_remote_endpoint = endpoint_type(); + m_sock.close(ec); + m_resolver.cancel(); + } + +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type remote_endpoint() const + { + TORRENT_ASSERT(m_magic == 0x1337); + return m_remote_endpoint; + } +#endif + + endpoint_type remote_endpoint(error_code& ec) const + { + TORRENT_ASSERT(m_magic == 0x1337); + if (!m_sock.is_open()) ec = boost::asio::error::not_connected; + return m_remote_endpoint; + } + +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type local_endpoint() const + { + TORRENT_ASSERT(m_magic == 0x1337); + return m_sock.local_endpoint(); + } +#endif + + endpoint_type local_endpoint(error_code& ec) const + { + TORRENT_ASSERT(m_magic == 0x1337); + return m_sock.local_endpoint(ec); + } + + lowest_layer_type& lowest_layer() + { + return m_sock.lowest_layer(); + } + + next_layer_type& next_layer() + { + return m_sock; + } + + bool is_open() const { return m_sock.is_open(); } + +protected: + + // The handler must be taken as lvalue reference here since we may not call + // it. But if we do, we want the call operator to own the function object. + template + bool handle_error(error_code e, Handler&& h) + { + TORRENT_ASSERT(m_magic == 0x1337); + if (!e) return false; + std::forward(h)(e); + error_code ec; + close(ec); + return true; + } + + aux::noexcept_movable m_sock; + std::string m_hostname; // proxy host + int m_port = 0; // proxy port + + aux::noexcept_movable m_remote_endpoint; + + // TODO: 2 use the resolver interface that has a built-in cache + aux::noexcept_move_only m_resolver; + +#if TORRENT_USE_ASSERTS + int m_magic = 0x1337; +#endif +}; + +template +struct wrap_allocator_t +{ + wrap_allocator_t(Handler h, UnderlyingHandler uh) + : m_handler(std::move(h)) + , m_underlying_handler(std::move(uh)) + {} + + wrap_allocator_t(wrap_allocator_t const&) = default; + wrap_allocator_t(wrap_allocator_t&&) = default; + + template + void operator()(A&&... a) + { + m_handler(std::forward(a)..., std::move(m_underlying_handler)); + } + + using allocator_type = typename boost::asio::associated_allocator::type; + using executor_type = typename boost::asio::associated_executor::type; + + allocator_type get_allocator() const noexcept + { return boost::asio::get_associated_allocator(m_underlying_handler); } + + executor_type get_executor() const noexcept + { + return boost::asio::get_associated_executor(m_underlying_handler); + } + +private: + Handler m_handler; + UnderlyingHandler m_underlying_handler; +}; + +template +wrap_allocator_t wrap_allocator(Handler h, UnderlyingHandler u) +{ + return wrap_allocator_t{std::move(h), std::move(u)}; +} + + +} + +#endif diff --git a/include/libtorrent/puff.hpp b/include/libtorrent/puff.hpp new file mode 100644 index 0000000..9aa5911 --- /dev/null +++ b/include/libtorrent/puff.hpp @@ -0,0 +1,35 @@ +/* puff.h + Copyright (C) 2002, 2003 Mark Adler, all rights reserved + version 1.7, 3 Mar 2002 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Mark Adler madler@alumni.caltech.edu + */ + +#ifndef PUFF_HPP_INCLUDED +#define PUFF_HPP_INCLUDED + +/* + * See puff.c for purpose and usage. + */ +int puff(unsigned char *dest, /* pointer to destination pointer */ + unsigned long *destlen, /* amount of output space */ + const unsigned char *source, /* pointer to source data pointer */ + unsigned long *sourcelen); /* amount of input available */ + +#endif // PUFF_HPP_INCLUDED diff --git a/include/libtorrent/random.hpp b/include/libtorrent/random.hpp new file mode 100644 index 0000000..bee257b --- /dev/null +++ b/include/libtorrent/random.hpp @@ -0,0 +1,85 @@ +/* + +Copyright (c) 2011-2013, 2016-2021, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_RANDOM_HPP_INCLUDED +#define TORRENT_RANDOM_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/span.hpp" + +#include +#include +#include + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT std::uint32_t random(std::uint32_t m); + +namespace aux { + + TORRENT_EXTRA_EXPORT std::mt19937& random_engine(); + + template + void random_shuffle(Range& range) + { +#ifdef TORRENT_BUILD_SIMULATOR + // in simulations, we want all shuffles to be deterministic (as long as + // the random engine is deterministic + if (range.size() == 0) return; + for (auto i = range.size() - 1; i > 0; --i) { + auto const other = random(std::uint32_t(i)); + if (i == other) continue; + using std::swap; + swap(range.data()[i], range.data()[other]); + } +#else + std::shuffle(range.data(), range.data() + range.size(), random_engine()); +#endif + } + + // Fills the buffer with pseudo random bytes. + // + // This functions perform differently under different setups + // For Windows and all platforms when compiled with libcrypto, it + // generates cryptographically random bytes. + // If the above conditions are not true, then a standard + // fill of bytes is used. + TORRENT_EXTRA_EXPORT void random_bytes(span buffer); + + // Fills the buffer with random bytes from a strong entropy source. This can + // be used to generate secrets. + TORRENT_EXTRA_EXPORT void crypto_random_bytes(span buffer); +} +} + +#endif // TORRENT_RANDOM_HPP_INCLUDED diff --git a/include/libtorrent/read_resume_data.hpp b/include/libtorrent/read_resume_data.hpp new file mode 100644 index 0000000..db57f1f --- /dev/null +++ b/include/libtorrent/read_resume_data.hpp @@ -0,0 +1,71 @@ +/* + +Copyright (c) 2015-2018, 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_READ_RESUME_DATA_HPP_INCLUDE +#define TORRENT_READ_RESUME_DATA_HPP_INCLUDE + +#include "libtorrent/fwd.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/torrent_info.hpp" // for load_torrent_limits + +namespace libtorrent { + + // these functions are used to parse resume data and populate the appropriate + // fields in an add_torrent_params object. This object can then be used to add + // the actual torrent_info object to and pass to session::add_torrent() or + // session::async_add_torrent(). + // + // If the client wants to override any field that was loaded from the resume + // data, e.g. save_path, those fields must be changed after loading resume + // data but before adding the torrent. + // + // The ``piece_limit`` parameter determines the largest number of pieces + // allowed in the torrent that may be loaded as part of the resume data, if + // it contains an ``info`` field. The overloads that take a flat buffer are + // instead configured with limits on torrent sizes via load_torrent limits. + // + // In order to support large torrents, it may also be necessary to raise the + // settings_pack::max_piece_count setting and pass a higher limit to calls + // to torrent_info::parse_info_section(). + TORRENT_EXPORT add_torrent_params read_resume_data(bdecode_node const& rd + , error_code& ec, int piece_limit = 0x200000); + TORRENT_EXPORT add_torrent_params read_resume_data(span buffer + , error_code& ec, load_torrent_limits const& cfg = {}); + TORRENT_EXPORT add_torrent_params read_resume_data(bdecode_node const& rd + , int piece_limit = 0x200000); + TORRENT_EXPORT add_torrent_params read_resume_data(span buffer + , load_torrent_limits const& cfg = {}); +} + +#endif diff --git a/include/libtorrent/request_blocks.hpp b/include/libtorrent/request_blocks.hpp new file mode 100644 index 0000000..fb01a8e --- /dev/null +++ b/include/libtorrent/request_blocks.hpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2010, 2014-2015, 2017, 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_REQUEST_BLOCKS_HPP_INCLUDED +#define TORRENT_REQUEST_BLOCKS_HPP_INCLUDED + +#include "libtorrent/peer_info.hpp" + +namespace libtorrent { + + struct torrent; + struct peer_connection; + + // returns false if the piece picker was not invoked, because + // of an early exit condition. In this case, the stats counter + // shouldn't be incremented, since it won't use any significant + // amount of CPU + bool request_a_block(torrent& t, peer_connection& c); + + // returns the rank of a peer's source. We have an affinity + // to connecting to peers with higher rank. This is to avoid + // problems when our peer list is diluted by stale peers from + // the resume data for instance + int source_rank(peer_source_flags_t source_bitmask); +} + +#endif diff --git a/include/libtorrent/resolve_links.hpp b/include/libtorrent/resolve_links.hpp new file mode 100644 index 0000000..a11f003 --- /dev/null +++ b/include/libtorrent/resolve_links.hpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2015-2020, 2022, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_RESOLVE_LINKS_HPP +#define TORRENT_RESOLVE_LINKS_HPP + +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/sha1_hash.hpp" // for sha256_hash + +namespace libtorrent { + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + // this class is used for mutable torrents, to discover identical files + // in other torrents. + struct TORRENT_EXTRA_EXPORT resolve_links + { + struct TORRENT_EXTRA_EXPORT link_t + { + std::shared_ptr ti; + std::string save_path; + file_index_t file_idx; + }; + + explicit resolve_links(std::shared_ptr ti); + + // check to see if any files are shared with this torrent + void match(std::shared_ptr const& ti + , std::string const& save_path); + + aux::vector const& get_links() const + { return m_links; } + + private: + + void match_v1(std::shared_ptr const& ti + , std::string const& save_path); + void match_v2(std::shared_ptr const& ti + , std::string const& save_path); + + // this is the torrent we're trying to find files for. + std::shared_ptr m_torrent_file; + + // each file in m_torrent_file has an entry in this vector. Any file + // that also exists somewhere else, is filled in with the corresponding + // torrent_info object and file index + aux::vector m_links; + + // maps file size to file index, in m_torrent_file + std::unordered_multimap m_file_sizes; + + // maps file root hash to file index, in m_torrent_file + std::unordered_multimap m_file_roots; + }; +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS + +} + +#endif diff --git a/include/libtorrent/session.hpp b/include/libtorrent/session.hpp new file mode 100644 index 0000000..38ca9a6 --- /dev/null +++ b/include/libtorrent/session.hpp @@ -0,0 +1,299 @@ +/* + +Copyright (c) 2003-2004, 2006-2007, 2009-2010, 2013-2020, Arvid Norberg +Copyright (c) 2015, Steven Siloti +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_HPP_INCLUDED +#define TORRENT_SESSION_HPP_INCLUDED + +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/session_handle.hpp" +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/session_params.hpp" +#include "libtorrent/session_types.hpp" // for session_flags_t + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/fingerprint.hpp" +#include // for snprintf +#endif + +namespace libtorrent { + +TORRENT_VERSION_NAMESPACE_3 + struct plugin; + struct session_params; +TORRENT_VERSION_NAMESPACE_3_END + + // The default values of the session settings are set for a regular + // bittorrent client running on a desktop system. There are functions that + // can set the session settings to pre set settings for other environments. + // These can be used for the basis, and should be tweaked to fit your needs + // better. + // + // ``min_memory_usage`` returns settings that will use the minimal amount of + // RAM, at the potential expense of upload and download performance. It + // adjusts the socket buffer sizes, disables the disk cache, lowers the send + // buffer watermarks so that each connection only has at most one block in + // use at any one time. It lowers the outstanding blocks send to the disk + // I/O thread so that connections only have one block waiting to be flushed + // to disk at any given time. It lowers the max number of peers in the peer + // list for torrents. It performs multiple smaller reads when it hashes + // pieces, instead of reading it all into memory before hashing. + // + // This configuration is intended to be the starting point for embedded + // devices. It will significantly reduce memory usage. + // + // ``high_performance_seed`` returns settings optimized for a seed box, + // serving many peers and that doesn't do any downloading. It has a 128 MB + // disk cache and has a limit of 400 files in its file pool. It support fast + // upload rates by allowing large send buffers. + TORRENT_EXPORT settings_pack min_memory_usage(); + TORRENT_EXPORT settings_pack high_performance_seed(); +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + inline void min_memory_usage(settings_pack& set) + { set = min_memory_usage(); } + TORRENT_DEPRECATED + inline void high_performance_seed(settings_pack& set) + { set = high_performance_seed(); } +#endif + +namespace aux { + + struct session_impl; +} + + struct disk_interface; + struct counters; + struct settings_interface; + + // the constructor function for the default storage. On systems that support + // memory mapped files (and a 64 bit address space) the memory mapped storage + // will be constructed, otherwise the portable posix storage. + TORRENT_EXPORT std::unique_ptr default_disk_io_constructor( + io_context& ios, settings_interface const&, counters& cnt); + + // this is a holder for the internal session implementation object. Once the + // session destruction is explicitly initiated, this holder is used to + // synchronize the completion of the shutdown. The lifetime of this object + // may outlive session, causing the session destructor to not block. The + // session_proxy destructor will block however, until the underlying session + // is done shutting down. + struct TORRENT_EXPORT session_proxy + { + friend struct session; + // default constructor, does not refer to any session + // implementation object. + session_proxy(); + ~session_proxy(); + session_proxy(session_proxy const&); + session_proxy& operator=(session_proxy const&) &; + session_proxy(session_proxy&&) noexcept; + session_proxy& operator=(session_proxy&&) & noexcept; + private: + session_proxy( + std::shared_ptr ios + , std::shared_ptr t + , std::shared_ptr impl); + + std::shared_ptr m_io_service; + std::shared_ptr m_thread; + std::shared_ptr m_impl; + }; + + // The session holds all state that spans multiple torrents. Among other + // things it runs the network loop and manages all torrents. Once it's + // created, the session object will spawn the main thread that will do all + // the work. The main thread will be idle as long it doesn't have any + // torrents to participate in. + // + // You have some control over session configuration through the + // ``session_handle::apply_settings()`` member function. To change one or more + // configuration options, create a settings_pack. object and fill it with + // the settings to be set and pass it in to ``session::apply_settings()``. + // + // see apply_settings(). + struct TORRENT_EXPORT session : session_handle + { + // Constructs the session objects which acts as the container of torrents. + // In order to avoid a race condition between starting the session and + // configuring it, you can pass in a session_params object. Its settings + // will take effect before the session starts up. + // + // The overloads taking ``flags`` can be used to start a session in + // paused mode (by passing in ``session::paused``). Note that + // ``add_default_plugins`` do not have an affect on constructors that + // take a session_params object. It already contains the plugins to use. + explicit session(session_params const& params); + explicit session(session_params&& params); + session(session_params const& params, session_flags_t flags); + session(session_params&& params, session_flags_t flags); + session(); + + // Overload of the constructor that takes an external io_context to run + // the session object on. This is primarily useful for tests that may want + // to run multiple sessions on a single io_context, or low resource + // systems where additional threads are expensive and sharing an + // io_context with other events is fine. + // + // .. warning:: + // The session object does not cleanly terminate with an external + // ``io_context``. The ``io_context::run()`` call *must* have returned + // before it's safe to destruct the session. Which means you *MUST* + // call session::abort() and save the session_proxy first, then + // destruct the session object, then sync with the io_context, then + // destruct the session_proxy object. + session(session_params&& params, io_context& ios); + session(session_params const& params, io_context& ios); + session(session_params&& params, io_context& ios, session_flags_t); + session(session_params const& params, io_context& ios, session_flags_t); + + // hidden + session(session&&); + session& operator=(session&&) &; + + // hidden + session(session const&) = delete; + session& operator=(session const&) = delete; + +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // Constructs the session objects which acts as the container of torrents. + // It provides configuration options across torrents (such as rate limits, + // disk cache, ip filter etc.). In order to avoid a race condition between + // starting the session and configuring it, you can pass in a + // settings_pack object. Its settings will take effect before the session + // starts up. + // + // The ``flags`` parameter can be used to start default features (UPnP & + // NAT-PMP) and default plugins (ut_metadata, ut_pex and smart_ban). The + // default is to start those features. If you do not want them to start, + // pass 0 as the flags parameter. + TORRENT_DEPRECATED + session(settings_pack&& pack, session_flags_t const flags); + TORRENT_DEPRECATED + session(settings_pack const& pack, session_flags_t const flags); + explicit session(settings_pack&& pack) : session(std::move(pack), add_default_plugins) {} + explicit session(settings_pack const& pack) : session(pack, add_default_plugins) {} + + // overload of the constructor that takes an external io_context to run + // the session object on. This is primarily useful for tests that may want + // to run multiple sessions on a single io_context, or low resource + // systems where additional threads are expensive and sharing an + // io_context with other events is fine. + // + // .. warning:: + // The session object does not cleanly terminate with an external + // ``io_context``. The ``io_context::run()`` call _must_ have returned + // before it's safe to destruct the session. Which means you *MUST* + // call session::abort() and save the session_proxy first, then + // destruct the session object, then sync with the io_context, then + // destruct the session_proxy object. + TORRENT_DEPRECATED + session(settings_pack&&, io_context&, session_flags_t); + TORRENT_DEPRECATED + session(settings_pack const&, io_context&, session_flags_t); + session(settings_pack&& pack, io_context& ios) : session(std::move(pack), ios, add_default_plugins) {} + session(settings_pack const& pack, io_context& ios) : session(pack, ios, add_default_plugins) {} + +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // TORRENT_ABI_VERSION + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + TORRENT_DEPRECATED + session(fingerprint const& print + , session_flags_t const flags = start_default_features | add_default_plugins + , alert_category_t const alert_mask = alert_category::error); + + TORRENT_DEPRECATED + session(fingerprint const& print + , std::pair listen_port_range + , char const* listen_interface = "0.0.0.0" + , session_flags_t const flags = start_default_features | add_default_plugins + , alert_category_t const alert_mask = alert_category::error); + +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // TORRENT_ABI_VERSION + + // The destructor of session will notify all trackers that our torrents + // have been shut down. If some trackers are down, they will time out. + // All this before the destructor of session returns. So, it's advised + // that any kind of interface (such as windows) are closed before + // destructing the session object. Because it can take a few second for + // it to finish. The timeout can be set with apply_settings(). + ~session(); + + // In case you want to destruct the session asynchronously, you can + // request a session destruction proxy. If you don't do this, the + // destructor of the session object will block while the trackers are + // contacted. If you keep one ``session_proxy`` to the session when + // destructing it, the destructor will not block, but start to close down + // the session, the destructor of the proxy will then synchronize the + // threads. So, the destruction of the session is performed from the + // ``session`` destructor call until the ``session_proxy`` destructor + // call. The ``session_proxy`` does not have any operations on it (since + // the session is being closed down, no operations are allowed on it). + // The only valid operation is calling the destructor:: + // + // struct session_proxy {}; + session_proxy abort(); + + private: + + void start(session_flags_t, session_params&& params, io_context* ios); + +#if TORRENT_ABI_VERSION <= 2 + void start(session_flags_t flags, settings_pack&& sp, io_context* ios); +#endif + + void start(session_params const& params, io_context* ios) = delete; +#if TORRENT_ABI_VERSION <= 2 + void start(session_flags_t flags, settings_pack const& sp, io_context* ios) = delete; +#endif + + // data shared between the main thread + // and the working thread + std::shared_ptr m_io_service; + std::shared_ptr m_thread; + std::shared_ptr m_impl; + }; + +} + +#endif // TORRENT_SESSION_HPP_INCLUDED diff --git a/include/libtorrent/session_handle.hpp b/include/libtorrent/session_handle.hpp new file mode 100644 index 0000000..2dafaf5 --- /dev/null +++ b/include/libtorrent/session_handle.hpp @@ -0,0 +1,1137 @@ +/* + +Copyright (c) 2014-2018, Steven Siloti +Copyright (c) 2015-2018, Alden Torres +Copyright (c) 2015-2022, Arvid Norberg +Copyright (c) 2020, AllSeeingEyeTolledEweSew +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_HANDLE_HPP_INCLUDED +#define TORRENT_SESSION_HANDLE_HPP_INCLUDED + +#include // for shared_ptr + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/alert.hpp" // alert_category::error +#include "libtorrent/peer_class.hpp" +#include "libtorrent/peer_class_type_filter.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/session_types.hpp" +#include "libtorrent/portmap.hpp" // for portmap_protocol + +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/kademlia/announce_flags.hpp" + +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/kademlia/dht_settings.hpp" +#endif + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/session_settings.hpp" +#include +#endif + +#include "libtorrent/extensions.hpp" +#include "libtorrent/session_types.hpp" // for session_flags_t + +namespace libtorrent { + + struct torrent; + +#if TORRENT_ABI_VERSION == 1 + struct session_status; + using user_load_function_t = std::function&, error_code&)>; +#endif + + // this class provides a non-owning handle to a session and a subset of the + // interface of the session class. If the underlying session is destructed + // any handle to it will no longer be valid. is_valid() will return false and + // any operation on it will throw a system_error exception, with error code + // invalid_session_handle. + struct TORRENT_EXPORT session_handle + { + friend struct session; + friend struct aux::session_impl; + + // hidden + session_handle() = default; + session_handle(session_handle const& t) = default; + session_handle(session_handle&& t) noexcept = default; + session_handle& operator=(session_handle const&) & = default; + session_handle& operator=(session_handle&&) & noexcept = default; + +#if TORRENT_ABI_VERSION == 1 + using save_state_flags_t = libtorrent::save_state_flags_t; + using session_flags_t = libtorrent::session_flags_t; +#endif + + // returns true if this handle refers to a valid session object. If the + // session has been destroyed, all session_handle objects will expire and + // not be valid. + bool is_valid() const { return !m_impl.expired(); } + + // saves settings (i.e. the settings_pack) + static constexpr save_state_flags_t save_settings = 0_bit; + +#if TORRENT_ABI_VERSION <= 2 + // saves dht_settings. All DHT settings are now part of the main + // settings_pack, and saved by setting the save_settings flag + TORRENT_DEPRECATED static constexpr save_state_flags_t save_dht_settings = 1_bit; +#endif + + // saves dht state such as nodes and node-id, possibly accelerating + // joining the DHT if provided at next session startup. + static constexpr save_state_flags_t save_dht_state = 2_bit; + +#if TORRENT_ABI_VERSION == 1 + // save pe_settings + TORRENT_DEPRECATED static constexpr save_state_flags_t save_encryption_settings = 3_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_as_map = 4_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_proxy = 5_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_i2p_proxy = 6_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_dht_proxy = 7_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_peer_proxy = 8_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_web_proxy = 9_bit; + TORRENT_DEPRECATED static constexpr save_state_flags_t save_tracker_proxy = 10_bit; +#endif + + // load or save state from plugins + static constexpr save_state_flags_t save_extension_state = 11_bit; + + // load or save the IP filter set on the session + static constexpr save_state_flags_t save_ip_filter = 12_bit; + +#if TORRENT_ABI_VERSION <= 2 + // deprecated in 2.0 + // instead of these functions, use session_state() below, and restore + // state using the session_params on session construction. + + // loads and saves all session settings, including dht_settings, + // encryption settings and proxy settings. ``save_state`` writes all keys + // to the ``entry`` that's passed in, which needs to either not be + // initialized, or initialized as a dictionary. + // + // ``load_state`` expects a bdecode_node which can be built from a bencoded + // buffer with bdecode(). + // + // The ``flags`` argument is used to filter which parts of the session + // state to save or load. By default, all state is saved/restored (except + // for the individual torrents). + // + // When saving settings, there are two fields that are *not* loaded. + // ``peer_fingerprint`` and ``user_agent``. Those are left as configured + // by the ``session_settings`` passed to the session constructor or + // subsequently set via apply_settings(). + TORRENT_DEPRECATED + void save_state(entry& e, save_state_flags_t flags = save_state_flags_t::all()) const; + TORRENT_DEPRECATED + void load_state(bdecode_node const& e, save_state_flags_t flags = save_state_flags_t::all()); +#endif + + // returns the current session state. This can be passed to + // write_session_params() to save the state to disk and restored using + // read_session_params() when constructing a new session. The kind of + // state that's included is all settings, the DHT routing table, possibly + // plugin-specific state. + // the flags parameter can be used to only save certain parts of the + // session state + session_params session_state(save_state_flags_t flags = save_state_flags_t::all()) const; + + // .. note:: + // these calls are potentially expensive and won't scale well with + // lots of torrents. If you're concerned about performance, consider + // using ``post_torrent_updates()`` instead. + // + // ``get_torrent_status`` returns a vector of the torrent_status for + // every torrent which satisfies ``pred``, which is a predicate function + // which determines if a torrent should be included in the returned set + // or not. Returning true means it should be included and false means + // excluded. The ``flags`` argument is the same as to + // torrent_handle::status(). Since ``pred`` is guaranteed to be + // called for every torrent, it may be used to count the number of + // torrents of different categories as well. + // + // ``refresh_torrent_status`` takes a vector of torrent_status structs + // (for instance the same vector that was returned by + // get_torrent_status() ) and refreshes the status based on the + // ``handle`` member. It is possible to use this function by first + // setting up a vector of default constructed ``torrent_status`` objects, + // only initializing the ``handle`` member, in order to request the + // torrent status for multiple torrents in a single call. This can save a + // significant amount of time if you have a lot of torrents. + // + // Any torrent_status object whose ``handle`` member is not referring to + // a valid torrent are ignored. + // + // The intended use of these functions is to start off by calling + // ``get_torrent_status()`` to get a list of all torrents that match your + // criteria. Then call ``refresh_torrent_status()`` on that list. This + // will only refresh the status for the torrents in your list, and thus + // ignore all other torrents you might be running. This may save a + // significant amount of time, especially if the number of torrents you're + // interested in is small. In order to keep your list of interested + // torrents up to date, you can either call ``get_torrent_status()`` from + // time to time, to include torrents you might have become interested in + // since the last time. In order to stop refreshing a certain torrent, + // simply remove it from the list. + std::vector get_torrent_status( + std::function const& pred + , status_flags_t flags = {}) const; + void refresh_torrent_status(std::vector* ret + , status_flags_t flags = {}) const; + + // This functions instructs the session to post the state_update_alert, + // containing the status of all torrents whose state changed since the + // last time this function was called. + // + // Only torrents who has the state subscription flag set will be + // included. This flag is on by default. See add_torrent_params. + // the ``flags`` argument is the same as for torrent_handle::status(). + // see status_flags_t in torrent_handle. + void post_torrent_updates(status_flags_t flags = status_flags_t::all()); + + // This function will post a session_stats_alert object, containing a + // snapshot of the performance counters from the internals of libtorrent. + // To interpret these counters, query the session via + // session_stats_metrics(). + // + // For more information, see the session-statistics_ section. + void post_session_stats(); + + // This will cause a dht_stats_alert to be posted. + void post_dht_stats(); + + // internal + io_context& get_context(); + + // set the DHT state for the session. This will be taken into account the + // next time the DHT is started, as if it had been passed in via the + // session_params on startup. + void set_dht_state(dht::dht_state const& st); + void set_dht_state(dht::dht_state&& st); + + // ``find_torrent()`` looks for a torrent with the given info-hash. In + // case there is such a torrent in the session, a torrent_handle to that + // torrent is returned. In case the torrent cannot be found, an invalid + // torrent_handle is returned. + // + // See ``torrent_handle::is_valid()`` to know if the torrent was found or + // not. + // + // ``get_torrents()`` returns a vector of torrent_handles to all the + // torrents currently in the session. + torrent_handle find_torrent(sha1_hash const& info_hash) const; + std::vector get_torrents() const; + + // You add torrents through the add_torrent() function where you give an + // object with all the parameters. The add_torrent() overloads will block + // until the torrent has been added (or failed to be added) and returns + // an error code and a torrent_handle. In order to add torrents more + // efficiently, consider using async_add_torrent() which returns + // immediately, without waiting for the torrent to add. Notification of + // the torrent being added is sent as add_torrent_alert. + // + // The ``save_path`` field in add_torrent_params must be set to a valid + // path where the files for the torrent will be saved. Even when using a + // custom storage, this needs to be set to something. If the save_path + // is empty, the call to add_torrent() will throw a system_error + // exception. + // + // The overload that does not take an error_code throws an exception on + // error and is not available when building without exception support. + // The torrent_handle returned by add_torrent() can be used to retrieve + // information about the torrent's progress, its peers etc. It is also + // used to abort a torrent. + // + // If the torrent you are trying to add already exists in the session (is + // either queued for checking, being checked or downloading) + // ``add_torrent()`` will throw system_error which derives from + // ``std::exception`` unless duplicate_is_error is set to false. In that + // case, add_torrent() will return the handle to the existing torrent. + // + // The add_torrent_params class has a flags field. It can be used to + // control what state the new torrent will be added in. Common flags to + // want to control are torrent_flags::paused and + // torrent_flags::auto_managed. In order to add a magnet link that will + // just download the metadata, but no payload, set the + // torrent_flags::upload_mode flag. + // + // Special consideration has to be taken when adding hybrid torrents + // (i.e. torrents that are BitTorrent v2 torrents that are backwards + // compatible with v1). For more details, see BitTorrent-v2-torrents_. +#ifndef BOOST_NO_EXCEPTIONS + torrent_handle add_torrent(add_torrent_params&& params); + torrent_handle add_torrent(add_torrent_params const& params); +#endif + torrent_handle add_torrent(add_torrent_params&& params, error_code& ec); + torrent_handle add_torrent(add_torrent_params const& params, error_code& ec); + void async_add_torrent(add_torrent_params&& params); + void async_add_torrent(add_torrent_params const& params); + +#ifndef BOOST_NO_EXCEPTIONS +#if TORRENT_ABI_VERSION == 1 + // deprecated in 0.14 + TORRENT_DEPRECATED + torrent_handle add_torrent( + torrent_info const& ti + , std::string const& save_path + , entry const& resume_data = entry() + , storage_mode_t storage_mode = storage_mode_sparse + , bool paused = false); + + // deprecated in 0.14 + TORRENT_DEPRECATED + torrent_handle add_torrent( + char const* tracker_url + , sha1_hash const& info_hash + , char const* name + , std::string const& save_path + , entry const& resume_data = entry() + , storage_mode_t storage_mode = storage_mode_sparse + , bool paused = false + , client_data_t userdata = {}); +#endif // TORRENT_ABI_VERSION +#endif + + // Pausing the session has the same effect as pausing every torrent in + // it, except that torrents will not be resumed by the auto-manage + // mechanism. Resuming will restore the torrents to their previous paused + // state. i.e. the session pause state is separate from the torrent pause + // state. A torrent is inactive if it is paused or if the session is + // paused. + void pause(); + void resume(); + bool is_paused() const; + +#if TORRENT_ABI_VERSION == 1 + // *the feature of dynamically loading/unloading torrents is deprecated + // and discouraged* + // + // This function enables dynamic-loading-of-torrent-files_. When a + // torrent is unloaded but needs to be available in memory, this function + // is called **from within the libtorrent network thread**. From within + // this thread, you can **not** use any of the public APIs of libtorrent + // itself. The info-hash of the torrent is passed in to the function + // and it is expected to fill in the passed in ``vector`` with the + // .torrent file corresponding to it. + // + // If there is an error loading the torrent file, the ``error_code`` + // (``ec``) should be set to reflect the error. In such case, the torrent + // itself is stopped and set to an error state with the corresponding + // error code. + // + // Given that the function is called from the internal network thread of + // libtorrent, it's important to not stall. libtorrent will not be able + // to send nor receive any data until the function call returns. + // + // The signature of the function to pass in is:: + // + // void fun(sha1_hash const& info_hash, std::vector& buf, error_code& ec); + TORRENT_DEPRECATED + void set_load_function(user_load_function_t fun); + +#include "libtorrent/aux_/disable_warnings_push.hpp" + + // deprecated in libtorrent 1.1, use performance_counters instead + // returns session wide-statistics and status. For more information, see + // the ``session_status`` struct. + TORRENT_DEPRECATED + session_status status() const; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // deprecated in 1.2 + TORRENT_DEPRECATED + void get_torrent_status(std::vector* ret + , std::function const& pred + , status_flags_t flags = {}) const; + + // ``start_dht`` starts the dht node and makes the trackerless service + // available to torrents. + // + // ``stop_dht`` stops the dht node. + // deprecated. use settings_pack::enable_dht instead + TORRENT_DEPRECATED + void start_dht(); + TORRENT_DEPRECATED + void stop_dht(); +#endif + +#if TORRENT_ABI_VERSION <= 2 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + // ``set_dht_settings`` sets some parameters available to the dht node. + // See dht_settings for more information. + // + // ``get_dht_settings()`` returns the current settings + void set_dht_settings(dht::dht_settings const& settings); + dht::dht_settings get_dht_settings() const; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif + + // ``is_dht_running()`` returns true if the DHT support has been started + // and false otherwise. + bool is_dht_running() const; + + // ``set_dht_storage`` set a dht custom storage constructor function + // to be used internally when the dht is created. + // + // Since the dht storage is a critical component for the dht behavior, + // this function will only be effective the next time the dht is started. + // If you never touch this feature, a default map-memory based storage + // is used. + // + // If you want to make sure the dht is initially created with your + // custom storage, create a session with the setting + // ``settings_pack::enable_dht`` to false, set your constructor function + // and call ``apply_settings`` with ``settings_pack::enable_dht`` to true. + void set_dht_storage(dht::dht_storage_constructor_type sc); + + // ``add_dht_node`` takes a host name and port pair. That endpoint will be + // pinged, and if a valid DHT reply is received, the node will be added to + // the routing table. + void add_dht_node(std::pair const& node); + +#if TORRENT_ABI_VERSION == 1 + // deprecated, use settings_pack::dht_bootstrap_nodes instead + // + // ``add_dht_router`` adds the given endpoint to a list of DHT router + // nodes. If a search is ever made while the routing table is empty, + // those nodes will be used as backups. Nodes in the router node list + // will also never be added to the regular routing table, which + // effectively means they are only used for bootstrapping, to keep the + // load off them. + // + // An example routing node that you could typically add is + // ``router.bittorrent.com``. + TORRENT_DEPRECATED + void add_dht_router(std::pair const& node); +#endif + + // query the DHT for an immutable item at the ``target`` hash. + // the result is posted as a dht_immutable_item_alert. + void dht_get_item(sha1_hash const& target); + + // query the DHT for a mutable item under the public key ``key``. + // this is an ed25519 key. ``salt`` is optional and may be left + // as an empty string if no salt is to be used. + // if the item is found in the DHT, a dht_mutable_item_alert is + // posted. + void dht_get_item(std::array key + , std::string salt = std::string()); + + // store the given bencoded data as an immutable item in the DHT. + // the returned hash is the key that is to be used to look the item + // up again. It's just the SHA-1 hash of the bencoded form of the + // structure. + sha1_hash dht_put_item(entry data); + + // store a mutable item. The ``key`` is the public key the blob is + // to be stored under. The optional ``salt`` argument is a string that + // is to be mixed in with the key when determining where in the DHT + // the value is to be stored. The callback function is called from within + // the libtorrent network thread once we've found where to store the blob, + // possibly with the current value stored under the key. + // The values passed to the callback functions are: + // + // entry& value + // the current value stored under the key (may be empty). Also expected + // to be set to the value to be stored by the function. + // + // std::array& signature + // the signature authenticating the current value. This may be zeros + // if there is currently no value stored. The function is expected to + // fill in this buffer with the signature of the new value to store. + // To generate the signature, you may want to use the + // ``sign_mutable_item`` function. + // + // std::int64_t& seq + // current sequence number. May be zero if there is no current value. + // The function is expected to set this to the new sequence number of + // the value that is to be stored. Sequence numbers must be monotonically + // increasing. Attempting to overwrite a value with a lower or equal + // sequence number will fail, even if the signature is correct. + // + // std::string const& salt + // this is the salt that was used for this put call. + // + // Since the callback function ``cb`` is called from within libtorrent, + // it is critical to not perform any blocking operations. Ideally not + // even locking a mutex. Pass any data required for this function along + // with the function object's context and make the function entirely + // self-contained. The only reason data blob's value is computed + // via a function instead of just passing in the new value is to avoid + // race conditions. If you want to *update* the value in the DHT, you + // must first retrieve it, then modify it, then write it back. The way + // the DHT works, it is natural to always do a lookup before storing and + // calling the callback in between is convenient. + void dht_put_item(std::array key + , std::function& + , std::int64_t&, std::string const&)> cb + , std::string salt = std::string()); + + // ``dht_get_peers()`` will issue a DHT get_peer request to the DHT for the + // specified info-hash. The response (the peers) will be posted back in a + // dht_get_peers_reply_alert. + // + // ``dht_announce()`` will issue a DHT announce request to the DHT to the + // specified info-hash, advertising the specified port. If the port is + // left at its default, 0, the port will be implied by the DHT message's + // source port (which may improve connectivity through a NAT). + // ``dht_announce()`` is not affected by the ``announce_port`` override setting. + // + // Both these functions are exposed for advanced custom use of the DHT. + // All torrents eligible to be announce to the DHT will be automatically, + // by libtorrent. + // + // For possible flags, see announce_flags_t. + void dht_get_peers(sha1_hash const& info_hash); + void dht_announce(sha1_hash const& info_hash, int port = 0, dht::announce_flags_t flags = {}); + + // Retrieve all the live DHT (identified by ``nid``) nodes. All the + // nodes id and endpoint will be returned in the list of nodes in the + // alert ``dht_live_nodes_alert``. + // Since this alert is a response to an explicit call, it will always be + // posted, regardless of the alert mask. + void dht_live_nodes(sha1_hash const& nid); + + // Query the DHT node specified by ``ep`` to retrieve a sample of the + // info-hashes that the node currently have in their storage. + // The ``target`` is included for iterative lookups so that indexing nodes + // can perform a key space traversal with a single RPC per node by adjusting + // the target value for each RPC. It has no effect on the returned sample value. + // The result is posted as a ``dht_sample_infohashes_alert``. + void dht_sample_infohashes(udp::endpoint const& ep, sha1_hash const& target); + + // Send an arbitrary DHT request directly to the specified endpoint. This + // function is intended for use by plugins. When a response is received + // or the request times out, a dht_direct_response_alert will be posted + // with the response (if any) and the userdata pointer passed in here. + // Since this alert is a response to an explicit call, it will always be + // posted, regardless of the alert mask. + void dht_direct_request(udp::endpoint const& ep, entry const& e, client_data_t userdata = {}); + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 0.15 + // use save_state and load_state instead + TORRENT_DEPRECATED + entry dht_state() const; + TORRENT_DEPRECATED + void start_dht(entry const& startup_state); +#endif + + // This function adds an extension to this session. The argument is a + // function object that is called with a ``torrent_handle`` and which should + // return a ``std::shared_ptr``. To write custom + // plugins, see `libtorrent plugins`_. For the typical bittorrent client + // all of these extensions should be added. The main plugins implemented + // in libtorrent are: + // + // uTorrent metadata + // Allows peers to download the metadata (.torrent files) from the swarm + // directly. Makes it possible to join a swarm with just a tracker and + // info-hash. + // + // .. code:: c++ + // + // #include + // ses.add_extension(<::create_ut_metadata_plugin); + // + // uTorrent peer exchange + // Exchanges peers between clients. + // + // .. code:: c++ + // + // #include + // ses.add_extension(<::create_ut_pex_plugin); + // + // smart ban plugin + // A plugin that, with a small overhead, can ban peers + // that sends bad data with very high accuracy. Should + // eliminate most problems on poisoned torrents. + // + // .. code:: c++ + // + // #include + // ses.add_extension(<::create_smart_ban_plugin); + // + // + // .. _`libtorrent plugins`: reference-Plugins.html + void add_extension(std::function( + torrent_handle const&, client_data_t)> ext); + void add_extension(std::shared_ptr ext); + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 0.15 + // use load_state and save_state instead + TORRENT_DEPRECATED + void load_state(entry const& ses_state + , save_state_flags_t flags = save_state_flags_t::all()); + TORRENT_DEPRECATED + entry state() const; +#endif // TORRENT_ABI_VERSION + + // Sets a filter that will be used to reject and accept incoming as well + // as outgoing connections based on their originating ip address. The + // default filter will allow connections to any ip address. To build a + // set of rules for which addresses are accepted and not, see ip_filter. + // + // Each time a peer is blocked because of the IP filter, a + // peer_blocked_alert is generated. ``get_ip_filter()`` Returns the + // ip_filter currently in the session. See ip_filter. + void set_ip_filter(ip_filter f); + ip_filter get_ip_filter() const; + + // apply port_filter ``f`` to incoming and outgoing peers. a port filter + // will reject making outgoing peer connections to certain remote ports. + // The main intention is to be able to avoid triggering certain + // anti-virus software by connecting to SMTP, FTP ports. + void set_port_filter(port_filter const& f); + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.1, use settings_pack::peer_fingerprint instead + TORRENT_DEPRECATED + void set_peer_id(peer_id const& pid); + + // deprecated in 1.1.7. read settings_pack::peer_fingerprint instead + TORRENT_DEPRECATED + peer_id id() const; +#endif + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + // sets the key sent to trackers. If it's not set, it is initialized + // by libtorrent. The key may be used by the tracker to identify the + // peer potentially across you changing your IP. + void set_key(std::uint32_t key); +#endif + + // built-in peer classes + static constexpr peer_class_t global_peer_class_id{0}; + static constexpr peer_class_t tcp_peer_class_id{1}; + static constexpr peer_class_t local_peer_class_id{2}; + + // ``is_listening()`` will tell you whether or not the session has + // successfully opened a listening port. If it hasn't, this function will + // return false, and then you can set a new + // settings_pack::listen_interfaces to try another interface and port to + // bind to. + // + // ``listen_port()`` returns the port we ended up listening on. + unsigned short listen_port() const; + unsigned short ssl_listen_port() const; + bool is_listening() const; + + // Sets the peer class filter for this session. All new peer connections + // will take this into account and be added to the peer classes specified + // by this filter, based on the peer's IP address. + // + // The ip-filter essentially maps an IP -> uint32. Each bit in that 32 + // bit integer represents a peer class. The least significant bit + // represents class 0, the next bit class 1 and so on. + // + // For more info, see ip_filter. + // + // For example, to make all peers in the range 200.1.1.0 - 200.1.255.255 + // belong to their own peer class, apply the following filter: + // + // .. code:: c++ + // + // ip_filter f = ses.get_peer_class_filter(); + // peer_class_t my_class = ses.create_peer_class("200.1.x.x IP range"); + // f.add_rule(make_address("200.1.1.0"), make_address("200.1.255.255") + // , 1 << static_cast(my_class)); + // ses.set_peer_class_filter(f); + // + // This setting only applies to new connections, it won't affect existing + // peer connections. + // + // This function is limited to only peer class 0-31, since there are only + // 32 bits in the IP range mapping. Only the set bits matter; no peer + // class will be removed from a peer as a result of this call, peer + // classes are only added. + // + // The ``peer_class`` argument cannot be greater than 31. The bitmasks + // representing peer classes in the ``peer_class_filter`` are 32 bits. + // + // The ``get_peer_class_filter()`` function returns the current filter. + // + // For more information, see peer-classes_. + void set_peer_class_filter(ip_filter const& f); + ip_filter get_peer_class_filter() const; + + // Sets and gets the *peer class type filter*. This is controls automatic + // peer class assignments to peers based on what kind of socket it is. + // + // It does not only support assigning peer classes, it also supports + // removing peer classes based on socket type. + // + // The order of these rules being applied are: + // + // 1. peer-class IP filter + // 2. peer-class type filter, removing classes + // 3. peer-class type filter, adding classes + // + // For more information, see peer-classes_. + void set_peer_class_type_filter(peer_class_type_filter const& f); + peer_class_type_filter get_peer_class_type_filter() const; + + // Creates a new peer class (see peer-classes_) with the given name. The + // returned integer is the new peer class identifier. Peer classes may + // have the same name, so each invocation of this function creates a new + // class and returns a unique identifier. + // + // Identifiers are assigned from low numbers to higher. So if you plan on + // using certain peer classes in a call to set_peer_class_filter(), + // make sure to create those early on, to get low identifiers. + // + // For more information on peer classes, see peer-classes_. + peer_class_t create_peer_class(char const* name); + + // This call dereferences the reference count of the specified peer + // class. When creating a peer class it's automatically referenced by 1. + // If you want to recycle a peer class, you may call this function. You + // may only call this function **once** per peer class you create. + // Calling it more than once for the same class will lead to memory + // corruption. + // + // Since peer classes are reference counted, this function will not + // remove the peer class if it's still assigned to torrents or peers. It + // will however remove it once the last peer and torrent drops their + // references to it. + // + // There is no need to call this function for custom peer classes. All + // peer classes will be properly destructed when the session object + // destructs. + // + // For more information on peer classes, see peer-classes_. + void delete_peer_class(peer_class_t cid); + + // These functions queries information from a peer class and updates the + // configuration of a peer class, respectively. + // + // ``cid`` must refer to an existing peer class. If it does not, the + // return value of ``get_peer_class()`` is undefined. + // + // ``set_peer_class()`` sets all the information in the + // peer_class_info object in the specified peer class. There is no + // option to only update a single property. + // + // A peer or torrent belonging to more than one class, the highest + // priority among any of its classes is the one that is taken into + // account. + // + // For more information, see peer-classes_. + peer_class_info get_peer_class(peer_class_t cid) const; + void set_peer_class(peer_class_t cid, peer_class_info const& pci); + +#if TORRENT_ABI_VERSION == 1 + // if the listen port failed in some way you can retry to listen on + // another port- range with this function. If the listener succeeded and + // is currently listening, a call to this function will shut down the + // listen port and reopen it using these new properties (the given + // interface and port range). As usual, if the interface is left as 0 + // this function will return false on failure. If it fails, it will also + // generate alerts describing the error. It will return true on success. + enum listen_on_flags_t + { + // this is always on starting with 0.16.2 + listen_reuse_address TORRENT_DEPRECATED_ENUM = 0x01, + listen_no_system_port TORRENT_DEPRECATED_ENUM = 0x02 + }; + + // deprecated in 0.16 + + // specify which interfaces to bind outgoing connections to + // This has been moved to a session setting + TORRENT_DEPRECATED + void use_interfaces(char const* interfaces); + + // instead of using this, specify listen interface and port in + // the settings_pack::listen_interfaces setting + TORRENT_DEPRECATED + void listen_on( + std::pair const& port_range + , error_code& ec + , const char* net_interface = nullptr + , int flags = 0); +#endif + + // delete the files belonging to the torrent from disk. + // including the part-file, if there is one + static constexpr remove_flags_t delete_files = 0_bit; + + // delete just the part-file associated with this torrent + static constexpr remove_flags_t delete_partfile = 1_bit; + +#if TORRENT_ABI_VERSION <= 2 + // this will add common extensions like ut_pex, ut_metadata, lt_tex + // smart_ban and possibly others. + TORRENT_DEPRECATED static constexpr session_flags_t add_default_plugins = 0_bit; +#endif + +#if TORRENT_ABI_VERSION == 1 + // this will start features like DHT, local service discovery, UPnP + // and NAT-PMP. + TORRENT_DEPRECATED static constexpr session_flags_t start_default_features = 1_bit; +#endif + + // when set, the session will start paused. Call + // session_handle::resume() to start + static constexpr session_flags_t paused = 2_bit; + + // ``remove_torrent()`` will close all peer connections associated with + // the torrent and tell the tracker that we've stopped participating in + // the swarm. This operation cannot fail. When it completes, you will + // receive a torrent_removed_alert. + // + // remove_torrent() is non-blocking, but will remove the torrent from the + // session synchronously. Calling session_handle::add_torrent() immediately + // afterward with the same torrent will succeed. Note that this creates a + // new handle which is not equal to the removed one. + // + // The optional second argument ``options`` can be used to delete all the + // files downloaded by this torrent. To do so, pass in the value + // ``session_handle::delete_files``. Once the torrent is deleted, a + // torrent_deleted_alert is posted. + // + // The torrent_handle remains valid for some time after remove_torrent() is + // called. It will become invalid only after all libtorrent tasks (such as + // I/O tasks) release their references to the torrent. Until this happens, + // torrent_handle::is_valid() will return true, and other calls such + // as torrent_handle::status() will succeed. Because of this, and because + // remove_torrent() is non-blocking, the following sequence usually + // succeeds (does not throw system_error): + // .. code:: c++ + // + // session.remove_handle(handle); + // handle.save_resume_data(); + // + // Note that when a queued or downloading torrent is removed, its position + // in the download queue is vacated and every subsequent torrent in the + // queue has their queue positions updated. This can potentially cause a + // large state_update to be posted. When removing all torrents, it is + // advised to remove them from the back of the queue, to minimize the + // shifting. + void remove_torrent(const torrent_handle&, remove_flags_t = {}); + + // Applies the settings specified by the settings_pack ``s``. This is an + // asynchronous operation that will return immediately and actually apply + // the settings to the main thread of libtorrent some time later. + void apply_settings(settings_pack const&); + void apply_settings(settings_pack&&); + settings_pack get_settings() const; + +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // deprecated in libtorrent 1.1. use settings_pack instead + TORRENT_DEPRECATED + void set_pe_settings(pe_settings const&); + TORRENT_DEPRECATED + pe_settings get_pe_settings() const; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // ``set_i2p_proxy`` sets the i2p_ proxy, and tries to open a persistent + // connection to it. The only used fields in the proxy settings structs + // are ``hostname`` and ``port``. + // + // ``i2p_proxy`` returns the current i2p proxy in use. + // + // .. _i2p: http://www.i2p2.de + + TORRENT_DEPRECATED + void set_i2p_proxy(proxy_settings const&); + TORRENT_DEPRECATED + proxy_settings i2p_proxy() const; + + // These functions sets and queries the proxy settings to be used for the + // session. + // + // For more information on what settings are available for proxies, see + // proxy_settings. If the session is not in anonymous mode, proxies that + // aren't working or fail, will automatically be disabled and packets + // will flow without using any proxy. If you want to enforce using a + // proxy, even when the proxy doesn't work, enable anonymous_mode in + // settings_pack. + TORRENT_DEPRECATED + void set_proxy(proxy_settings const& s); + TORRENT_DEPRECATED + proxy_settings proxy() const; + + // deprecated in 0.16 + // Get the number of uploads. + TORRENT_DEPRECATED + int num_uploads() const; + + // Get the number of connections. This number also contains the + // number of half open connections. + TORRENT_DEPRECATED + int num_connections() const; + + // deprecated in 0.15. + TORRENT_DEPRECATED + void set_peer_proxy(proxy_settings const& s); + TORRENT_DEPRECATED + void set_web_seed_proxy(proxy_settings const& s); + TORRENT_DEPRECATED + void set_tracker_proxy(proxy_settings const& s); + + TORRENT_DEPRECATED + proxy_settings peer_proxy() const; + TORRENT_DEPRECATED + proxy_settings web_seed_proxy() const; + TORRENT_DEPRECATED + proxy_settings tracker_proxy() const; + + TORRENT_DEPRECATED + void set_dht_proxy(proxy_settings const& s); + TORRENT_DEPRECATED + proxy_settings dht_proxy() const; + + // deprecated in 0.16 + TORRENT_DEPRECATED + int upload_rate_limit() const; + TORRENT_DEPRECATED + int download_rate_limit() const; + TORRENT_DEPRECATED + int local_upload_rate_limit() const; + TORRENT_DEPRECATED + int local_download_rate_limit() const; + TORRENT_DEPRECATED + int max_half_open_connections() const; + + TORRENT_DEPRECATED + void set_local_upload_rate_limit(int bytes_per_second); + TORRENT_DEPRECATED + void set_local_download_rate_limit(int bytes_per_second); + TORRENT_DEPRECATED + void set_upload_rate_limit(int bytes_per_second); + TORRENT_DEPRECATED + void set_download_rate_limit(int bytes_per_second); + TORRENT_DEPRECATED + void set_max_uploads(int limit); + TORRENT_DEPRECATED + void set_max_connections(int limit); + TORRENT_DEPRECATED + void set_max_half_open_connections(int limit); + + TORRENT_DEPRECATED + int max_connections() const; + TORRENT_DEPRECATED + int max_uploads() const; + +#endif + + // Alerts is the main mechanism for libtorrent to report errors and + // events. ``pop_alerts`` fills in the vector passed to it with pointers + // to new alerts. The session still owns these alerts and they will stay + // valid until the next time ``pop_alerts`` is called. You may not delete + // the alert objects. + // + // It is safe to call ``pop_alerts`` from multiple different threads, as + // long as the alerts themselves are not accessed once another thread + // calls ``pop_alerts``. Doing this requires manual synchronization + // between the popping threads. + // + // ``wait_for_alert`` will block the current thread for ``max_wait`` time + // duration, or until another alert is posted. If an alert is available + // at the time of the call, it returns immediately. The returned alert + // pointer is the head of the alert queue. ``wait_for_alert`` does not + // pop alerts from the queue, it merely peeks at it. The returned alert + // will stay valid until ``pop_alerts`` is called twice. The first time + // will pop it and the second will free it. + // + // If there is no alert in the queue and no alert arrives within the + // specified timeout, ``wait_for_alert`` returns nullptr. + // + // In the python binding, ``wait_for_alert`` takes the number of + // milliseconds to wait as an integer. + // + // The alert queue in the session will not grow indefinitely. Make sure + // to pop periodically to not miss notifications. To control the max + // number of alerts that's queued by the session, see + // ``settings_pack::alert_queue_size``. + // + // Some alerts are considered so important that they are posted even when + // the alert queue is full. Some alerts are considered mandatory and cannot + // be disabled by the ``alert_mask``. For instance, + // save_resume_data_alert and save_resume_data_failed_alert are always + // posted, regardless of the alert mask. + // + // To control which alerts are posted, set the alert_mask + // (settings_pack::alert_mask). + // + // If the alert queue fills up to the point where alerts are dropped, this + // will be indicated by a alerts_dropped_alert, which contains a bitmask + // of which types of alerts were dropped. Generally it is a good idea to + // make sure the alert queue is large enough, the alert_mask doesn't have + // unnecessary categories enabled and to call pop_alert() frequently, to + // avoid alerts being dropped. + // + // the ``set_alert_notify`` function lets the client set a function object + // to be invoked every time the alert queue goes from having 0 alerts to + // 1 alert. This function is called from within libtorrent, it may be the + // main thread, or it may be from within a user call. The intention of + // of the function is that the client wakes up its main thread, to poll + // for more alerts using ``pop_alerts()``. If the notify function fails + // to do so, it won't be called again, until ``pop_alerts`` is called for + // some other reason. For instance, it could signal an eventfd, post a + // message to an HWND or some other main message pump. The actual + // retrieval of alerts should not be done in the callback. In fact, the + // callback should not block. It should not perform any expensive work. + // It really should just notify the main application thread. + // + // The type of an alert is returned by the polymorphic function + // ``alert::type()`` but can also be queries from a concrete type via + // ``T::alert_type``, as a static constant. + void pop_alerts(std::vector* alerts); + alert* wait_for_alert(time_duration max_wait); + void set_alert_notify(std::function const& fun); + +#if TORRENT_ABI_VERSION == 1 + // use the setting instead + TORRENT_DEPRECATED + size_t set_alert_queue_size_limit(size_t queue_size_limit_); + + // Changes the mask of which alerts to receive. By default only errors + // are reported. ``m`` is a bitmask where each bit represents a category + // of alerts. + // + // ``get_alert_mask()`` returns the current mask; + // + // See category_t enum for options. + TORRENT_DEPRECATED + void set_alert_mask(std::uint32_t m); + TORRENT_DEPRECATED + std::uint32_t get_alert_mask() const; + + // Starts and stops Local Service Discovery. This service will broadcast + // the info-hashes of all the non-private torrents on the local network to + // look for peers on the same swarm within multicast reach. + // + // deprecated. use settings_pack::enable_lsd instead + TORRENT_DEPRECATED + void start_lsd(); + TORRENT_DEPRECATED + void stop_lsd(); + + // Starts and stops the UPnP service. When started, the listen port and + // the DHT port are attempted to be forwarded on local UPnP router + // devices. + // + // The upnp object returned by ``start_upnp()`` can be used to add and + // remove arbitrary port mappings. Mapping status is returned through the + // portmap_alert and the portmap_error_alert. The object will be valid + // until ``stop_upnp()`` is called. See upnp-and-nat-pmp_. + // + // deprecated. use settings_pack::enable_upnp instead + TORRENT_DEPRECATED + void start_upnp(); + TORRENT_DEPRECATED + void stop_upnp(); + + // Starts and stops the NAT-PMP service. When started, the listen port + // and the DHT port are attempted to be forwarded on the router through + // NAT-PMP. + // + // The natpmp object returned by ``start_natpmp()`` can be used to add + // and remove arbitrary port mappings. Mapping status is returned through + // the portmap_alert and the portmap_error_alert. The object will be + // valid until ``stop_natpmp()`` is called. See upnp-and-nat-pmp_. + // + // deprecated. use settings_pack::enable_natpmp instead + TORRENT_DEPRECATED + void start_natpmp(); + TORRENT_DEPRECATED + void stop_natpmp(); +#endif + + // protocols used by add_port_mapping() + static constexpr portmap_protocol udp = portmap_protocol::udp; + static constexpr portmap_protocol tcp = portmap_protocol::tcp; + + // add_port_mapping adds one or more port forwards on UPnP and/or NAT-PMP, + // whichever is enabled. A mapping is created for each listen socket + // in the session. The return values are all handles referring to the + // port mappings that were just created. Pass them to delete_port_mapping() + // to remove them. + std::vector add_port_mapping(portmap_protocol t, int external_port, int local_port); + void delete_port_mapping(port_mapping_t handle); + + // This option indicates if the ports are mapped using natpmp + // and upnp. If mapping was already made, they are deleted and added + // again. This only works if natpmp and/or upnp are configured to be + // enable. + static constexpr reopen_network_flags_t reopen_map_ports = 0_bit; + + // Instructs the session to reopen all listen and outgoing sockets. + // + // It's useful in the case your platform doesn't support the built in + // IP notifier mechanism, or if you have a better more reliable way to + // detect changes in the IP routing table. + void reopen_network_sockets(reopen_network_flags_t options = reopen_map_ports); + + // This function is intended only for use by plugins. This type does + // not have a stable API and should be relied on as little as possible. + std::shared_ptr native_handle() const + { return m_impl.lock(); } + + private: + + template + void async_call(Fun f, Args&&... a) const; + + template + void sync_call(Fun f, Args&&... a) const; + + template + Ret sync_call_ret(Fun f, Args&&... a) const; + + explicit session_handle(std::weak_ptr impl) + : m_impl(std::move(impl)) + {} + + std::weak_ptr m_impl; + }; + +} // namespace libtorrent + +#endif // TORRENT_SESSION_HANDLE_HPP_INCLUDED diff --git a/include/libtorrent/session_params.hpp b/include/libtorrent/session_params.hpp new file mode 100644 index 0000000..49b0eb2 --- /dev/null +++ b/include/libtorrent/session_params.hpp @@ -0,0 +1,156 @@ +/* + +Copyright (c) 2015, Steven Siloti +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_PARAMS_HPP_INCLUDED +#define TORRENT_SESSION_PARAMS_HPP_INCLUDED + +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/kademlia/dht_state.hpp" +#include "libtorrent/session_types.hpp" +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/ip_filter.hpp" + +#if TORRENT_ABI_VERSION <= 2 +#include "libtorrent/kademlia/dht_settings.hpp" +#endif + +namespace libtorrent { + +TORRENT_VERSION_NAMESPACE_3 +struct plugin; +TORRENT_VERSION_NAMESPACE_3_END + +struct disk_interface; +struct counters; + +using disk_io_constructor_type = std::function( + io_context&, settings_interface const&, counters&)>; + +TORRENT_VERSION_NAMESPACE_3 + +// The session_params is a parameters pack for configuring the session +// before it's started. +struct TORRENT_EXPORT session_params +{ + // This constructor can be used to start with the default plugins + // (ut_metadata, ut_pex and smart_ban). Pass a settings_pack to set the + // initial settings when the session starts. + session_params(settings_pack&& sp); // NOLINT + session_params(settings_pack const& sp); // NOLINT + session_params(); + + // hidden + ~session_params(); + + // This constructor helps to configure the set of initial plugins + // to be added to the session before it's started. + session_params(settings_pack&& sp + , std::vector> exts); + session_params(settings_pack const& sp + , std::vector> exts); + + // hidden + session_params(session_params const&); + session_params(session_params&&); + session_params& operator=(session_params const&) &; + session_params& operator=(session_params&&) &; + + // The settings to configure the session with + settings_pack settings; + + // the plugins to add to the session as it is constructed + std::vector> extensions; + +#if TORRENT_ABI_VERSION <= 2 + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + // this is deprecated. Use the dht_* settings instead. + dht::dht_settings dht_settings; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif + + // DHT node ID and node addresses to bootstrap the DHT with. + dht::dht_state dht_state; + + // function object to construct the storage object for DHT items. + dht::dht_storage_constructor_type dht_storage_constructor; + + // function object to create the disk I/O subsystem. Defaults to + // default_disk_io_constructor. + disk_io_constructor_type disk_io_constructor; + + // this container can be used by extensions/plugins to store settings. It's + // primarily here to make it convenient to save and restore state across + // sessions, using read_session_params() and write_session_params(). + std::map ext_state; + + // the IP filter to use for the session. This restricts which peers are allowed + // to connect. As if passed to set_ip_filter(). + libtorrent::ip_filter ip_filter; +}; + +TORRENT_VERSION_NAMESPACE_3_END + +// These functions serialize and de-serialize a ``session_params`` object to and +// from bencoded form. The session_params object is used to initialize a new +// session using the state from a previous one (or by programmatically configure +// the session up-front). +// The flags parameter can be used to only save and load certain aspects of the +// session's state. +// The ``_buf`` suffix indicates the function operates on buffer rather than the +// bencoded structure. +// The torrents in a session are not part of the session_params state, they have +// to be restored separately. +TORRENT_EXPORT session_params read_session_params(bdecode_node const& e + , save_state_flags_t flags = save_state_flags_t::all()); +TORRENT_EXPORT session_params read_session_params(span buf + , save_state_flags_t flags = save_state_flags_t::all()); +TORRENT_EXPORT entry write_session_params(session_params const& sp + , save_state_flags_t flags = save_state_flags_t::all()); +TORRENT_EXPORT std::vector write_session_params_buf(session_params const& sp + , save_state_flags_t flags = save_state_flags_t::all()); + +} + +#endif diff --git a/include/libtorrent/session_settings.hpp b/include/libtorrent/session_settings.hpp new file mode 100644 index 0000000..a4ae9d6 --- /dev/null +++ b/include/libtorrent/session_settings.hpp @@ -0,0 +1,118 @@ +/* + +Copyright (c) 2006-2007, 2010, 2013-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_SETTINGS_HPP_INCLUDED +#define TORRENT_SESSION_SETTINGS_HPP_INCLUDED + +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/config.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/aux_/proxy_settings.hpp" +#include "libtorrent/kademlia/dht_settings.hpp" + +#include + +namespace libtorrent { + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + using dht_settings = dht::dht_settings; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + using aux::proxy_settings; + + // The ``pe_settings`` structure is used to control the settings related + // to peer protocol encryption. + struct TORRENT_DEPRECATED_EXPORT pe_settings + { + // initializes the encryption settings with the default values + pe_settings() + : out_enc_policy(enabled) + , in_enc_policy(enabled) + , allowed_enc_level(both) + , prefer_rc4(false) + {} + + // the encoding policy options for use with pe_settings::out_enc_policy + // and pe_settings::in_enc_policy. + enum enc_policy + { + // Only encrypted connections are allowed. Incoming connections that + // are not encrypted are closed and if the encrypted outgoing + // connection fails, a non-encrypted retry will not be made. + forced, + + // encrypted connections are enabled, but non-encrypted connections + // are allowed. An incoming non-encrypted connection will be accepted, + // and if an outgoing encrypted connection fails, a non- encrypted + // connection will be tried. + enabled, + + // only non-encrypted connections are allowed. + disabled + }; + + // the encryption levels, to be used with pe_settings::allowed_enc_level. + enum enc_level + { + // use only plaintext encryption + plaintext = 1, + // use only rc4 encryption + rc4 = 2, + // allow both + both = 3 + }; + + // control the settings for incoming + // and outgoing connections respectively. + // see enc_policy enum for the available options. + std::uint8_t out_enc_policy; + std::uint8_t in_enc_policy; + + // determines the encryption level of the + // connections. This setting will adjust which encryption scheme is + // offered to the other peer, as well as which encryption scheme is + // selected by the client. See enc_level enum for options. + std::uint8_t allowed_enc_level; + + // if the allowed encryption level is both, setting this to + // true will prefer rc4 if both methods are offered, plaintext + // otherwise + bool prefer_rc4; + }; +} + +#endif // TORRENT_ABI_VERSION +#endif diff --git a/include/libtorrent/session_stats.hpp b/include/libtorrent/session_stats.hpp new file mode 100644 index 0000000..d36fb86 --- /dev/null +++ b/include/libtorrent/session_stats.hpp @@ -0,0 +1,79 @@ +/* + +Copyright (c) 2015, 2017-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_STATS_HPP_INCLUDED +#define TORRENT_SESSION_STATS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/string_view.hpp" + +#include + +namespace libtorrent { + + enum class metric_type_t + { + counter, gauge + }; + + // describes one statistics metric from the session. For more information, + // see the session-statistics_ section. + struct TORRENT_EXPORT stats_metric + { + // the name of the counter or gauge + char const* name; + + // the index into the session stats array, where the underlying value of + // this counter or gauge is found. The session stats array is part of the + // session_stats_alert object. + int value_index; +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED static constexpr metric_type_t type_counter = metric_type_t::counter; + TORRENT_DEPRECATED static constexpr metric_type_t type_gauge = metric_type_t::gauge; +#endif + metric_type_t type; + }; + + // This free function returns the list of available metrics exposed by + // libtorrent's statistics API. Each metric has a name and a *value index*. + // The value index is the index into the array in session_stats_alert where + // this metric's value can be found when the session stats is sampled (by + // calling post_session_stats()). + TORRENT_EXPORT std::vector session_stats_metrics(); + + // given a name of a metric, this function returns the counter index of it, + // or -1 if it could not be found. The counter index is the index into the + // values array returned by session_stats_alert. + TORRENT_EXPORT int find_metric_idx(string_view name); +} + +#endif diff --git a/include/libtorrent/session_status.hpp b/include/libtorrent/session_status.hpp new file mode 100644 index 0000000..34ec416 --- /dev/null +++ b/include/libtorrent/session_status.hpp @@ -0,0 +1,238 @@ +/* + +Copyright (c) 2006, 2008-2011, 2013-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, Falco +Copyright (c) 2017, Falcosc +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_STATUS_HPP_INCLUDED +#define TORRENT_SESSION_STATUS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/export.hpp" +#include + +#if TORRENT_ABI_VERSION == 1 +// for dht_lookup and dht_routing_bucket +#include "libtorrent/alert_types.hpp" +#endif + +#if TORRENT_ABI_VERSION == 1 +namespace libtorrent { + + // holds counters and gauges for the uTP sockets + // deprecated in 1.1 in favor of session_stats counters, which is a more + // flexible, extensible and performant mechanism for stats. + struct TORRENT_DEPRECATED_EXPORT utp_status + { + // gauges. These are snapshots of the number of + // uTP sockets in each respective state + int num_idle; + int num_syn_sent; + int num_connected; + int num_fin_sent; + int num_close_wait; + + // These are monotonically increasing + // and cumulative counters for their respective event. + std::uint64_t packet_loss; + std::uint64_t timeout; + std::uint64_t packets_in; + std::uint64_t packets_out; + std::uint64_t fast_retransmit; + std::uint64_t packet_resend; + std::uint64_t samples_above_target; + std::uint64_t samples_below_target; + std::uint64_t payload_pkts_in; + std::uint64_t payload_pkts_out; + std::uint64_t invalid_pkts_in; + std::uint64_t redundant_pkts_in; + }; + + // contains session wide state and counters + // deprecated in 1.1 in favor of session_stats counters, which is a more + // flexible, extensible and performant mechanism for stats. + struct TORRENT_DEPRECATED_EXPORT session_status + { + // false as long as no incoming connections have been + // established on the listening socket. Every time you change the listen port, this will + // be reset to false. + bool has_incoming_connections; + + // the total download and upload rates accumulated + // from all torrents. This includes bittorrent protocol, DHT and an estimated TCP/IP + // protocol overhead. + // deprecated, use session_stats_metrics "net.recv_bytes" + "net.recv_ip_overhead_bytes" + // they does include payload + protocol + ip overhead bytes + int upload_rate; + int download_rate; + + // the total number of bytes downloaded and + // uploaded to and from all torrents. This also includes all the protocol overhead. + // deprecated, use session_stats_metrics "net.recv_bytes" + "net.recv_ip_overhead_bytes" + // they does include payload + protocol + ip overhead bytes + std::int64_t total_download; + std::int64_t total_upload; + + // the rate of the payload + // down- and upload only. + // deprecated, use session_stats_metrics "net.recv_payload_bytes" + int payload_upload_rate; + // deprecated, use session_stats_metrics "net.sent_payload_bytes" + int payload_download_rate; + + // the total transfers of payload + // only. The payload does not include the bittorrent protocol overhead, but only parts of the + // actual files to be downloaded. + // ``total_payload_download`` is deprecated, use session_stats_metrics + // "net.recv_payload_bytes" ``total_payload_upload`` is deprecated, use + // session_stats_metrics "net.sent_payload_bytes" + std::int64_t total_payload_download; + std::int64_t total_payload_upload; + + // the estimated TCP/IP overhead in each direction. + int ip_overhead_upload_rate; + int ip_overhead_download_rate; + std::int64_t total_ip_overhead_download; + std::int64_t total_ip_overhead_upload; + + // the upload and download rate used by DHT traffic. Also the total number + // of bytes sent and received to and from the DHT. + int dht_upload_rate; + int dht_download_rate; + std::int64_t total_dht_download; + std::int64_t total_dht_upload; + + // the upload and download rate used by tracker traffic. Also the total number + // of bytes sent and received to and from trackers. + int tracker_upload_rate; + int tracker_download_rate; + std::int64_t total_tracker_download; + std::int64_t total_tracker_upload; + + // the number of bytes that has been received more than once. + // This can happen if a request from a peer times out and is requested from a different + // peer, and then received again from the first one. To make this lower, increase the + // ``request_timeout`` and the ``piece_timeout`` in the session settings. + std::int64_t total_redundant_bytes; + + // the number of bytes that was downloaded which later failed + // the hash-check. + std::int64_t total_failed_bytes; + + // the total number of peer connections this session has. This includes + // incoming connections that still hasn't sent their handshake or outgoing connections + // that still hasn't completed the TCP connection. This number may be slightly higher + // than the sum of all peers of all torrents because the incoming connections may not + // be assigned a torrent yet. + int num_peers; + + int num_dead_peers; + + // the current number of unchoked peers. + int num_unchoked; + + // the current allowed number of unchoked peers. + int allowed_upload_slots; + + // the number of peers that are + // waiting for more bandwidth quota from the torrent rate limiter. + int up_bandwidth_queue; + int down_bandwidth_queue; + + // count the number of + // bytes the connections are waiting for to be able to send and receive. + int up_bandwidth_bytes_queue; + int down_bandwidth_bytes_queue; + + // tells the number of + // seconds until the next optimistic unchoke change and the start of the next + // unchoke interval. These numbers may be reset prematurely if a peer that is + // unchoked disconnects or becomes not interested. + int optimistic_unchoke_counter; + int unchoke_counter; + + // the number of peers currently + // waiting on a disk write or disk read to complete before it receives or sends + // any more data on the socket. It'a a metric of how disk bound you are. + int disk_write_queue; + int disk_read_queue; + + // only available when + // built with DHT support. They are all set to 0 if the DHT isn't running. When + // the DHT is running, ``dht_nodes`` is set to the number of nodes in the routing + // table. This number only includes *active* nodes, not cache nodes. The + // ``dht_node_cache`` is set to the number of nodes in the node cache. These nodes + // are used to replace the regular nodes in the routing table in case any of them + // becomes unresponsive. + // deprecated, use session_stats_metrics "dht.dht_nodes" and "dht.dht_nodes_cache" + int dht_nodes; + int dht_node_cache; + + // the number of torrents tracked by the DHT at the moment. + int dht_torrents; + + // an estimation of the total number of nodes in the DHT + // network. + std::int64_t dht_global_nodes; + + // a vector of the currently running DHT lookups. + std::vector active_requests; + + // contains information about every bucket in the DHT routing + // table. + std::vector dht_routing_table; + + // the number of nodes allocated dynamically for a + // particular DHT lookup. This represents roughly the amount of memory used + // by the DHT. + int dht_total_allocations; + +#include "libtorrent/aux_/disable_warnings_push.hpp" + + // statistics on the uTP sockets. + utp_status utp_stats; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // the number of known peers across all torrents. These are not necessarily + // connected peers, just peers we know of. + int peerlist_size; + + // the number of torrents in the + // session and the number of them that are currently paused, respectively. + int num_torrents; + int num_paused_torrents; + }; +} +#endif // TORRENT_ABI_VERSION + +#endif // TORRENT_SESSION_STATUS_HPP_INCLUDED diff --git a/include/libtorrent/session_types.hpp b/include/libtorrent/session_types.hpp new file mode 100644 index 0000000..f897095 --- /dev/null +++ b/include/libtorrent/session_types.hpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2017, Alden Torres +Copyright (c) 2017-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_TYPES_HPP_INCLUDED +#define TORRENT_SESSION_TYPES_HPP_INCLUDED + +#include +#include "libtorrent/flags.hpp" + +namespace libtorrent { + + // hidden + using save_state_flags_t = flags::bitfield_flag; + + // hidden + using session_flags_t = flags::bitfield_flag; + + // The flags type used to specify options to removing files of torrents + using remove_flags_t = flags::bitfield_flag; + + // hidden + using reopen_network_flags_t = flags::bitfield_flag; +} + +#endif + diff --git a/include/libtorrent/settings_pack.hpp b/include/libtorrent/settings_pack.hpp new file mode 100644 index 0000000..b35729d --- /dev/null +++ b/include/libtorrent/settings_pack.hpp @@ -0,0 +1,2263 @@ +/* + +Copyright (c) 2014-2022, Arvid Norberg +Copyright (c) 2016-2018, Alden Torres +Copyright (c) 2017, Andrei Kurushin +Copyright (c) 2017, Steven Siloti +Copyright (c) 2018, TheOriginalWinCat +Copyright (c) 2019, Amir Abrams +Copyright (c) 2022, Kevin Bracey +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SETTINGS_PACK_HPP_INCLUDED +#define TORRENT_SETTINGS_PACK_HPP_INCLUDED + +#include "libtorrent/entry.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/flags.hpp" + +#include +#include + +// OVERVIEW +// +// You have some control over session configuration through the session::apply_settings() +// member function. To change one or more configuration options, create a settings_pack +// object and fill it with the settings to be set and pass it in to session::apply_settings(). +// +// The settings_pack object is a collection of settings updates that are applied +// to the session when passed to session::apply_settings(). It's empty when +// constructed. +// +// You have control over proxy and authorization settings and also the user-agent +// that will be sent to the tracker. The user-agent will also be used to identify the +// client with other peers. +// +// Each configuration option is named with an enum value inside the +// settings_pack class. These are the available settings: +namespace libtorrent { + +namespace aux { + struct session_impl; + struct session_settings; + struct session_settings_single_thread; +} + + struct settings_pack; + struct bdecode_node; + + TORRENT_EXTRA_EXPORT settings_pack load_pack_from_dict(bdecode_node const& settings); + + TORRENT_EXTRA_EXPORT void save_settings_to_dict(settings_pack const& sett, entry::dictionary_type& out); + TORRENT_EXTRA_EXPORT settings_pack non_default_settings(aux::session_settings const& sett); + TORRENT_EXTRA_EXPORT void apply_pack(settings_pack const* pack, aux::session_settings& sett + , aux::session_impl* ses = nullptr); + TORRENT_EXTRA_EXPORT void apply_pack_impl(settings_pack const* pack + , aux::session_settings_single_thread& sett + , std::vector* callbacks = nullptr); + TORRENT_EXTRA_EXPORT void run_all_updates(aux::session_impl& ses); + + // converts a setting integer (from the enums string_types, int_types or + // bool_types) to a string, and vice versa. + TORRENT_EXPORT int setting_by_name(string_view name); + TORRENT_EXPORT char const* name_for_setting(int s); + + // returns a settings_pack with every setting set to its default value + TORRENT_EXPORT settings_pack default_settings(); + + // the common interface to settings_pack and the internal representation of + // settings. + struct TORRENT_EXPORT settings_interface + { + virtual void set_str(int name, std::string val) = 0; + virtual void set_int(int name, int val) = 0; + virtual void set_bool(int name, bool val) = 0; + virtual bool has_val(int name) const = 0; + + virtual std::string const& get_str(int name) const = 0; + virtual int get_int(int name) const = 0; + virtual bool get_bool(int name) const = 0; + + template + // hidden + void set_int(int name, flags::bitfield_flag const val) + { set_int(name, static_cast(static_cast(val))); } + + // hidden + // these are here just to suppress the warning about virtual destructors + // internal + settings_interface() = default; + settings_interface(settings_interface const&) = default; + settings_interface(settings_interface&&) = default; + settings_interface& operator=(settings_interface const&) = default; + settings_interface& operator=(settings_interface&&) = default; + protected: + ~settings_interface() = default; + }; + + // The ``settings_pack`` struct, contains the names of all settings as + // enum values. These values are passed in to the ``set_str()``, + // ``set_int()``, ``set_bool()`` functions, to specify the setting to + // change. + // + // The ``settings_pack`` only stores values for settings that have been + // explicitly set on this object. However, it can still be queried for + // settings that have not been set and returns the default value for those + // settings. + // + // .. include:: settings-ref.rst + // + struct TORRENT_EXPORT settings_pack final : settings_interface + { + friend TORRENT_EXTRA_EXPORT void apply_pack_impl(settings_pack const* + , aux::session_settings_single_thread& + , std::vector*); + + // hidden + settings_pack() = default; + settings_pack(settings_pack const&) = default; + settings_pack(settings_pack&&) noexcept = default; + settings_pack& operator=(settings_pack const&) = default; + settings_pack& operator=(settings_pack&&) noexcept = default; + + // set a configuration option in the settings_pack. ``name`` is one of + // the enum values from string_types, int_types or bool_types. They must + // match the respective type of the set_* function. + void set_str(int name, std::string val) override; + void set_int(int name, int val) override; + void set_bool(int name, bool val) override; + template + void set_int(int name, flags::bitfield_flag const val) + { set_int(name, static_cast(static_cast(val))); } + + // queries whether the specified configuration option has a value set in + // this pack. ``name`` can be any enumeration value from string_types, + // int_types or bool_types. + bool has_val(int name) const override; + + // clear the settings pack from all settings + void clear(); + + // clear a specific setting from the pack + void clear(int name); + + // queries the current configuration option from the settings_pack. + // ``name`` is one of the enumeration values from string_types, int_types + // or bool_types. The enum value must match the type of the get_* + // function. If the specified setting field has not been set, the default + // value is returned. + std::string const& get_str(int name) const override; + int get_int(int name) const override; + bool get_bool(int name) const override; + + // setting names (indices) are 16 bits. The two most significant + // bits indicate what type the setting has. (string, int, bool) + enum type_bases + { + string_type_base = 0x0000, + int_type_base = 0x4000, + bool_type_base = 0x8000, + type_mask = 0xc000, + index_mask = 0x3fff + }; + + // internal + template + void for_each(Fun&& f) const + { + for (auto const& s : m_strings) f(s.first, s.second); + for (auto const& i : m_ints) f(i.first, i.second); + for (auto const& b : m_bools) f(b.first, b.second); + } + + // hidden + enum string_types + { + // this is the client identification to the tracker. The recommended + // format of this string is: "client-name/client-version + // libtorrent/libtorrent-version". This name will not only be used when + // making HTTP requests, but also when sending extended headers to + // peers that support that extension. It may not contain \r or \n + user_agent = string_type_base, + + // ``announce_ip`` is the ip address passed along to trackers as the + // ``&ip=`` parameter. If left as the default, that parameter is + // omitted. + // + // .. note:: + // This setting is only meant for very special cases where a seed is + // running on the same host as the tracker, and the tracker accepts + // the IP parameter (which normal trackers don't). Do not set this + // option unless you also control the tracker. + announce_ip, + +#if TORRENT_ABI_VERSION == 1 + // ``mmap_cache`` may be set to a filename where the disk cache will + // be mmapped to. This could be useful, for instance, to map the disk + // cache from regular rotating hard drives onto an SSD drive. Doing + // that effectively introduces a second layer of caching, allowing the + // disk cache to be as big as can fit on an SSD drive (probably about + // one order of magnitude more than the available RAM). The intention + // of this setting is to set it up once at the start up and not change + // it while running. The setting may not be changed as long as there + // are any disk buffers in use. This default to the empty string, + // which means use regular RAM allocations for the disk cache. The + // file specified will be created and truncated to the disk cache size + // (``cache_size``). Any existing file with the same name will be + // replaced. + // + // This feature requires the ``mmap`` system call, on systems that + // don't have ``mmap`` this setting is ignored. + mmap_cache TORRENT_DEPRECATED_ENUM, +#else + deprecated_mmap_cache, +#endif + + // this is the client name and version identifier sent to peers in the + // handshake message. If this is an empty string, the user_agent is + // used instead. This string must be a UTF-8 encoded unicode string. + handshake_client_version, + + // This controls which IP address outgoing TCP peer connections are bound + // to, in addition to controlling whether such connections are also + // bound to a specific network interface/adapter (*bind-to-device*). + // + // This string is a comma-separated list of IP addresses and + // interface names. An empty string will not bind TCP sockets to a + // device, and let the network stack assign the local address. + // + // A list of names will be used to bind outgoing TCP sockets in a + // round-robin fashion. An IP address will simply be used to `bind()` + // the socket. An interface name will attempt to bind the socket to + // that interface. If that fails, or is unsupported, one of the IP + // addresses configured for that interface is used to `bind()` the + // socket to. If the interface or adapter doesn't exist, the + // outgoing peer connection will fail with an error message suggesting + // the device cannot be found. Adapter names on Unix systems are of + // the form "eth0", "eth1", "tun0", etc. This may be useful for + // clients that are multi-homed. Binding an outgoing connection to a + // local IP does not necessarily make the connection via the + // associated NIC/Adapter. + // + // When outgoing interfaces are specified, incoming connections or + // packets sent to a local interface or IP that's *not* in this list + // will be rejected with a peer_blocked_alert with + // ``invalid_local_interface`` as the reason. + // + // Note that these are just interface/adapter names or IP addresses. + // There are no ports specified in this list. IPv6 addresses without + // port should be specified without enclosing ``[``, ``]``. + outgoing_interfaces, + + // a comma-separated list of (IP or device name, port) pairs. These are + // the listen ports that will be opened for accepting incoming uTP and + // TCP peer connections. These are also used for *outgoing* uTP and UDP + // tracker connections and DHT nodes. + // + // It is possible to listen on multiple interfaces and + // multiple ports. Binding to port 0 will make the operating system + // pick the port. + // + // .. note:: + // There are reasons to stick to the same port across sessions, + // which would mean only using port 0 on the first start, and + // recording the port that was picked for subsequent startups. + // Trackers, the DHT and other peers will remember the port they see + // you use and hand that port out to other peers trying to connect + // to you, as well as trying to connect to you themselves. + // + // A port that has an "s" suffix will accept SSL peer connections. (note + // that SSL sockets are only available in builds with SSL support) + // + // A port that has an "l" suffix will be considered a local network. + // i.e. it's assumed to only be able to reach hosts in the same local + // network as the IP address (based on the netmask associated with the + // IP, queried from the operating system). + // + // if binding fails, the listen_failed_alert is posted. Once a + // socket binding succeeds (if it does), the listen_succeeded_alert + // is posted. There may be multiple failures before a success. + // + // If a device name that does not exist is configured, no listen + // socket will be opened for that interface. If this is the only + // interface configured, it will be as if no listen ports are + // configured. + // + // If no listen ports are configured (e.g. listen_interfaces is an + // empty string), networking will be disabled. No DHT will start, no + // outgoing uTP or tracker connections will be made. No incoming TCP + // or uTP connections will be accepted. (outgoing TCP connections + // will still be possible, depending on + // settings_pack::outgoing_interfaces). + // + // For example: + // ``[::1]:8888`` - will only accept connections on the IPv6 loopback + // address on port 8888. + // + // ``eth0:4444,eth1:4444`` - will accept connections on port 4444 on + // any IP address bound to device ``eth0`` or ``eth1``. + // + // ``[::]:0s`` - will accept SSL connections on a port chosen by the + // OS. And not accept non-SSL connections at all. + // + // ``0.0.0.0:6881,[::]:6881`` - binds to all interfaces on port 6881. + // + // ``10.0.1.13:6881l`` - binds to the local IP address, port 6881, but + // only allow talking to peers on the same local network. The netmask + // is queried from the operating system. Interfaces marked ``l`` are + // not announced to trackers, unless the tracker is also on the same + // local network. + // + // Windows OS network adapter device name must be specified with GUID. + // It can be obtained from "netsh lan show interfaces" command output. + // GUID must be uppercased string embraced in curly brackets. + // ``{E4F0B674-0DFC-48BB-98A5-2AA730BDB6D6}:7777`` - will accept + // connections on port 7777 on adapter with this GUID. + // + // For more information, see the `Multi-homed hosts`_ section. + // + // .. _`Multi-homed hosts`: manual-ref.html#multi-homed-hosts + listen_interfaces, + + // when using a proxy, this is the hostname where the proxy is running + // see proxy_type. Note that when using a proxy, the + // settings_pack::listen_interfaces setting is overridden and only a + // single interface is created, just to contact the proxy. This + // means a proxy cannot be combined with SSL torrents or multiple + // listen interfaces. This proxy listen interface will not accept + // incoming TCP connections, will not map ports with any gateway and + // will not enable local service discovery. All traffic is supposed + // to be channeled through the proxy. + proxy_hostname, + + // when using a proxy, these are the credentials (if any) to use when + // connecting to it. see proxy_type + proxy_username, + proxy_password, + + // sets the i2p_ SAM bridge to connect to. set the port with the + // ``i2p_port`` setting. Unless this is set, i2p torrents are not + // supported. This setting is separate from the other proxy settings + // since i2p torrents and their peers are orthogonal. You can have + // i2p peers as well as regular peers via a proxy. + // + // .. _i2p: http://www.i2p2.de + i2p_hostname, + + // this is the fingerprint for the client. It will be used as the + // prefix to the peer_id. If this is 20 bytes (or longer) it will be + // truncated to 20 bytes and used as the entire peer-id + // + // There is a utility function, generate_fingerprint() that can be used + // to generate a standard client peer ID fingerprint prefix. + peer_fingerprint, + + // This is a comma-separated list of IP port-pairs. They will be added + // to the DHT node (if it's enabled) as back-up nodes in case we don't + // know of any. + // + // Changing these after the DHT has been started may not have any + // effect until the DHT is restarted. + // Here are some other bootstrap nodes that may work: + // ``router.bittorrent.com:6881``, + // ``dht.transmissionbt.com:6881`` + // ``router.bt.ouinet.work:6881``, + dht_bootstrap_nodes, + + max_string_setting_internal + }; + + // hidden + enum bool_types + { + // determines if connections from the same IP address as existing + // connections should be rejected or not. Rejecting multiple connections + // from the same IP address will prevent abusive + // behavior by peers. The logic for determining whether connections are + // to the same peer is more complicated with this enabled, and more + // likely to fail in some edge cases. It is not recommended to enable + // this feature. + allow_multiple_connections_per_ip = bool_type_base, + +#if TORRENT_ABI_VERSION == 1 + // if set to true, upload, download and unchoke limits are ignored for + // peers on the local network. This option is *DEPRECATED*, please use + // set_peer_class_filter() instead. + ignore_limits_on_local_network TORRENT_DEPRECATED_ENUM, +#else + deprecated_ignore_limits_on_local_network, +#endif + + // ``send_redundant_have`` controls if have messages will be sent to + // peers that already have the piece. This is typically not necessary, + // but it might be necessary for collecting statistics in some cases. + send_redundant_have, + +#if TORRENT_ABI_VERSION == 1 + // if this is true, outgoing bitfields will never be fuil. If the + // client is seed, a few bits will be set to 0, and later filled in + // with have messages. This is to prevent certain ISPs from stopping + // people from seeding. + lazy_bitfields TORRENT_DEPRECATED_ENUM, +#else + deprecated_lazy_bitfield, +#endif + + // ``use_dht_as_fallback`` determines how the DHT is used. If this is + // true, the DHT will only be used for torrents where all trackers in + // its tracker list has failed. Either by an explicit error message or + // a time out. If this is false, the DHT is used regardless of if the + // trackers fail or not. + use_dht_as_fallback, + + // ``upnp_ignore_nonrouters`` indicates whether or not the UPnP + // implementation should ignore any broadcast response from a device + // whose address is not on our subnet. i.e. + // it's a way to not talk to other people's routers by mistake. + upnp_ignore_nonrouters, + + // ``use_parole_mode`` specifies if parole mode should be used. Parole + // mode means that peers that participate in pieces that fail the hash + // check are put in a mode where they are only allowed to download + // whole pieces. If the whole piece a peer in parole mode fails the + // hash check, it is banned. If a peer participates in a piece that + // passes the hash check, it is taken out of parole mode. + use_parole_mode, + +#if TORRENT_ABI_VERSION == 1 + // enable and disable caching of blocks read from disk. the purpose of + // the read cache is partly read-ahead of requests but also to avoid + // reading blocks back from the disk multiple times for popular + // pieces. + use_read_cache TORRENT_DEPRECATED_ENUM, + use_write_cache TORRENT_DEPRECATED_ENUM, + + // this will make the disk cache never flush a write piece if it would + // cause is to have to re-read it once we want to calculate the piece + // hash + dont_flush_write_cache TORRENT_DEPRECATED_ENUM, + + // allocate separate, contiguous, buffers for read and write calls. + // Only used where writev/readv cannot be used will use more RAM but + // may improve performance + coalesce_reads TORRENT_DEPRECATED_ENUM, + coalesce_writes TORRENT_DEPRECATED_ENUM, +#else + deprecated_use_read_cache, + deprecated_use_write_cache, + deprecated_flush_write_cache, + deprecated_coalesce_reads, + deprecated_coalesce_writes, +#endif + + // if true, prefer seeding torrents when determining which torrents to give + // active slots to. If false, give preference to downloading torrents + auto_manage_prefer_seeds, + + // if ``dont_count_slow_torrents`` is true, torrents without any + // payload transfers are not subject to the ``active_seeds`` and + // ``active_downloads`` limits. This is intended to make it more + // likely to utilize all available bandwidth, and avoid having + // torrents that don't transfer anything block the active slots. + dont_count_slow_torrents, + + // ``close_redundant_connections`` specifies whether libtorrent should + // close connections where both ends have no utility in keeping the + // connection open. For instance if both ends have completed their + // downloads, there's no point in keeping it open. + close_redundant_connections, + + // If ``prioritize_partial_pieces`` is true, partial pieces are picked + // before pieces that are more rare. If false, rare pieces are always + // prioritized, unless the number of partial pieces is growing out of + // proportion. + prioritize_partial_pieces, + + // if set to true, the estimated TCP/IP overhead is drained from the + // rate limiters, to avoid exceeding the limits with the total traffic + rate_limit_ip_overhead, + + // ``announce_to_all_trackers`` controls how multi tracker torrents + // are treated. If this is set to true, all trackers in the same tier + // are announced to in parallel. If all trackers in tier 0 fails, all + // trackers in tier 1 are announced as well. If it's set to false, the + // behavior is as defined by the multi tracker specification. + // + // ``announce_to_all_tiers`` also controls how multi tracker torrents + // are treated. When this is set to true, one tracker from each tier + // is announced to. This is the uTorrent behavior. To be compliant + // with the Multi-tracker specification, set it to false. + announce_to_all_tiers, + announce_to_all_trackers, + + // ``prefer_udp_trackers``: true means that trackers + // may be rearranged in a way that udp trackers are always tried + // before http trackers for the same hostname. Setting this to false + // means that the tracker's tier is respected and there's no + // preference of one protocol over another. + prefer_udp_trackers, + +#if TORRENT_ABI_VERSION == 1 + // ``strict_super_seeding`` when this is set to true, a piece has to + // have been forwarded to a third peer before another one is handed + // out. This is the traditional definition of super seeding. + strict_super_seeding TORRENT_DEPRECATED_ENUM, +#else + deprecated_strict_super_seeding, +#endif + +#if TORRENT_ABI_VERSION == 1 + // if this is set to true, the memory allocated for the disk cache + // will be locked in physical RAM, never to be swapped out. Every time + // a disk buffer is allocated and freed, there will be the extra + // overhead of a system call. + lock_disk_cache TORRENT_DEPRECATED_ENUM, +#else + deprecated_lock_disk_cache, +#endif + + // when set to true, all data downloaded from peers will be assumed to + // be correct, and not tested to match the hashes in the torrent this + // is only useful for simulation and testing purposes (typically + // combined with disabled_storage) + disable_hash_checks, + + // if this is true, i2p torrents are allowed to also get peers from + // other sources than the tracker, and connect to regular IPs, not + // providing any anonymization. This may be useful if the user is not + // interested in the anonymization of i2p, but still wants to be able + // to connect to i2p peers. + allow_i2p_mixed, + +#if TORRENT_ABI_VERSION == 1 + // ``low_prio_disk`` determines if the disk I/O should use a normal or + // low priority policy. True, means that it's + // low priority by default. Other processes doing disk I/O will + // normally take priority in this mode. This is meant to improve the + // overall responsiveness of the system while downloading in the + // background. For high-performance server setups, this might not be + // desirable. + low_prio_disk TORRENT_DEPRECATED_ENUM, +#else + deprecated_low_prio_disk, +#endif + +#if TORRENT_ABI_VERSION <= 2 + // ``volatile_read_cache``, if this is set to true, read cache blocks + // that are hit by peer read requests are removed from the disk cache + // to free up more space. This is useful if you don't expect the disk + // cache to create any cache hits from other peers than the one who + // triggered the cache line to be read into the cache in the first + // place. + volatile_read_cache TORRENT_DEPRECATED_ENUM, +#else + deprecated_volatile_read_cache, +#endif + +#if TORRENT_ABI_VERSION == 1 + // ``guided_read_cache`` enables the disk cache to adjust the size of + // a cache line generated by peers to depend on the upload rate you + // are sending to that peer. The intention is to optimize the RAM + // usage of the cache, to read ahead further for peers that you're + // sending faster to. + guided_read_cache TORRENT_DEPRECATED_ENUM, +#else + deprecated_guided_read_cache, +#endif + + // ``no_atime_storage`` this is a Linux-only option and passes in the + // ``O_NOATIME`` to ``open()`` when opening files. This may lead to + // some disk performance improvements. + no_atime_storage, + + // ``incoming_starts_queued_torrents``. If a torrent + // has been paused by the auto managed feature in libtorrent, i.e. the + // torrent is paused and auto managed, this feature affects whether or + // not it is automatically started on an incoming connection. The main + // reason to queue torrents, is not to make them unavailable, but to + // save on the overhead of announcing to the trackers, the DHT and to + // avoid spreading one's unchoke slots too thin. If a peer managed to + // find us, even though we're no in the torrent anymore, this setting + // can make us start the torrent and serve it. + incoming_starts_queued_torrents, + + // when set to true, the downloaded counter sent to trackers will + // include the actual number of payload bytes downloaded including + // redundant bytes. If set to false, it will not include any redundancy + // bytes + report_true_downloaded, + + // ``strict_end_game_mode`` controls when a + // block may be requested twice. If this is ``true``, a block may only + // be requested twice when there's at least one request to every piece + // that's left to download in the torrent. This may slow down progress + // on some pieces sometimes, but it may also avoid downloading a lot + // of redundant bytes. If this is ``false``, libtorrent attempts to + // use each peer connection to its max, by always requesting + // something, even if it means requesting something that has been + // requested from another peer already. + strict_end_game_mode, + +#if TORRENT_ABI_VERSION == 1 + // if ``broadcast_lsd`` is set to true, the local peer discovery (or + // Local Service Discovery) will not only use IP multicast, but also + // broadcast its messages. This can be useful when running on networks + // that don't support multicast. Since broadcast messages might be + // expensive and disruptive on networks, only every 8th announce uses + // broadcast. + broadcast_lsd TORRENT_DEPRECATED_ENUM, +#else + deprecated_broadcast_lsd, +#endif + + // Enables incoming and outgoing, TCP and uTP peer connections. + // ``false`` is disabled and ``true`` is enabled. When outgoing + // connections are disabled, libtorrent will simply not make + // outgoing peer connections with the specific transport protocol. + // Disabled incoming peer connections will simply be rejected. + // These options only apply to peer connections, not tracker- or any + // other kinds of connections. + enable_outgoing_utp, + enable_incoming_utp, + enable_outgoing_tcp, + enable_incoming_tcp, + +#if TORRENT_ABI_VERSION == 1 + // ``ignore_resume_timestamps`` determines if the storage, when + // loading resume data files, should verify that the file modification + // time with the timestamps in the resume data. False, means timestamps + // are taken into account, and resume + // data is less likely to accepted (torrents are more likely to be + // fully checked when loaded). It might be useful to set this to true + // if your network is faster than your disk, and it would be faster to + // redownload potentially missed pieces than to go through the whole + // storage to look for them. + ignore_resume_timestamps TORRENT_DEPRECATED_ENUM, +#else + // hidden + deprecated_ignore_resume_timestamps, +#endif + + // ``no_recheck_incomplete_resume`` determines if the storage should + // check the whole files when resume data is incomplete or missing or + // whether it should simply assume we don't have any of the data. If + // false, any existing files will be checked. + // By setting this setting to true, the files won't be checked, but + // will go straight to download mode. + no_recheck_incomplete_resume, + + // ``anonymous_mode``: When set to true, the client tries to hide + // its identity to a certain degree. + // + // * A generic user-agent will be + // used for trackers (except for private torrents). + // * Your local IPv4 and IPv6 address won't be sent as query string + // parameters to private trackers. + // * If announce_ip is configured, it will not be sent to trackers + // * The client version will not be sent to peers in the extension + // handshake. + anonymous_mode, + + // specifies whether downloads from web seeds is reported to the + // tracker or not. Turning it off also excludes web + // seed traffic from other stats and download rate reporting via the + // libtorrent API. + report_web_seed_downloads, + +#if TORRENT_ABI_VERSION == 1 + // set to true if uTP connections should be rate limited This option + // is *DEPRECATED*, please use set_peer_class_filter() instead. + rate_limit_utp TORRENT_DEPRECATED_ENUM, +#else + deprecated_rate_limit_utp, +#endif + +#if TORRENT_ABI_VERSION == 1 + // if this is true, the ``&ip=`` argument in tracker requests (unless + // otherwise specified) will be set to the intermediate IP address if + // the user is double NATed. If the user is not double NATed, this + // option does not have an affect + announce_double_nat TORRENT_DEPRECATED_ENUM, +#else + deprecated_announce_double_nat, +#endif + + // ``seeding_outgoing_connections`` determines if seeding (and + // finished) torrents should attempt to make outgoing connections or + // not. It may be set to false in very + // specific applications where the cost of making outgoing connections + // is high, and there are no or small benefits of doing so. For + // instance, if no nodes are behind a firewall or a NAT, seeds don't + // need to make outgoing connections. + seeding_outgoing_connections, + + // when this is true, libtorrent will not attempt to make outgoing + // connections to peers whose port is < 1024. This is a safety + // precaution to avoid being part of a DDoS attack + no_connect_privileged_ports, + + // ``smooth_connects`` means the number of + // connection attempts per second may be limited to below the + // ``connection_speed``, in case we're close to bump up against the + // limit of number of connections. The intention of this setting is to + // more evenly distribute our connection attempts over time, instead + // of attempting to connect in batches, and timing them out in + // batches. + smooth_connects, + + // always send user-agent in every web seed request. If false, only + // the first request per http connection will include the user agent + always_send_user_agent, + + // ``apply_ip_filter_to_trackers`` determines + // whether the IP filter applies to trackers as well as peers. If this + // is set to false, trackers are exempt from the IP filter (if there + // is one). If no IP filter is set, this setting is irrelevant. + apply_ip_filter_to_trackers, + +#if TORRENT_ABI_VERSION == 1 + // ``use_disk_read_ahead`` if true will attempt to + // optimize disk reads by giving the operating system heads up of disk + // read requests as they are queued in the disk job queue. + use_disk_read_ahead TORRENT_DEPRECATED_ENUM, +#else + deprecated_use_disk_read_ahead, +#endif + +#if TORRENT_ABI_VERSION == 1 + // ``lock_files`` determines whether or not to lock files which + // libtorrent is downloading to or seeding from. This is implemented + // using ``fcntl(F_SETLK)`` on Unix systems and by not passing in + // ``SHARE_READ`` and ``SHARE_WRITE`` on windows. This might prevent + // 3rd party processes from corrupting the files under libtorrent's + // feet. + lock_files TORRENT_DEPRECATED_ENUM, +#else + deprecated_lock_files, +#endif + +#if TORRENT_ABI_VERSION == 1 + // ``contiguous_recv_buffer`` determines whether or not libtorrent + // should receive data from peers into a contiguous intermediate + // buffer, to then copy blocks into disk buffers from, or to make many + // smaller calls to ``read()``, each time passing in the specific + // buffer the data belongs in. When downloading at high rates, the + // latter may save some time copying data. When seeding at high rates, + // all incoming traffic consists of a very large number of tiny + // packets, and enabling ``contiguous_recv_buffer`` will provide + // higher performance. When this is enabled, it will only be used when + // seeding to peers, since that's when it provides performance + // improvements. + contiguous_recv_buffer TORRENT_DEPRECATED_ENUM, +#else + deprecated_contiguous_recv_buffer, +#endif + + // when true, web seeds sending bad data will be banned + ban_web_seeds, + +#if TORRENT_ABI_VERSION <= 2 + // when set to false, the ``write_cache_line_size`` will apply across + // piece boundaries. this is a bad idea unless the piece picker also + // is configured to have an affinity to pick pieces belonging to the + // same write cache line as is configured in the disk cache. + allow_partial_disk_writes TORRENT_DEPRECATED_ENUM, +#else + deprecated_allow_partial_disk_writes, +#endif + +#if TORRENT_ABI_VERSION == 1 + // If true, disables any communication that's not going over a proxy. + // Enabling this requires a proxy to be configured as well, see + // proxy_type and proxy_hostname settings. The listen sockets are + // closed, and incoming connections will only be accepted through a + // SOCKS5 or I2P proxy (if a peer proxy is set up and is run on the + // same machine as the tracker proxy). + force_proxy TORRENT_DEPRECATED_ENUM, +#else + deprecated_force_proxy, +#endif + + // if false, prevents libtorrent to advertise share-mode support + support_share_mode, + +#if TORRENT_ABI_VERSION <= 2 + // support for BEP 30 merkle torrents has been removed + + // if this is false, don't advertise support for the Tribler merkle + // tree piece message + support_merkle_torrents TORRENT_DEPRECATED_ENUM, +#else + deprecated_support_merkle_torrents, +#endif + + // if this is true, the number of redundant bytes is sent to the + // tracker + report_redundant_bytes, + + // if this is true, libtorrent will fall back to listening on a port + // chosen by the operating system (i.e. binding to port 0). If a + // failure is preferred, set this to false. + listen_system_port_fallback, + +#if TORRENT_ABI_VERSION == 1 + // ``use_disk_cache_pool`` enables using a pool allocator for disk + // cache blocks. Enabling it makes the cache perform better at high + // throughput. It also makes the cache less likely and slower at + // returning memory back to the system, once allocated. + use_disk_cache_pool TORRENT_DEPRECATED_ENUM, +#else + deprecated_use_disk_cache_pool, +#endif + + // when this is true, and incoming encrypted connections are enabled, + // &supportcrypt=1 is included in http tracker announces + announce_crypto_support, + + // Starts and stops the UPnP service. When started, the listen port + // and the DHT port are attempted to be forwarded on local UPnP router + // devices. + // + // The upnp object returned by ``start_upnp()`` can be used to add and + // remove arbitrary port mappings. Mapping status is returned through + // the portmap_alert and the portmap_error_alert. The object will be + // valid until ``stop_upnp()`` is called. See upnp-and-nat-pmp_. + enable_upnp, + + // Starts and stops the NAT-PMP service. When started, the listen port + // and the DHT port are attempted to be forwarded on the router + // through NAT-PMP. + // + // The natpmp object returned by ``start_natpmp()`` can be used to add + // and remove arbitrary port mappings. Mapping status is returned + // through the portmap_alert and the portmap_error_alert. The object + // will be valid until ``stop_natpmp()`` is called. See + // upnp-and-nat-pmp_. + enable_natpmp, + + // Starts and stops Local Service Discovery. This service will + // broadcast the info-hashes of all the non-private torrents on the + // local network to look for peers on the same swarm within multicast + // reach. + enable_lsd, + + // starts the dht node and makes the trackerless service available to + // torrents. + enable_dht, + + // if the allowed encryption level is both, setting this to true will + // prefer RC4 if both methods are offered, plain text otherwise + prefer_rc4, + + // if true, hostname lookups are done via the configured proxy (if + // any). This is only supported by SOCKS5 and HTTP. + proxy_hostnames, + + // if true, peer connections are made (and accepted) over the + // configured proxy, if any. Web seeds as well as regular bittorrent + // peer connections are considered "peer connections". Anything + // transporting actual torrent payload (trackers and DHT traffic are + // not considered peer connections). + proxy_peer_connections, + + // if this setting is true, torrents with a very high availability of + // pieces (and seeds) are downloaded sequentially. This is more + // efficient for the disk I/O. With many seeds, the download order is + // unlikely to matter anyway + auto_sequential, + + // if true, tracker connections are made over the configured proxy, if + // any. + proxy_tracker_connections, + + // Starts and stops the internal IP table route changes notifier. + // + // The current implementation supports multiple platforms, and it is + // recommended to have it enable, but you may want to disable it if + // it's supported but unreliable, or if you have a better way to + // detect the changes. In the later case, you should manually call + // ``session_handle::reopen_network_sockets`` to ensure network + // changes are taken in consideration. + enable_ip_notifier, + + // when this is true, nodes whose IDs are derived from their source + // IP according to `BEP 42`_ are preferred in the routing table. + dht_prefer_verified_node_ids, + + // determines if the routing table entries should restrict entries to one + // per IP. This defaults to true, which helps mitigate some attacks on + // the DHT. It prevents adding multiple nodes with IPs with a very close + // CIDR distance. + // + // when set, nodes whose IP address that's in the same /24 (or /64 for + // IPv6) range in the same routing table bucket. This is an attempt to + // mitigate node ID spoofing attacks also restrict any IP to only have a + // single entry in the whole routing table + dht_restrict_routing_ips, + + // determines if DHT searches should prevent adding nodes with IPs with + // very close CIDR distance. This also defaults to true and helps + // mitigate certain attacks on the DHT. + dht_restrict_search_ips, + + // makes the first buckets in the DHT routing table fit 128, 64, 32 and + // 16 nodes respectively, as opposed to the standard size of 8. All other + // buckets have size 8 still. + dht_extended_routing_table, + + // slightly changes the lookup behavior in terms of how many outstanding + // requests we keep. Instead of having branch factor be a hard limit, we + // always keep *branch factor* outstanding requests to the closest nodes. + // i.e. every time we get results back with closer nodes, we query them + // right away. It lowers the lookup times at the cost of more outstanding + // queries. + dht_aggressive_lookups, + + // when set, perform lookups in a way that is slightly more expensive, + // but which minimizes the amount of information leaked about you. + dht_privacy_lookups, + + // when set, node's whose IDs that are not correctly generated based on + // its external IP are ignored. When a query arrives from such node, an + // error message is returned with a message saying "invalid node ID". + dht_enforce_node_id, + + // ignore DHT messages from parts of the internet we wouldn't expect to + // see any traffic from + dht_ignore_dark_internet, + + // when set, the other nodes won't keep this node in their routing + // tables, it's meant for low-power and/or ephemeral devices that + // cannot support the DHT, it is also useful for mobile devices which + // are sensitive to network traffic and battery life. + // this node no longer responds to 'query' messages, and will place a + // 'ro' key (value = 1) in the top-level message dictionary of outgoing + // query messages. + dht_read_only, + + // when this is true, create an affinity for downloading 4 MiB extents + // of adjacent pieces. This is an attempt to achieve better disk I/O + // throughput by downloading larger extents of bytes, for torrents with + // small piece sizes + piece_extent_affinity, + + // when set to true, the certificate of HTTPS trackers and HTTPS web + // seeds will be validated against the system's certificate store + // (as defined by OpenSSL). If the system does not have a + // certificate store, this option may have to be disabled in order + // to get trackers and web seeds to work). + validate_https_trackers, + + // when enabled, tracker and web seed requests are subject to + // certain restrictions. + // + // An HTTP(s) tracker requests to localhost (loopback) + // must have the request path start with "/announce". This is the + // conventional bittorrent tracker request. Any other HTTP(S) + // tracker request to loopback will be rejected. This applies to + // trackers that redirect to loopback as well. + // + // Web seeds that end up on the client's local network (i.e. in a + // private IP address range) may not include query string arguments. + // This applies to web seeds redirecting to the local network as + // well. + // + // Web seeds on global IPs (i.e. not local network) may not redirect + // to a local network address + ssrf_mitigation, + + // when disabled, any tracker or web seed with an IDNA hostname + // (internationalized domain name) is ignored. This is a security + // precaution to avoid various unicode encoding attacks that might + // happen at the application level. + allow_idna, + + // when set to true, enables the attempt to use SetFileValidData() + // to pre-allocate disk space. This system call will only work when + // running with Administrator privileges on Windows, and so this + // setting is only relevant in that scenario. Using + // SetFileValidData() poses a security risk, as it may reveal + // previously deleted information from the disk. + enable_set_file_valid_data, + + // When using a SOCKS5 proxy, UDP traffic is routed through the + // proxy by sending a UDP ASSOCIATE command. If this option is true, + // the UDP ASSOCIATE command will include the IP address and + // listen port to the local UDP socket. This indicates to the proxy + // which source endpoint to expect our packets from. The benefit is + // that incoming packets can be forwarded correctly, before any + // outgoing packets are sent. The risk is that if there's a NAT + // between the client and the proxy, the IP address specified in the + // protocol may not be valid from the proxy's point of view. + socks5_udp_send_local_ep, + + max_bool_setting_internal + }; + + // hidden + enum int_types + { + // ``tracker_completion_timeout`` is the number of seconds the tracker + // connection will wait from when it sent the request until it + // considers the tracker to have timed-out. + tracker_completion_timeout = int_type_base, + + // ``tracker_receive_timeout`` is the number of seconds to wait to + // receive any data from the tracker. If no data is received for this + // number of seconds, the tracker will be considered as having timed + // out. If a tracker is down, this is the kind of timeout that will + // occur. + tracker_receive_timeout, + + // ``stop_tracker_timeout`` is the number of seconds to wait when + // sending a stopped message before considering a tracker to have + // timed out. This is usually shorter, to make the client quit faster. + // If the value is set to 0, the connections to trackers with the + // stopped event are suppressed. + stop_tracker_timeout, + + // this is the maximum number of bytes in a tracker response. If a + // response size passes this number of bytes it will be rejected and + // the connection will be closed. On gzipped responses this size is + // measured on the uncompressed data. So, if you get 20 bytes of gzip + // response that'll expand to 2 megabytes, it will be interrupted + // before the entire response has been uncompressed (assuming the + // limit is lower than 2 MiB). + tracker_maximum_response_length, + + // the number of seconds from a request is sent until it times out if + // no piece response is returned. + piece_timeout, + + // the number of seconds one block (16 kiB) is expected to be received + // within. If it's not, the block is requested from a different peer + request_timeout, + + // the length of the request queue given in the number of seconds it + // should take for the other end to send all the pieces. i.e. the + // actual number of requests depends on the download rate and this + // number. + request_queue_time, + + // the number of outstanding block requests a peer is allowed to queue + // up in the client. If a peer sends more requests than this (before + // the first one has been sent) the last request will be dropped. the + // higher this is, the faster upload speeds the client can get to a + // single peer. + max_allowed_in_request_queue, + + // ``max_out_request_queue`` is the maximum number of outstanding + // requests to send to a peer. This limit takes precedence over + // ``request_queue_time``. i.e. no matter the download speed, the + // number of outstanding requests will never exceed this limit. + max_out_request_queue, + + // if a whole piece can be downloaded in this number of seconds, or + // less, the peer_connection will prefer to request whole pieces at a + // time from this peer. The benefit of this is to better utilize disk + // caches by doing localized accesses and also to make it easier to + // identify bad peers if a piece fails the hash check. + whole_pieces_threshold, + + // ``peer_timeout`` is the number of seconds the peer connection + // should wait (for any activity on the peer connection) before + // closing it due to time out. 120 seconds is + // specified in the protocol specification. After half + // the time out, a keep alive message is sent. + peer_timeout, + + // same as peer_timeout, but only applies to url-seeds. this is + // usually set lower, because web servers are expected to be more + // reliable. + urlseed_timeout, + + // controls the pipelining size of url and http seeds. i.e. the number of HTTP + // request to keep outstanding before waiting for the first one to + // complete. It's common for web servers to limit this to a relatively + // low number, like 5 + urlseed_pipeline_size, + + // number of seconds until a new retry of a url-seed takes place. + // Default retry value for http-seeds that don't provide + // a valid ``retry-after`` header. + urlseed_wait_retry, + + // sets the upper limit on the total number of files this session will + // keep open. The reason why files are left open at all is that some + // anti virus software hooks on every file close, and scans the file + // for viruses. deferring the closing of the files will be the + // difference between a usable system and a completely hogged down + // system. Most operating systems also has a limit on the total number + // of file descriptors a process may have open. + file_pool_size, + + // ``max_failcount`` is the maximum times we try to + // connect to a peer before stop connecting again. If a + // peer succeeds, the failure counter is reset. If a + // peer is retrieved from a peer source (other than DHT) + // the failcount is decremented by one, allowing another + // try. + max_failcount, + + // the number of seconds to wait to reconnect to a peer. this time is + // multiplied with the failcount. + min_reconnect_time, + + // ``peer_connect_timeout`` the number of seconds to wait after a + // connection attempt is initiated to a peer until it is considered as + // having timed out. This setting is especially important in case the + // number of half-open connections are limited, since stale half-open + // connection may delay the connection of other peers considerably. + peer_connect_timeout, + + // ``connection_speed`` is the number of connection attempts that are + // made per second. If a number < 0 is specified, it will default to + // 200 connections per second. If 0 is specified, it means don't make + // outgoing connections at all. + connection_speed, + + // if a peer is uninteresting and uninterested for longer than this + // number of seconds, it will be disconnected. + inactivity_timeout, + + // ``unchoke_interval`` is the number of seconds between + // chokes/unchokes. On this interval, peers are re-evaluated for being + // choked/unchoked. This is defined as 30 seconds in the protocol, and + // it should be significantly longer than what it takes for TCP to + // ramp up to it's max rate. + unchoke_interval, + + // ``optimistic_unchoke_interval`` is the number of seconds between + // each *optimistic* unchoke. On this timer, the currently + // optimistically unchoked peer will change. + optimistic_unchoke_interval, + + // ``num_want`` is the number of peers we want from each tracker + // request. It defines what is sent as the ``&num_want=`` parameter to + // the tracker. + num_want, + + // ``initial_picker_threshold`` specifies the number of pieces we need + // before we switch to rarest first picking. The first + // ``initial_picker_threshold`` pieces in any torrent are picked at random + // , the following pieces are picked in rarest first order. + initial_picker_threshold, + + // the number of allowed pieces to send to peers that supports the + // fast extensions + allowed_fast_set_size, + + // ``suggest_mode`` controls whether or not libtorrent will send out + // suggest messages to create a bias of its peers to request certain + // pieces. The modes are: + // + // * ``no_piece_suggestions`` which will not send out suggest messages. + // * ``suggest_read_cache`` which will send out suggest messages for + // the most recent pieces that are in the read cache. + suggest_mode, + + // ``max_queued_disk_bytes`` is the maximum number of bytes, to + // be written to disk, that can wait in the disk I/O thread queue. + // This queue is only for waiting for the disk I/O thread to receive + // the job and either write it to disk or insert it in the write + // cache. When this limit is reached, the peer connections will stop + // reading data from their sockets, until the disk thread catches up. + // Setting this too low will severely limit your download rate. + max_queued_disk_bytes, + + // the number of seconds to wait for a handshake response from a peer. + // If no response is received within this time, the peer is + // disconnected. + handshake_timeout, + + // ``send_buffer_low_watermark`` the minimum send buffer target size + // (send buffer includes bytes pending being read from disk). For good + // and snappy seeding performance, set this fairly high, to at least + // fit a few blocks. This is essentially the initial window size which + // will determine how fast we can ramp up the send rate + // + // if the send buffer has fewer bytes than ``send_buffer_watermark``, + // we'll read another 16 kiB block onto it. If set too small, upload + // rate capacity will suffer. If set too high, memory will be wasted. + // The actual watermark may be lower than this in case the upload rate + // is low, this is the upper limit. + // + // the current upload rate to a peer is multiplied by this factor to + // get the send buffer watermark. The factor is specified as a + // percentage. i.e. 50 -> 0.5 This product is clamped to the + // ``send_buffer_watermark`` setting to not exceed the max. For high + // speed upload, this should be set to a greater value than 100. For + // high capacity connections, setting this higher can improve upload + // performance and disk throughput. Setting it too high may waste RAM + // and create a bias towards read jobs over write jobs. + send_buffer_low_watermark, + send_buffer_watermark, + send_buffer_watermark_factor, + + // ``choking_algorithm`` specifies which algorithm to use to determine + // how many peers to unchoke. The unchoking algorithm for + // downloading torrents is always "tit-for-tat", i.e. the peers we + // download the fastest from are unchoked. + // + // The options for choking algorithms are defined in the + // choking_algorithm_t enum. + // + // ``seed_choking_algorithm`` controls the seeding unchoke behavior. + // i.e. How we select which peers to unchoke for seeding torrents. + // Since a seeding torrent isn't downloading anything, the + // tit-for-tat mechanism cannot be used. The available options are + // defined in the seed_choking_algorithm_t enum. + choking_algorithm, + seed_choking_algorithm, + +#if TORRENT_ABI_VERSION == 1 + // ``cache_size`` is the disk write and read cache. It is specified + // in units of 16 kiB blocks. Buffers that are part of a peer's send + // or receive buffer also count against this limit. Send and receive + // buffers will never be denied to be allocated, but they will cause + // the actual cached blocks to be flushed or evicted. If this is set + // to -1, the cache size is automatically set based on the amount of + // physical RAM on the machine. If the amount of physical RAM cannot + // be determined, it's set to 1024 (= 16 MiB). + // + // On 32 bit builds, the effective cache size will be limited to 3/4 of + // 2 GiB to avoid exceeding the virtual address space limit. + cache_size TORRENT_DEPRECATED_ENUM, + + // Disk buffers are allocated using a pool allocator, the number of + // blocks that are allocated at a time when the pool needs to grow can + // be specified in ``cache_buffer_chunk_size``. Lower numbers saves + // memory at the expense of more heap allocations. If it is set to 0, + // the effective chunk size is proportional to the total cache size, + // attempting to strike a good balance between performance and memory + // usage. It defaults to 0. + cache_buffer_chunk_size TORRENT_DEPRECATED_ENUM, + + // ``cache_expiry`` is the number of seconds + // from the last cached write to a piece in the write cache, to when + // it's forcefully flushed to disk. + cache_expiry TORRENT_DEPRECATED_ENUM, +#else + deprecated_cache_size, + deprecated_cache_buffer_chunk_size, + deprecated_cache_expiry, +#endif + + // determines how files are opened when they're in read only mode + // versus read and write mode. The options are: + // + // enable_os_cache + // Files are opened normally, with the OS caching reads and writes. + // disable_os_cache + // This opens all files in no-cache mode. This corresponds to the + // OS not letting blocks for the files linger in the cache. This + // makes sense in order to avoid the bittorrent client to + // potentially evict all other processes' cache by simply handling + // high throughput and large files. If libtorrent's read cache is + // disabled, enabling this may reduce performance. + // write_through + // flush pieces to disk as they complete validation. + // + // One reason to disable caching is that it may help the operating + // system from growing its file cache indefinitely. + disk_io_write_mode, + disk_io_read_mode, + + // this is the first port to use for binding outgoing connections to. + // This is useful for users that have routers that allow QoS settings + // based on local port. when binding outgoing connections to specific + // ports, ``num_outgoing_ports`` is the size of the range. It should + // be more than a few + // + // .. warning:: setting outgoing ports will limit the ability to keep + // multiple connections to the same client, even for different + // torrents. It is not recommended to change this setting. Its main + // purpose is to use as an escape hatch for cheap routers with QoS + // capability but can only classify flows based on port numbers. + // + // It is a range instead of a single port because of the problems with + // failing to reconnect to peers if a previous socket to that peer and + // port is in ``TIME_WAIT`` state. + outgoing_port, + num_outgoing_ports, + + // ``peer_dscp`` determines the DSCP field in the IP header of every + // packet sent to peers (including web seeds). ``0x0`` means no marking, + // ``0x04`` represents Lower Effort. For more details see `RFC 8622`_. + // + // .. _`RFC 8622`: http://www.faqs.org/rfcs/rfc8622.html + // + // ``peer_tos`` is the backwards compatible name for this setting. + peer_dscp, + + // hidden + peer_tos = peer_dscp, + + // for auto managed torrents, these are the limits they are subject + // to. If there are too many torrents some of the auto managed ones + // will be paused until some slots free up. ``active_downloads`` and + // ``active_seeds`` controls how many active seeding and downloading + // torrents the queuing mechanism allows. The target number of active + // torrents is ``min(active_downloads + active_seeds, active_limit)``. + // ``active_downloads`` and ``active_seeds`` are upper limits on the + // number of downloading torrents and seeding torrents respectively. + // Setting the value to -1 means unlimited. + // + // For example if there are 10 seeding torrents and 10 downloading + // torrents, and ``active_downloads`` is 4 and ``active_seeds`` is 4, + // there will be 4 seeds active and 4 downloading torrents. If the + // settings are ``active_downloads`` = 2 and ``active_seeds`` = 4, + // then there will be 2 downloading torrents and 4 seeding torrents + // active. Torrents that are not auto managed are not counted against + // these limits. + // + // ``active_checking`` is the limit of number of simultaneous checking + // torrents. + // + // ``active_limit`` is a hard limit on the number of active (auto + // managed) torrents. This limit also applies to slow torrents. + // + // ``active_dht_limit`` is the max number of torrents to announce to + // the DHT. + // + // ``active_tracker_limit`` is the max number of torrents to announce + // to their trackers. + // + // ``active_lsd_limit`` is the max number of torrents to announce to + // the local network over the local service discovery protocol. + // + // You can have more torrents *active*, even though they are not + // announced to the DHT, lsd or their tracker. If some peer knows + // about you for any reason and tries to connect, it will still be + // accepted, unless the torrent is paused, which means it won't accept + // any connections. + active_downloads, + active_seeds, + active_checking, + active_dht_limit, + active_tracker_limit, + active_lsd_limit, + active_limit, + +#if TORRENT_ABI_VERSION == 1 + // ``active_loaded_limit`` is the number of torrents that are allowed + // to be *loaded* at any given time. Note that a torrent can be active + // even though it's not loaded. If an unloaded torrents finds a peer + // that wants to access it, the torrent will be loaded on demand, + // using a user-supplied callback function. If the feature of + // unloading torrents is not enabled, this setting have no effect. If + // this limit is set to 0, it means unlimited. For more information, + // see dynamic-loading-of-torrent-files_. + active_loaded_limit TORRENT_DEPRECATED_ENUM, +#else + deprecated_active_loaded_limit, +#endif + + // ``auto_manage_interval`` is the number of seconds between the + // torrent queue is updated, and rotated. + auto_manage_interval, + + // this is the limit on the time a torrent has been an active seed + // (specified in seconds) before it is considered having met the seed + // limit criteria. See queuing_. + seed_time_limit, + + // ``auto_scrape_interval`` is the number of seconds between scrapes + // of queued torrents (auto managed and paused torrents). Auto managed + // torrents that are paused, are scraped regularly in order to keep + // track of their downloader/seed ratio. This ratio is used to + // determine which torrents to seed and which to pause. + // + // ``auto_scrape_min_interval`` is the minimum number of seconds + // between any automatic scrape (regardless of torrent). In case there + // are a large number of paused auto managed torrents, this puts a + // limit on how often a scrape request is sent. + auto_scrape_interval, + auto_scrape_min_interval, + + // ``max_peerlist_size`` is the maximum number of peers in the list of + // known peers. These peers are not necessarily connected, so this + // number should be much greater than the maximum number of connected + // peers. Peers are evicted from the cache when the list grows passed + // 90% of this limit, and once the size hits the limit, peers are no + // longer added to the list. If this limit is set to 0, there is no + // limit on how many peers we'll keep in the peer list. + // + // ``max_paused_peerlist_size`` is the max peer list size used for + // torrents that are paused. This can be used to save memory for paused + // torrents, since it's not as important for them to keep a large peer + // list. + max_peerlist_size, + max_paused_peerlist_size, + + // this is the minimum allowed announce interval for a tracker. This + // is specified in seconds and is used as a sanity check on what is + // returned from a tracker. It mitigates hammering mis-configured + // trackers. + min_announce_interval, + + // this is the number of seconds a torrent is considered active after + // it was started, regardless of upload and download speed. This is so + // that newly started torrents are not considered inactive until they + // have a fair chance to start downloading. + auto_manage_startup, + + // ``seeding_piece_quota`` is the number of pieces to send to a peer, + // when seeding, before rotating in another peer to the unchoke set. + seeding_piece_quota, + + // ``max_rejects`` is the number of piece requests we will reject in a + // row while a peer is choked before the peer is considered abusive + // and is disconnected. + max_rejects, + + // specifies the buffer sizes set on peer sockets. 0 means the OS + // default (i.e. don't change the buffer sizes). + // The socket buffer sizes are changed using setsockopt() with + // SOL_SOCKET/SO_RCVBUF and SO_SNDBUFFER. + // + // Note that uTP peers share a single UDP socket buffer for each of the + // ``listen_interfaces``, along with DHT and UDP tracker traffic. + // If the buffer size is too small for the combined traffic through the + // socket, packets may be dropped. + recv_socket_buffer_size, + send_socket_buffer_size, + + // the max number of bytes a single peer connection's receive buffer is + // allowed to grow to. + max_peer_recv_buffer_size, + +#if TORRENT_ABI_VERSION == 1 + // ``file_checks_delay_per_block`` is the number of milliseconds to + // sleep in between disk read operations when checking torrents. + // This can be set to higher numbers to slow down the + // rate at which data is read from the disk while checking. This may + // be useful for background tasks that doesn't matter if they take a + // bit longer, as long as they leave disk I/O time for other + // processes. + file_checks_delay_per_block TORRENT_DEPRECATED_ENUM, +#else + deprecated_file_checks_delay_per_block, +#endif + +#if TORRENT_ABI_VERSION <= 2 + // ``read_cache_line_size`` is the number of blocks to read into the + // read cache when a read cache miss occurs. Setting this to 0 is + // essentially the same thing as disabling read cache. The number of + // blocks read into the read cache is always capped by the piece + // boundary. + // + // When a piece in the write cache has ``write_cache_line_size`` + // contiguous blocks in it, they will be flushed. Setting this to 1 + // effectively disables the write cache. + read_cache_line_size, + write_cache_line_size, +#else + deprecated_read_cache_line_size, + deprecated_write_cache_line_size, +#endif + + // ``optimistic_disk_retry`` is the number of seconds from a disk + // write errors occur on a torrent until libtorrent will take it out + // of the upload mode, to test if the error condition has been fixed. + // + // libtorrent will only do this automatically for auto managed + // torrents. + // + // You can explicitly take a torrent out of upload only mode using + // set_upload_mode(). + optimistic_disk_retry, + + // ``max_suggest_pieces`` is the max number of suggested piece indices + // received from a peer that's remembered. If a peer floods suggest + // messages, this limit prevents libtorrent from using too much RAM. + max_suggest_pieces, + + // ``local_service_announce_interval`` is the time between local + // network announces for a torrent. + // This interval is specified in seconds. + local_service_announce_interval, + + // ``dht_announce_interval`` is the number of seconds between + // announcing torrents to the distributed hash table (DHT). + dht_announce_interval, + + // ``udp_tracker_token_expiry`` is the number of seconds libtorrent + // will keep UDP tracker connection tokens around for. This is + // specified to be 60 seconds. The higher this + // value is, the fewer packets have to be sent to the UDP tracker. In + // order for higher values to work, the tracker needs to be configured + // to match the expiration time for tokens. + udp_tracker_token_expiry, + +#if TORRENT_ABI_VERSION == 1 + // ``default_cache_min_age`` is the minimum number of seconds any read + // cache line is kept in the cache. This + // may be greater if ``guided_read_cache`` is enabled. Having a lower + // bound on the time a cache line stays in the cache is an attempt + // to avoid swapping the same pieces in and out of the cache in case + // there is a shortage of spare cache space. + default_cache_min_age TORRENT_DEPRECATED_ENUM, +#else + deprecated_default_cache_min_age, +#endif + + // ``num_optimistic_unchoke_slots`` is the number of optimistic + // unchoke slots to use. + // Having a higher number of optimistic unchoke slots mean you will + // find the good peers faster but with the trade-off to use up more + // bandwidth. 0 means automatic, where libtorrent opens up 20% of your + // allowed upload slots as optimistic unchoke slots. + num_optimistic_unchoke_slots, + +#if TORRENT_ABI_VERSION == 1 + // ``default_est_reciprocation_rate`` is the assumed reciprocation + // rate from peers when using the BitTyrant choker. If set too high, + // you will over-estimate your peers and be + // more altruistic while finding the true reciprocation rate, if it's + // set too low, you'll be too stingy and waste finding the true + // reciprocation rate. + // + // ``increase_est_reciprocation_rate`` specifies how many percent the + // estimated reciprocation rate should be increased by each unchoke + // interval a peer is still choking us back. + // This only applies to the BitTyrant choker. + // + // ``decrease_est_reciprocation_rate`` specifies how many percent the + // estimated reciprocation rate should be decreased by each unchoke + // interval a peer unchokes us. This only applies + // to the BitTyrant choker. + default_est_reciprocation_rate TORRENT_DEPRECATED_ENUM, + increase_est_reciprocation_rate TORRENT_DEPRECATED_ENUM, + decrease_est_reciprocation_rate TORRENT_DEPRECATED_ENUM, +#else + deprecated_default_est_reciprocation_rate, + deprecated_increase_est_reciprocation_rate, + deprecated_decrease_est_reciprocation_rate, +#endif + + // the max number of peers we accept from pex messages from a single + // peer. this limits the number of concurrent peers any of our peers + // claims to be connected to. If they claim to be connected to more + // than this, we'll ignore any peer that exceeds this limit + max_pex_peers, + + // ``tick_interval`` specifies the number of milliseconds between + // internal ticks. This is the frequency with which bandwidth quota is + // distributed to peers. It should not be more than one second (i.e. + // 1000 ms). Setting this to a low value (around 100) means higher + // resolution bandwidth quota distribution, setting it to a higher + // value saves CPU cycles. + tick_interval, + + // ``share_mode_target`` specifies the target share ratio for share + // mode torrents. If set to 3, we'll try to upload 3 + // times as much as we download. Setting this very high, will make it + // very conservative and you might end up not downloading anything + // ever (and not affecting your share ratio). It does not make any + // sense to set this any lower than 2. For instance, if only 3 peers + // need to download the rarest piece, it's impossible to download a + // single piece and upload it more than 3 times. If the + // share_mode_target is set to more than 3, nothing is downloaded. + share_mode_target, + + // ``upload_rate_limit`` and ``download_rate_limit`` sets + // the session-global limits of upload and download rate limits, in + // bytes per second. By default peers on the local network are not rate + // limited. + // + // A value of 0 means unlimited. + // + // For fine grained control over rate limits, including making them apply + // to local peers, see peer-classes_. + upload_rate_limit, + download_rate_limit, +#if TORRENT_ABI_VERSION == 1 + local_upload_rate_limit TORRENT_DEPRECATED_ENUM, + local_download_rate_limit TORRENT_DEPRECATED_ENUM, +#else + deprecated_local_upload_rate_limit, + deprecated_local_download_rate_limit, +#endif + + // the number of bytes per second (on average) the DHT is allowed to send. + // If the incoming requests causes to many bytes to be sent in responses, + // incoming requests will be dropped until the quota has been replenished. + dht_upload_rate_limit, + + // ``unchoke_slots_limit`` is the max number of unchoked peers in the + // session. The number of unchoke slots may be ignored depending on + // what ``choking_algorithm`` is set to. Setting this limit to -1 + // means unlimited, i.e. all peers will always be unchoked. + unchoke_slots_limit, + +#if TORRENT_ABI_VERSION == 1 + // ``half_open_limit`` sets the maximum number of half-open + // connections libtorrent will have when connecting to peers. A + // half-open connection is one where connect() has been called, but + // the connection still hasn't been established (nor failed). Windows + // XP Service Pack 2 sets a default, system wide, limit of the number + // of half-open connections to 10. So, this limit can be used to work + // nicer together with other network applications on that system. The + // default is to have no limit, and passing -1 as the limit, means to + // have no limit. When limiting the number of simultaneous connection + // attempts, peers will be put in a queue waiting for their turn to + // get connected. + half_open_limit TORRENT_DEPRECATED_ENUM, +#else + deprecated_half_open_limit, +#endif + + // ``connections_limit`` sets a global limit on the number of + // connections opened. The number of connections is set to a hard + // minimum of at least two per torrent, so if you set a too low + // connections limit, and open too many torrents, the limit will not + // be met. + connections_limit, + + // ``connections_slack`` is the number of incoming connections + // exceeding the connection limit to accept in order to potentially + // replace existing ones. + connections_slack, + + // ``utp_target_delay`` is the target delay for uTP sockets in + // milliseconds. A high value will make uTP connections more + // aggressive and cause longer queues in the upload bottleneck. It + // cannot be too low, since the noise in the measurements would cause + // it to send too slow. + // ``utp_gain_factor`` is the number of bytes the uTP congestion + // window can increase at the most in one RTT. + // If this is set too high, the congestion controller reacts + // too hard to noise and will not be stable, if it's set too low, it + // will react slow to congestion and not back off as fast. + // + // ``utp_min_timeout`` is the shortest allowed uTP socket timeout, + // specified in milliseconds. The + // timeout depends on the RTT of the connection, but is never smaller + // than this value. A connection times out when every packet in a + // window is lost, or when a packet is lost twice in a row (i.e. the + // resent packet is lost as well). + // + // The shorter the timeout is, the faster the connection will recover + // from this situation, assuming the RTT is low enough. + // ``utp_syn_resends`` is the number of SYN packets that are sent (and + // timed out) before giving up and closing the socket. + // ``utp_num_resends`` is the number of times a packet is sent (and + // lost or timed out) before giving up and closing the connection. + // ``utp_connect_timeout`` is the number of milliseconds of timeout + // for the initial SYN packet for uTP connections. For each timed out + // packet (in a row), the timeout is doubled. ``utp_loss_multiplier`` + // controls how the congestion window is changed when a packet loss is + // experienced. It's specified as a percentage multiplier for + // ``cwnd``. Do not change this value unless you know what you're doing. + // Never set it higher than 100. + utp_target_delay, + utp_gain_factor, + utp_min_timeout, + utp_syn_resends, + utp_fin_resends, + utp_num_resends, + utp_connect_timeout, +#if TORRENT_ABI_VERSION == 1 + utp_delayed_ack TORRENT_DEPRECATED_ENUM, +#else + deprecated_utp_delayed_ack, +#endif + utp_loss_multiplier, + + // The ``mixed_mode_algorithm`` determines how to treat TCP + // connections when there are uTP connections. Since uTP is designed + // to yield to TCP, there's an inherent problem when using swarms that + // have both TCP and uTP connections. If nothing is done, uTP + // connections would often be starved out for bandwidth by the TCP + // connections. This mode is ``prefer_tcp``. The ``peer_proportional`` + // mode simply looks at the current throughput and rate limits all TCP + // connections to their proportional share based on how many of the + // connections are TCP. This works best if uTP connections are not + // rate limited by the global rate limiter (which they aren't by + // default). + mixed_mode_algorithm, + + // ``listen_queue_size`` is the value passed in to listen() for the + // listen socket. It is the number of outstanding incoming connections + // to queue up while we're not actively waiting for a connection to be + // accepted. 5 should be sufficient for any + // normal client. If this is a high performance server which expects + // to receive a lot of connections, or used in a simulator or test, it + // might make sense to raise this number. It will not take affect + // until the ``listen_interfaces`` settings is updated. + listen_queue_size, + + // ``torrent_connect_boost`` is the number of peers to try to connect + // to immediately when the first tracker response is received for a + // torrent. This is a boost to given to new torrents to accelerate + // them starting up. The normal connect scheduler is run once every + // second, this allows peers to be connected immediately instead of + // waiting for the session tick to trigger connections. + // This may not be set higher than 255. + torrent_connect_boost, + + // ``alert_queue_size`` is the maximum number of alerts queued up + // internally. If alerts are not popped, the queue will eventually + // fill up to this level. Once the alert queue is full, additional + // alerts will be dropped, and not delivered to the client. Once the + // client drains the queue, new alerts may be delivered again. In order + // to know that alerts have been dropped, see + // session_handle::dropped_alerts(). + alert_queue_size, + + // ``max_metadata_size`` is the maximum allowed size (in bytes) to be + // received by the metadata extension, i.e. magnet links. + max_metadata_size, + + // ``hashing_threads`` is the number of disk I/O threads to use for + // piece hash verification. These threads are *in addition* to the + // regular disk I/O threads specified by settings_pack::aio_threads. + // These threads are only used for full checking of torrents. The + // hash checking done while downloading are done by the regular disk + // I/O threads. + // The hasher threads do not only compute hashes, but also perform + // the read from disk. On storage optimal for sequential access, + // such as hard drives, this setting should be set to 1, which is + // also the default. + hashing_threads, + + // the number of blocks to keep outstanding at any given time when + // checking torrents. Higher numbers give faster re-checks but uses + // more memory. Specified in number of 16 kiB blocks + checking_mem_usage, + + // if set to > 0, pieces will be announced to other peers before they + // are fully downloaded (and before they are hash checked). The + // intention is to gain 1.5 potential round trip times per downloaded + // piece. When non-zero, this indicates how many milliseconds in + // advance pieces should be announced, before they are expected to be + // completed. + predictive_piece_announce, + + // for some aio back-ends, ``aio_threads`` specifies the number of + // io-threads to use. + aio_threads, + +#if TORRENT_ABI_VERSION == 1 + // for some aio back-ends, ``aio_max`` specifies the max number of + // outstanding jobs. + aio_max TORRENT_DEPRECATED_ENUM, + + // .. note:: This is not implemented + // + // ``network_threads`` is the number of threads to use to call + // ``async_write_some`` (i.e. send) on peer connection sockets. When + // seeding at extremely high rates, this may become a bottleneck, and + // setting this to 2 or more may parallelize that cost. When using SSL + // torrents, all encryption for outgoing traffic is done within the + // socket send functions, and this will help parallelizing the cost of + // SSL encryption as well. + network_threads TORRENT_DEPRECATED_ENUM, + + // ``ssl_listen`` sets the listen port for SSL connections. If this is + // set to 0, no SSL listen port is opened. Otherwise a socket is + // opened on this port. This setting is only taken into account when + // opening the regular listen port, and won't re-open the listen + // socket simply by changing this setting. + ssl_listen TORRENT_DEPRECATED_ENUM, +#else + // hidden + deprecated_aio_max, + deprecated_network_threads, + deprecated_ssl_listen, +#endif + + // ``tracker_backoff`` determines how aggressively to back off from + // retrying failing trackers. This value determines *x* in the + // following formula, determining the number of seconds to wait until + // the next retry: + // + // delay = 5 + 5 * x / 100 * fails^2 + // + // This setting may be useful to make libtorrent more or less + // aggressive in hitting trackers. + tracker_backoff, + + // when a seeding torrent reaches either the share ratio (bytes up / + // bytes down) or the seed time ratio (seconds as seed / seconds as + // downloader) or the seed time limit (seconds as seed) it is + // considered done, and it will leave room for other torrents. These + // are specified as percentages. Torrents that are considered done will + // still be allowed to be seeded, they just won't have priority anymore. + // For more, see queuing_. + share_ratio_limit, + seed_time_ratio_limit, + + // peer_turnover is the percentage of peers to disconnect every + // turnover peer_turnover_interval (if we're at the peer limit), this + // is specified in percent when we are connected to more than limit * + // peer_turnover_cutoff peers disconnect peer_turnover fraction of the + // peers. It is specified in percent peer_turnover_interval is the + // interval (in seconds) between optimistic disconnects if the + // disconnects happen and how many peers are disconnected is + // controlled by peer_turnover and peer_turnover_cutoff + peer_turnover, + peer_turnover_cutoff, + peer_turnover_interval, + + // this setting controls the priority of downloading torrents over + // seeding or finished torrents when it comes to making peer + // connections. Peer connections are throttled by the connection_speed + // and the half-open connection limit. This makes peer connections a + // limited resource. Torrents that still have pieces to download are + // prioritized by default, to avoid having many seeding torrents use + // most of the connection attempts and only give one peer every now + // and then to the downloading torrent. libtorrent will loop over the + // downloading torrents to connect a peer each, and every n:th + // connection attempt, a finished torrent is picked to be allowed to + // connect to a peer. This setting controls n. + connect_seed_every_n_download, + + // the max number of bytes to allow an HTTP response to be when + // announcing to trackers or downloading .torrent files via the + // ``url`` provided in ``add_torrent_params``. + max_http_recv_buffer_size, + + // if binding to a specific port fails, should the port be incremented + // by one and tried again? This setting specifies how many times to + // retry a failed port bind + max_retry_port_bind, + + // a bitmask combining flags from alert_category_t defining which + // kinds of alerts to receive + alert_mask, + + // control the settings for incoming and outgoing connections + // respectively. see enc_policy enum for the available options. + // Keep in mind that protocol encryption degrades performance in + // several respects: + // + // 1. It prevents "zero copy" disk buffers being sent to peers, since + // each peer needs to mutate the data (i.e. encrypt it) the data + // must be copied per peer connection rather than sending the same + // buffer to multiple peers. + // 2. The encryption itself requires more CPU than plain bittorrent + // protocol. The highest cost is the Diffie Hellman exchange on + // connection setup. + // 3. The encryption handshake adds several round-trips to the + // connection setup, and delays transferring data. + out_enc_policy, + in_enc_policy, + + // determines the encryption level of the connections. This setting + // will adjust which encryption scheme is offered to the other peer, + // as well as which encryption scheme is selected by the client. See + // enc_level enum for options. + allowed_enc_level, + + // the download and upload rate limits for a torrent to be considered + // active by the queuing mechanism. A torrent whose download rate is + // less than ``inactive_down_rate`` and whose upload rate is less than + // ``inactive_up_rate`` for ``auto_manage_startup`` seconds, is + // considered inactive, and another queued torrent may be started. + // This logic is disabled if ``dont_count_slow_torrents`` is false. + inactive_down_rate, + inactive_up_rate, + + // proxy to use. see proxy_type_t. + proxy_type, + + // the port of the proxy server + proxy_port, + + // sets the i2p_ SAM bridge port to connect to. set the hostname with + // the ``i2p_hostname`` setting. + // + // .. _i2p: http://www.i2p2.de + i2p_port, + +#if TORRENT_ABI_VERSION == 1 + // this determines the max number of volatile disk cache blocks. If the + // number of volatile blocks exceed this limit, other volatile blocks + // will start to be evicted. A disk cache block is volatile if it has + // low priority, and should be one of the first blocks to be evicted + // under pressure. For instance, blocks pulled into the cache as the + // result of calculating a piece hash are volatile. These blocks don't + // represent potential interest among peers, so the value of keeping + // them in the cache is limited. + cache_size_volatile, +#else + deprecated_cache_size_volatile, +#endif + + // The maximum request range of an url seed in bytes. This value + // defines the largest possible sequential web seed request. Lower values + // are possible but will be ignored if they are lower then piece size. + // This value should be related to your download speed to prevent + // libtorrent from creating too many expensive http requests per + // second. You can select a value as high as you want but keep in mind + // that libtorrent can't create parallel requests if the first request + // did already select the whole file. + // If you combine bittorrent seeds with web seeds and pick strategies + // like rarest first you may find your web seed requests split into + // smaller parts because we don't download already picked pieces + // twice. + urlseed_max_request_bytes, + + // time to wait until a new retry of a web seed name lookup + web_seed_name_lookup_retry, + + // the number of seconds between closing the file opened the longest + // ago. 0 means to disable the feature. The purpose of this is to + // periodically close files to trigger the operating system flushing + // disk cache. Specifically it has been observed to be required on + // windows to not have the disk cache grow indefinitely. + // This defaults to 240 seconds on windows, and disabled on other + // systems. + close_file_interval, + + // When uTP experiences packet loss, it will reduce the congestion + // window, and not reduce it again for this many milliseconds, even if + // experiencing another lost packet. + utp_cwnd_reduce_timer, + + // the max number of web seeds to have connected per torrent at any + // given time. + max_web_seed_connections, + + // the number of seconds before the internal host name resolver + // considers a cache value timed out, negative values are interpreted + // as zero. + resolver_cache_timeout, + + // specify the not-sent low watermark for socket send buffers. This + // corresponds to the, Linux-specific, ``TCP_NOTSENT_LOWAT`` TCP socket + // option. + send_not_sent_low_watermark, + + // the rate based choker compares the upload rate to peers against a + // threshold that increases proportionally by its size for every + // peer it visits, visiting peers in decreasing upload rate. The + // number of upload slots is determined by the number of peers whose + // upload rate exceeds the threshold. This option sets the start + // value for this threshold. A higher value leads to fewer unchoke + // slots, a lower value leads to more. + rate_choker_initial_threshold, + + // The expiration time of UPnP port-mappings, specified in seconds. 0 + // means permanent lease. Some routers do not support expiration times + // on port-maps (nor correctly returning an error indicating lack of + // support). In those cases, set this to 0. Otherwise, don't set it any + // lower than 5 minutes. + upnp_lease_duration, + + // limits the number of concurrent HTTP tracker announces. Once the + // limit is hit, tracker requests are queued and issued when an + // outstanding announce completes. + max_concurrent_http_announces, + + // the maximum number of peers to send in a reply to ``get_peers`` + dht_max_peers_reply, + + // the number of concurrent search request the node will send when + // announcing and refreshing the routing table. This parameter is called + // alpha in the kademlia paper + dht_search_branching, + + // the maximum number of failed tries to contact a node before it is + // removed from the routing table. If there are known working nodes that + // are ready to replace a failing node, it will be replaced immediately, + // this limit is only used to clear out nodes that don't have any node + // that can replace them. + dht_max_fail_count, + + // the total number of torrents to track from the DHT. This is simply an + // upper limit to make sure malicious DHT nodes cannot make us allocate + // an unbounded amount of memory. + dht_max_torrents, + + // max number of items the DHT will store + dht_max_dht_items, + + // the max number of peers to store per torrent (for the DHT) + dht_max_peers, + + // the max number of torrents to return in a torrent search query to the + // DHT + dht_max_torrent_search_reply, + + // the number of seconds a DHT node is banned if it exceeds the rate + // limit. The rate limit is averaged over 10 seconds to allow for bursts + // above the limit. + dht_block_timeout, + + // the max number of packets per second a DHT node is allowed to send + // without getting banned. + dht_block_ratelimit, + + // the number of seconds a immutable/mutable item will be expired. + // default is 0, means never expires. + dht_item_lifetime, + + // the info-hashes sample recomputation interval (in seconds). + // The node will precompute a subset of the tracked info-hashes and return + // that instead of calculating it upon each request. The permissible range + // is between 0 and 21600 seconds (inclusive). + dht_sample_infohashes_interval, + + // the maximum number of elements in the sampled subset of info-hashes. + // If this number is too big, expect the DHT storage implementations + // to clamp it in order to allow UDP packets go through + dht_max_infohashes_sample_count, + + // ``max_piece_count`` is the maximum allowed number of pieces in + // metadata received via magnet links. Loading large torrents (with + // more pieces than the default limit) may also require passing in + // a higher limit to read_resume_data() and + // torrent_info::parse_info_section(), if those are used. + max_piece_count, + + // when receiving metadata (torrent file) from peers, this is the + // max number of bencoded tokens we're willing to parse. This limit + // is meant to prevent DoS attacks on peers. For very large + // torrents, this limit may have to be raised. + metadata_token_limit, + + // controls whether disk writes will be made through a memory mapped + // file or via normal write calls. This only affects the + // mmap_disk_io. When saving to a non-local drive (network share, + // NFS or NAS) using memory mapped files is most likely inferior. + // When writing to a local SSD (especially in DAX mode) using memory + // mapped files likely gives the best performance. + // The values for this setting are specified as mmap_write_mode_t. + disk_write_mode, + + // when using mmap_disk_io, files smaller than this number of blocks + // will not be memory mapped, but will use normal pread/pwrite + // operations. This file size limit is specified in 16 kiB blocks. + mmap_file_size_cutoff, + + // Configures the SAM session + // quantity of I2P inbound and outbound tunnels [1..16]. + // number of hops for I2P inbound and outbound tunnels [0..7] + // Changing these will not trigger a reconnect to the SAM bridge, + // they will take effect the next time the SAM connection is + // re-established (by restarting or changing i2p_hostname or + // i2p_port). + i2p_inbound_quantity, + i2p_outbound_quantity, + i2p_inbound_length, + i2p_outbound_length, + + // ``announce_port`` is the port passed along as the ``port`` parameter + // to remote trackers such as HTTP or DHT. This setting does not affect + // the effective listening port nor local service discovery announcements. + // If left as zero (default), the listening port value is used. + // + // .. note:: + // This setting is only meant for very special cases where a + // seed's listening port differs from the external port. As an + // example, if a local proxy is used and that the proxy supports + // reverse tunnels through NAT-PMP, the tracker must connect to + // the external NAT-PMP port (configured using ``announce_port``) + // instead of the actual local listening port. + announce_port, + + max_int_setting_internal + }; + + // hidden + constexpr static int num_string_settings = int(max_string_setting_internal) - int(string_type_base); + constexpr static int num_bool_settings = int(max_bool_setting_internal) - int(bool_type_base); + constexpr static int num_int_settings = int(max_int_setting_internal) - int(int_type_base); + + enum mmap_write_mode_t : std::uint8_t + { + // disable writing to disk via mmap, always use normal write calls + always_pwrite = 0, + + // prefer using memory mapped files for disk writes (at least for + // large files where it might make sense) + always_mmap_write, + + // determine whether to use pwrite or memory mapped files for disk + // writes depending on the kind of storage behind the save path + auto_mmap_write, + }; + + enum suggest_mode_t : std::uint8_t { no_piece_suggestions = 0, suggest_read_cache = 1 }; + + enum choking_algorithm_t : std::uint8_t + { + // This is the traditional choker with a fixed number of unchoke + // slots (as specified by settings_pack::unchoke_slots_limit). + fixed_slots_choker = 0, + + // This opens up unchoke slots based on the upload rate achieved to + // peers. The more slots that are opened, the marginal upload rate + // required to open up another slot increases. Configure the initial + // threshold with settings_pack::rate_choker_initial_threshold. + // + // For more information, see `rate based choking`_. + rate_based_choker = 2, +#if TORRENT_ABI_VERSION == 1 + bittyrant_choker TORRENT_DEPRECATED_ENUM = 3 +#else + deprecated_bittyrant_choker = 3 +#endif + }; + + enum seed_choking_algorithm_t : std::uint8_t + { + // which round-robins the peers that are unchoked + // when seeding. This distributes the upload bandwidth uniformly and + // fairly. It minimizes the ability for a peer to download everything + // without redistributing it. + round_robin, + + // unchokes the peers we can send to the fastest. This might be a + // bit more reliable in utilizing all available capacity. + fastest_upload, + + // prioritizes peers who have just started or are + // just about to finish the download. The intention is to force + // peers in the middle of the download to trade with each other. + // This does not just take into account the pieces a peer is + // reporting having downloaded, but also the pieces we have sent + // to it. + anti_leech + }; + + enum io_buffer_mode_t : std::uint8_t + { + enable_os_cache = 0, +#if TORRENT_ABI_VERSION == 1 + disable_os_cache_for_aligned_files TORRENT_DEPRECATED_ENUM = 2, +#else + deprecated_disable_os_cache_for_aligned_files = 1, +#endif + disable_os_cache = 2, + + write_through = 3, + }; + + enum bandwidth_mixed_algo_t : std::uint8_t + { + // disables the mixed mode bandwidth balancing + prefer_tcp = 0, + + // does not throttle uTP, throttles TCP to the same proportion + // of throughput as there are TCP connections + peer_proportional = 1 + }; + + // the encoding policy options for use with + // settings_pack::out_enc_policy and settings_pack::in_enc_policy. + enum enc_policy : std::uint8_t + { + // Only encrypted connections are allowed. Incoming connections that + // are not encrypted are closed and if the encrypted outgoing + // connection fails, a non-encrypted retry will not be made. + pe_forced, + + // encrypted connections are enabled, but non-encrypted connections + // are allowed. An incoming non-encrypted connection will be accepted, + // and if an outgoing encrypted connection fails, a non- encrypted + // connection will be tried. + pe_enabled, + + // only non-encrypted connections are allowed. + pe_disabled + }; + + // the encryption levels, to be used with + // settings_pack::allowed_enc_level. + enum enc_level : std::uint8_t + { + // use only plain text encryption + pe_plaintext = 1, + // use only RC4 encryption + pe_rc4 = 2, + // allow both + pe_both = 3 + }; + + enum proxy_type_t : std::uint8_t + { + // No proxy server is used and all other fields are ignored. + none, + + // The server is assumed to be a `SOCKS4 server`_ that requires a + // username. + // + // .. _`SOCKS4 server`: http://www.ufasoft.com/doc/socks4_protocol.htm + socks4, + + // The server is assumed to be a SOCKS5 server (`RFC 1928`_) that does + // not require any authentication. The username and password are + // ignored. + // + // .. _`RFC 1928`: http://www.faqs.org/rfcs/rfc1928.html + socks5, + + // The server is assumed to be a SOCKS5 server that supports plain + // text username and password authentication (`RFC 1929`_). The + // username and password specified may be sent to the proxy if it + // requires. + // + // .. _`RFC 1929`: http://www.faqs.org/rfcs/rfc1929.html + socks5_pw, + + // The server is assumed to be an HTTP proxy. If the transport used + // for the connection is non-HTTP, the server is assumed to support + // the CONNECT_ method. i.e. for web seeds and HTTP trackers, a plain + // proxy will suffice. The proxy is assumed to not require + // authorization. The username and password will not be used. + // + // .. _CONNECT: http://tools.ietf.org/html/draft-luotonen-web-proxy-tunneling-01 + http, + + // The server is assumed to be an HTTP proxy that requires user + // authorization. The username and password will be sent to the proxy. + http_pw, + +#if TORRENT_USE_I2P + // internal + // This is used internally to communicate with the + // http_tracker_connection. To configure an i2p SAM bridge, set + // i2p_hostname and i2p_port. + i2p_proxy +#endif + }; + private: + + std::vector> m_strings; + std::vector> m_ints; + std::vector> m_bools; + }; +} + +#endif diff --git a/include/libtorrent/sha1.hpp b/include/libtorrent/sha1.hpp new file mode 100644 index 0000000..185d0ee --- /dev/null +++ b/include/libtorrent/sha1.hpp @@ -0,0 +1,43 @@ +/* +SHA-1 C++ conversion + +original version: + +SHA-1 in C +By Steve Reid +100% Public Domain + +changelog at the end of sha1.cpp +*/ + +#ifndef TORRENT_SHA1_HPP_INCLUDED +#define TORRENT_SHA1_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if !defined TORRENT_USE_LIBGCRYPT \ + && !TORRENT_USE_COMMONCRYPTO \ + && !TORRENT_USE_CNG \ + && !TORRENT_USE_CRYPTOAPI \ + && !defined TORRENT_USE_LIBCRYPTO + +#include + +namespace libtorrent { + + struct sha1_ctx + { + std::uint32_t state[5]; + std::uint32_t count[2]; + std::uint8_t buffer[64]; + }; + + // we don't want these to clash with openssl's libcrypto + TORRENT_EXTRA_EXPORT void SHA1_init(sha1_ctx* context); + TORRENT_EXTRA_EXPORT void SHA1_update(sha1_ctx* context + , std::uint8_t const* data, size_t len); + TORRENT_EXTRA_EXPORT void SHA1_final(std::uint8_t* digest, sha1_ctx* context); +} + +#endif +#endif diff --git a/include/libtorrent/sha1_hash.hpp b/include/libtorrent/sha1_hash.hpp new file mode 100644 index 0000000..2c1bb83 --- /dev/null +++ b/include/libtorrent/sha1_hash.hpp @@ -0,0 +1,316 @@ +/* + +Copyright (c) 2013-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, Steven Siloti +Copyright (c) 2020, Mike Tzou +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SHA1_HASH_HPP_INCLUDED +#define TORRENT_SHA1_HASH_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/byteswap.hpp" +#include "libtorrent/aux_/ffs.hpp" +#include "libtorrent/span.hpp" + +#if TORRENT_USE_IOSTREAM +#include +#endif // TORRENT_USE_IOSTREAM + +namespace libtorrent { + + // This type holds an N digest or any other kind of N bits + // sequence. It implements a number of convenience functions, such + // as bit operations, comparison operators etc. + // + // This data structure is 32 bits aligned, like it's the case for + // each SHA-N specification. + template + class digest32 + { + static_assert(N % 32 == 0, "N must be a multiple of 32"); + static constexpr std::ptrdiff_t number_size = N / 32; + static constexpr int bits_in_byte = 8; + public: + + using difference_type = std::ptrdiff_t; + using index_type = std::ptrdiff_t; + + // the size of the hash in bytes + static constexpr difference_type size() noexcept { return N / bits_in_byte; } + + // constructs an all-zero digest + digest32() noexcept { clear(); } + + digest32(digest32 const&) noexcept = default; + digest32& operator=(digest32 const&) noexcept = default; + + // returns an all-F digest. i.e. the maximum value + // representable by an N bit number (N/8 bytes). This is + // a static member function. + static digest32 (max)() noexcept + { + digest32 ret; + ret.m_number.fill(0xffffffff); + return ret; + } + + // returns an all-zero digest. i.e. the minimum value + // representable by an N bit number (N/8 bytes). This is + // a static member function. + static digest32 (min)() noexcept + { + digest32 ret; + // all bits are already 0 + return ret; + } + + // copies N/8 bytes from the pointer provided, into the digest. + // The passed in string MUST be at least N/8 bytes. 0-terminators + // are ignored, ``s`` is treated like a raw memory buffer. + explicit digest32(char const* s) noexcept + { + if (s == nullptr) clear(); + else std::memcpy(m_number.data(), s, size()); + } +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + explicit digest32(std::string const& s) + { + assign(s.data()); + } +#endif + explicit digest32(span s) noexcept + { + assign(s); + } + void assign(span s) noexcept + { + TORRENT_ASSERT(s.size() >= N / bits_in_byte); + auto const sl = s.size() < size() ? s.size() : size(); + std::memcpy(m_number.data(), s.data(), static_cast(sl)); + } + void assign(char const* str) noexcept { std::memcpy(m_number.data(), str, size()); } + + char const* data() const noexcept { return reinterpret_cast(m_number.data()); } + char* data() noexcept { return reinterpret_cast(m_number.data()); } + + // set the digest to all zeros. + void clear() noexcept { m_number.fill(0); } + + // return true if the digest is all zero. + bool is_all_zeros() const noexcept + { + return std::all_of(m_number.begin(), m_number.end() + , [](std::uint32_t v) { return v == 0; }); + } + + // shift left or right ``n`` bits. + digest32& operator<<=(int n) & noexcept; + digest32& operator>>=(int n) & noexcept; + + // standard comparison operators + bool operator==(digest32 const& n) const noexcept + { + return std::equal(n.m_number.begin(), n.m_number.end(), m_number.begin()); + } + bool operator!=(digest32 const& n) const noexcept + { + return !std::equal(n.m_number.begin(), n.m_number.end(), m_number.begin()); + } + bool operator<(digest32 const& n) const noexcept + { + for (auto const v : boost::combine(m_number, n.m_number)) + { + std::uint32_t const lhs = aux::network_to_host(boost::get<0>(v)); + std::uint32_t const rhs = aux::network_to_host(boost::get<1>(v)); + if (lhs < rhs) return true; + if (lhs > rhs) return false; + } + return false; + } + + int count_leading_zeroes() const noexcept + { + return aux::count_leading_zeros(m_number); + } + + // returns a bit-wise negated copy of the digest + digest32 operator~() const noexcept + { + digest32 ret; + for (auto const v : boost::combine(m_number, ret.m_number)) + boost::get<1>(v) = ~boost::get<0>(v); + return ret; + } + + // returns the bit-wise XOR of the two digests. + digest32 operator^(digest32 const& n) const noexcept + { + digest32 ret = *this; + ret ^= n; + return ret; + } + + // in-place bit-wise XOR with the passed in digest. + digest32& operator^=(digest32 const& n) & noexcept + { + for (auto const v : boost::combine(m_number, n.m_number)) + boost::get<0>(v) ^= boost::get<1>(v); + return *this; + } + + // returns the bit-wise AND of the two digests. + digest32 operator&(digest32 const& n) const noexcept + { + digest32 ret = *this; + ret &= n; + return ret; + } + + // in-place bit-wise AND of the passed in digest + digest32& operator&=(digest32 const& n) & noexcept + { + for (auto const v : boost::combine(m_number, n.m_number)) + boost::get<0>(v) &= boost::get<1>(v); + return *this; + } + + // in-place bit-wise OR of the two digests. + digest32& operator|=(digest32 const& n) & noexcept + { + for (auto const v : boost::combine(m_number, n.m_number)) + boost::get<0>(v) |= boost::get<1>(v); + return *this; + } + + // accessors for specific bytes + std::uint8_t& operator[](index_type i) noexcept + { + TORRENT_ASSERT(i < size()); + return reinterpret_cast(m_number.data())[i]; + } + std::uint8_t const& operator[](index_type i) const noexcept + { + TORRENT_ASSERT(i < size()); + return reinterpret_cast(m_number.data())[i]; + } + + using const_iterator = std::uint8_t const*; + using iterator = std::uint8_t*; + + // start and end iterators for the hash. The value type + // of these iterators is ``std::uint8_t``. + const_iterator begin() const + { return reinterpret_cast(m_number.data()); } + const_iterator end() const + { return reinterpret_cast(m_number.data()) + size(); } + iterator begin() + { return reinterpret_cast(m_number.data()); } + iterator end() + { return reinterpret_cast(m_number.data()) + size(); } + + // return a copy of the N/8 bytes representing the digest as a std::string. + // It's still a binary string with N/8 binary characters. + std::string to_string() const + { + return std::string(reinterpret_cast(m_number.data()), size()); + } + +#if TORRENT_USE_IOSTREAM + // print a sha1_hash object to an ostream as 40 hexadecimal digits + friend std::ostream& operator<<(std::ostream& os, digest32 const& val) + { val.stream_out(os); return os; } + + // read 40 hexadecimal digits from an istream into a sha1_hash + friend std::istream& operator>>(std::istream& is, digest32& val) + { val.stream_in(is); return is; } +#endif // TORRENT_USE_IOSTREAM + + private: + +#if TORRENT_USE_IOSTREAM + void stream_in(std::istream& is); + void stream_out(std::ostream& os) const; +#endif // TORRENT_USE_IOSTREAM + + std::array m_number; + }; + + // This type holds a SHA-1 digest or any other kind of 20 byte + // sequence. It implements a number of convenience functions, such + // as bit operations, comparison operators etc. + // + // In libtorrent it is primarily used to hold info-hashes, piece-hashes, + // peer IDs, node IDs etc. + using sha1_hash = digest32<160>; + using sha256_hash = digest32<256>; + +#if TORRENT_USE_IOSTREAM + extern template void digest32<160>::stream_out(std::ostream&) const; + extern template void digest32<256>::stream_out(std::ostream&) const; + extern template void digest32<160>::stream_in(std::istream&); + extern template void digest32<256>::stream_in(std::istream&); +#endif // TORRENT_USE_IOSTREAM + + extern template digest32<160>& digest32<160>::operator<<=(int) & noexcept; + extern template digest32<256>& digest32<256>::operator<<=(int) & noexcept; + extern template digest32<160>& digest32<160>::operator>>=(int) & noexcept; + extern template digest32<256>& digest32<256>::operator>>=(int) & noexcept; +} + +namespace std { + template + struct hash> + { + std::size_t operator()(libtorrent::digest32 const& k) const + { + std::size_t ret; + static_assert(N >= sizeof(ret) * 8, "hash is not defined for small digests"); + // this is OK because digest32 is already a hash + std::memcpy(&ret, k.data(), sizeof(ret)); + return ret; + } + }; +} + +#endif // TORRENT_SHA1_HASH_HPP_INCLUDED diff --git a/include/libtorrent/sha256.hpp b/include/libtorrent/sha256.hpp new file mode 100644 index 0000000..df96d26 --- /dev/null +++ b/include/libtorrent/sha256.hpp @@ -0,0 +1,33 @@ +// SHA-256. Adapted from LibTomCrypt. This code is Public Domain + +#ifndef TORRENT_SHA256_HPP_INCLUDED +#define TORRENT_SHA256_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if !defined TORRENT_USE_LIBGCRYPT \ + && !TORRENT_USE_COMMONCRYPTO \ + && !TORRENT_USE_CNG \ + && !TORRENT_USE_CRYPTOAPI_SHA_512 \ + && !defined TORRENT_USE_LIBCRYPTO + +#include + +namespace libtorrent { + + struct sha256_ctx + { + std::uint64_t length; + std::uint32_t state[8]; + std::uint32_t curlen; + std::uint8_t buf[64]; + }; + + TORRENT_EXTRA_EXPORT void SHA256_init(sha256_ctx& md); + TORRENT_EXTRA_EXPORT void SHA256_update(sha256_ctx& md + , std::uint8_t const* in, size_t len); + TORRENT_EXTRA_EXPORT void SHA256_final(std::uint8_t* digest, sha256_ctx& md); +} + +#endif +#endif diff --git a/include/libtorrent/sliding_average.hpp b/include/libtorrent/sliding_average.hpp new file mode 100644 index 0000000..b2c813f --- /dev/null +++ b/include/libtorrent/sliding_average.hpp @@ -0,0 +1,93 @@ +/* + +Copyright (c) 2010, 2014, 2016-2019, Arvid Norberg +Copyright (c) 2019, Amir Abrams +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SLIDING_AVERAGE_HPP_INCLUDED +#define TORRENT_SLIDING_AVERAGE_HPP_INCLUDED + +#include +#include // for std::abs +#include +#include // for is_integral + +#include "libtorrent/assert.hpp" + +namespace libtorrent { + +// an exponential moving average accumulator. Add samples to it and it keeps +// track of a moving mean value and an average deviation +template +struct sliding_average +{ + static_assert(std::is_integral::value, "template argument must be integral"); + + sliding_average(): m_mean(0), m_average_deviation(0), m_num_samples(0) {} + sliding_average(sliding_average const&) = default; + sliding_average& operator=(sliding_average const&) = default; + + void add_sample(Int s) + { + TORRENT_ASSERT(s < std::numeric_limits::max() / 64); + // fixed point + s *= 64; + Int const deviation = (m_num_samples > 0) ? std::abs(m_mean - s) : 0; + + if (m_num_samples < inverted_gain) + ++m_num_samples; + + m_mean += (s - m_mean) / m_num_samples; + + if (m_num_samples > 1) { + // the exact same thing for deviation off the mean except -1 on + // the samples, because the number of deviation samples always lags + // behind by 1 (you need to actual samples to have a single deviation + // sample). + m_average_deviation += (deviation - m_average_deviation) / (m_num_samples - 1); + } + } + + Int mean() const { return m_num_samples > 0 ? (m_mean + 32) / 64 : 0; } + Int avg_deviation() const { return m_num_samples > 1 ? (m_average_deviation + 32) / 64 : 0; } + int num_samples() const { return m_num_samples; } + +private: + // both of these are fixed point values (* 64) + Int m_mean = 0; + Int m_average_deviation = 0; + // the number of samples we have received, but no more than inverted_gain + // this is the effective inverted_gain + int m_num_samples = 0; +}; + +} + +#endif diff --git a/include/libtorrent/socket.hpp b/include/libtorrent/socket.hpp new file mode 100644 index 0000000..a4e046b --- /dev/null +++ b/include/libtorrent/socket.hpp @@ -0,0 +1,298 @@ +/* + +Copyright (c) 2003-2004, 2006-2010, 2012, 2014-2022, Arvid Norberg +Copyright (c) 2017, Alden Torres +Copyright (c) 2018, Alexandre Janniaux +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SOCKET_HPP_INCLUDED +#define TORRENT_SOCKET_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +// if building as Objective C++, asio's template +// parameters Protocol has to be renamed to avoid +// colliding with keywords + +#ifdef __OBJC__ +#define Protocol Protocol_ +#endif + +#if defined TORRENT_WINDOWS || defined TORRENT_CYGWIN +// asio assumes that the windows error codes are defined already +#include +#endif + +#include +#include +#include +#include + +#ifdef __OBJC__ +#undef Protocol +#endif + +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#endif + +#if TORRENT_USE_NETLINK +#include +#ifndef SOL_NETLINK +#define SOL_NETLINK 270 +#endif + +// NETLINK_NO_ENOBUFS exists at least since android 2.3, but is not exposed +#if defined TORRENT_ANDROID && !defined NETLINK_NO_ENOBUFS +#define NETLINK_NO_ENOBUFS 5 +#endif +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + +#if defined TORRENT_BUILD_SIMULATOR +struct tcp : sim::asio::ip::tcp { + tcp(sim::asio::ip::tcp const& p) : sim::asio::ip::tcp(p) {} // NOLINT + using socket = aux::noexcept_move_only; +}; +struct udp : sim::asio::ip::udp { + udp(sim::asio::ip::udp const& p) : sim::asio::ip::udp(p) {} // NOLINT + using socket = aux::noexcept_move_only; +}; + using sim::asio::async_write; + using sim::asio::async_read; + using true_tcp_socket = sim::asio::ip::tcp::socket; +#else +struct tcp : boost::asio::ip::tcp { + tcp(boost::asio::ip::tcp const& p) : boost::asio::ip::tcp(p) {} // NOLINT + using socket = aux::noexcept_move_only; +}; +struct udp : boost::asio::ip::udp { + udp(boost::asio::ip::udp const& p) : boost::asio::ip::udp(p) {} // NOLINT + using socket = aux::noexcept_move_only; +}; + using boost::asio::async_write; + using boost::asio::async_read; + using true_tcp_socket = boost::asio::ip::tcp::socket; +#endif + + // internal + inline udp::endpoint make_udp(tcp::endpoint const ep) + { return {ep.address(), ep.port()}; } + + // internal + inline tcp::endpoint make_tcp(udp::endpoint const ep) + { return {ep.address(), ep.port()}; } + +#ifdef TORRENT_WINDOWS + +#ifndef PROTECTION_LEVEL_UNRESTRICTED +#define PROTECTION_LEVEL_UNRESTRICTED 10 +#endif + +#ifndef IPV6_PROTECTION_LEVEL +#define IPV6_PROTECTION_LEVEL 30 +#endif + + struct v6_protection_level + { + explicit v6_protection_level(int level): m_value(level) {} + template + int level(Protocol const&) const { return IPPROTO_IPV6; } + template + int name(Protocol const&) const { return IPV6_PROTECTION_LEVEL; } + template + int const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; + + struct exclusive_address_use + { + explicit exclusive_address_use(int enable): m_value(enable) {} + template + int level(Protocol const&) const { return SOL_SOCKET; } + template + int name(Protocol const&) const { return SO_EXCLUSIVEADDRUSE; } + template + int const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; +#endif // TORRENT_WINDOWS + +#ifdef IPV6_TCLASS + struct traffic_class + { + explicit traffic_class(int val): m_value(val) {} + template + int level(Protocol const&) const { return IPPROTO_IPV6; } + template + int name(Protocol const&) const { return IPV6_TCLASS; } + template + int const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; +#endif + + struct type_of_service + { +#ifdef _WIN32 + using tos_t = DWORD; +#else + using tos_t = int; +#endif + explicit type_of_service(tos_t const val) : m_value(tos_t(val)) {} + template + int level(Protocol const&) const { return IPPROTO_IP; } + template + int name(Protocol const&) const { return IP_TOS; } + template + tos_t const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + tos_t m_value; + }; + +#ifdef IP_DSCP_TRAFFIC_TYPE + struct dscp_traffic_type + { + explicit dscp_traffic_type(DWORD val) : m_value(val) {} + template + int level(Protocol const&) const { return IP_DSCP_TRAFFIC_TYPE; } + template + int name(Protocol const&) const { return DSCP_TRAFFIC_TYPE; } + template + DWORD const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + DWORD m_value; + }; +#endif + +#if defined IP_DONTFRAG || defined IP_MTU_DISCOVER || defined IP_DONTFRAGMENT +#define TORRENT_HAS_DONT_FRAGMENT +#endif + +#ifdef TORRENT_HAS_DONT_FRAGMENT + + // the order of these preprocessor tests matters. Windows defines both + // IP_DONTFRAGMENT and IP_MTU_DISCOVER, but the latter is not supported + // in general, the simple option of just setting the DF bit is preferred, if + // it's available +#if defined IP_DONTFRAG || defined IP_DONTFRAGMENT + + struct dont_fragment + { + explicit dont_fragment(bool val) : m_value(val) {} + template + int level(Protocol const&) const { return IPPROTO_IP; } + template + int name(Protocol const&) const +#if defined IP_DONTFRAG + { return IP_DONTFRAG; } +#else // defined IP_DONTFRAGMENT + { return IP_DONTFRAGMENT; } +#endif + template + int const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; + +#else + + // this is the fallback mechanism using the IP_MTU_DISCOVER option, which + // does a little bit more than we want, it makes the kernel track an estimate + // of the MTU and rejects packets immediately if they are believed to exceed + // it. + struct dont_fragment + { + explicit dont_fragment(bool val) + : m_value(val ? IP_PMTUDISC_PROBE : IP_PMTUDISC_DONT) {} + template + int level(Protocol const&) const { return IPPROTO_IP; } + template + int name(Protocol const&) const { return IP_MTU_DISCOVER; } + template + int const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; + +#endif // IP_DONTFRAG vs. IP_MTU_DISCOVER + +#endif // TORRENT_HAS_DONT_FRAGMENT + +#if TORRENT_USE_NETLINK + struct no_enobufs + { + explicit no_enobufs(bool val) : m_value(val) {} + template + int level(Protocol const&) const { return SOL_NETLINK; } + template + int name(Protocol const&) const { return NETLINK_NO_ENOBUFS; } + template + int const* data(Protocol const&) const { return &m_value; } + template + std::size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; +#endif // TORRENT_USE_NETLINK + +#ifdef TCP_NOTSENT_LOWAT + struct tcp_notsent_lowat + { + explicit tcp_notsent_lowat(int val) : m_value(val) {} + template + int level(Protocol const&) const { return IPPROTO_TCP; } + template + int name(Protocol const&) const { return TCP_NOTSENT_LOWAT; } + template + int const* data(Protocol const&) const { return &m_value; } + template + std::size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; +#endif +} + +#endif // TORRENT_SOCKET_HPP_INCLUDED diff --git a/include/libtorrent/socket_io.hpp b/include/libtorrent/socket_io.hpp new file mode 100644 index 0000000..85474f8 --- /dev/null +++ b/include/libtorrent/socket_io.hpp @@ -0,0 +1,146 @@ +/* + +Copyright (c) 2009, 2013-2019, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2016, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SOCKET_IO_HPP_INCLUDED +#define TORRENT_SOCKET_IO_HPP_INCLUDED + +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/string_view.hpp" +#include + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT std::string print_address(address const& addr); + TORRENT_EXTRA_EXPORT std::string print_endpoint(address const& addr, int port); + TORRENT_EXTRA_EXPORT std::string print_endpoint(tcp::endpoint const& ep); + TORRENT_EXTRA_EXPORT std::string print_endpoint(udp::endpoint const& ep); + TORRENT_EXTRA_EXPORT tcp::endpoint parse_endpoint(string_view str, error_code& ec); + + TORRENT_EXTRA_EXPORT std::string address_to_bytes(address const& a); + TORRENT_EXTRA_EXPORT std::string endpoint_to_bytes(udp::endpoint const& ep); + TORRENT_EXTRA_EXPORT sha1_hash hash_address(address const& ip); + +namespace aux { + + template + std::size_t address_size(Proto p) + { + if (p == Proto::v6()) + return std::tuple_size::value; + else + return std::tuple_size::value; + } + + template + void write_address(address const& a, OutIt&& out) + { + if (a.is_v4()) + { + write_uint32(a.to_v4().to_uint(), out); + } + else if (a.is_v6()) + { + for (auto b : a.to_v6().to_bytes()) + write_uint8(b, out); + } + } + + template + address_v4 read_v4_address(InIt&& in) + { + std::uint32_t const ip = read_uint32(in); + return address_v4(ip); + } + + template + address_v6 read_v6_address(InIt&& in) + { + address_v6::bytes_type bytes; + for (auto& b : bytes) + b = read_uint8(in); + return address_v6(bytes); + } + + template + void write_endpoint(Endpoint const& e, OutIt&& out) + { + write_address(e.address(), out); + write_uint16(e.port(), out); + } + + template + Endpoint read_v4_endpoint(InIt&& in) + { + address addr = read_v4_address(in); + std::uint16_t port = read_uint16(in); + return Endpoint(addr, port); + } + + template + Endpoint read_v6_endpoint(InIt&& in) + { + address addr = read_v6_address(in); + std::uint16_t port = read_uint16(in); + return Endpoint(addr, port); + } + + template + std::vector read_endpoint_list(libtorrent::bdecode_node const& n) + { + std::vector ret; + if (n.type() != bdecode_node::list_t) return ret; + for (int i = 0; i < n.list_size(); ++i) + { + bdecode_node e = n.list_at(i); + if (e.type() != bdecode_node::string_t) return ret; + if (e.string_length() < 6) continue; + char const* in = e.string_ptr(); + if (e.string_length() == 6) + ret.push_back(read_v4_endpoint(in)); + else if (e.string_length() == 18) + ret.push_back(read_v6_endpoint(in)); + } + return ret; + } +} // namespace aux + +} + +#endif diff --git a/include/libtorrent/socket_type.hpp b/include/libtorrent/socket_type.hpp new file mode 100644 index 0000000..ae15d31 --- /dev/null +++ b/include/libtorrent/socket_type.hpp @@ -0,0 +1,65 @@ +/* + +Copyright (c) 2019-2020, Arvid Norberg +Copyright (c) 2020, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SOCKET_TYPE_HPP +#define TORRENT_SOCKET_TYPE_HPP + +#include "libtorrent/config.hpp" +#include + +namespace libtorrent { + +// A type describing kinds of sockets involved in various operations or events. +enum class socket_type_t : std::uint8_t { + tcp, + socks5, + http, + utp, + i2p, + tcp_ssl, + socks5_ssl, + http_ssl, + utp_ssl, + +#if TORRENT_ABI_VERSION <= 2 + udp TORRENT_DEPRECATED_ENUM = utp, +#endif +}; + +// return a short human readable name for types of socket +// TODO: move to aux +char const* socket_type_name(socket_type_t); + +} + +#endif diff --git a/include/libtorrent/socks5_stream.hpp b/include/libtorrent/socks5_stream.hpp new file mode 100644 index 0000000..0459331 --- /dev/null +++ b/include/libtorrent/socks5_stream.hpp @@ -0,0 +1,561 @@ +/* + +Copyright (c) 2007, 2009-2010, 2013-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SOCKS5_STREAM_HPP_INCLUDED +#define TORRENT_SOCKS5_STREAM_HPP_INCLUDED + +#include + +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/aux_/ip_helpers.hpp" // for is_ip_address +#include "libtorrent/assert.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/string_util.hpp" // for to_string +#include "libtorrent/socket_io.hpp" + +namespace libtorrent { + +namespace socks_error { + + // SOCKS5 error values. If an error_code has the + // socks error category (get_socks_category()), these + // are the error values. + enum socks_error_code + { + no_error = 0, + unsupported_version, + unsupported_authentication_method, + unsupported_authentication_version, + authentication_error, + username_required, + general_failure, + command_not_supported, + no_identd, + identd_error, + + num_errors + }; + + // internal + TORRENT_EXPORT boost::system::error_code make_error_code(socks_error_code e); + +} // namespace socks_error +} + +namespace boost { +namespace system { + + template<> struct is_error_code_enum + { static const bool value = true; }; + +} +} + +namespace libtorrent { + +// returns the error_category for SOCKS5 errors +TORRENT_EXPORT boost::system::error_category& socks_category(); + +#if TORRENT_ABI_VERSION == 1 +TORRENT_DEPRECATED +inline boost::system::error_category& get_socks_category() +{ return socks_category(); } +#endif + +class socks5_stream : public proxy_base +{ +public: + + // commands + enum { + socks5_connect = 1, + socks5_udp_associate = 3 + }; + + socks5_stream(socks5_stream&&) = default; + explicit socks5_stream(io_context& io_context) + : proxy_base(io_context) + , m_version(5) + , m_command(socks5_connect) + {} + + void set_version(int v) { m_version = v; } + + void set_command(int c) + { + TORRENT_ASSERT(c == socks5_connect || c == socks5_udp_associate); + m_command = c; + } + + void set_username(std::string const& user + , std::string const& password) + { + m_user = user; + m_password = password; + } + + void set_dst_name(std::string const& host) + { + // if this assert trips, set_dst_name() is called with an IP address rather + // than a hostname. Instead, resolve the IP into an address and pass it to + // async_connect instead + TORRENT_ASSERT(!aux::is_ip_address(host)); + m_dst_name = host; + if (m_dst_name.size() > 255) + m_dst_name.resize(255); + } + + void close(error_code& ec) + { + m_dst_name.clear(); + proxy_base::close(ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void close() + { + m_dst_name.clear(); + proxy_base::close(); + } +#endif + + // TODO: 2 add async_connect() that takes a hostname and port as well + template + void async_connect(endpoint_type const& endpoint, Handler handler) + { + // make sure we don't try to connect to INADDR_ANY. binding is fine, + // and using a hostname is fine on SOCKS version 5. + TORRENT_ASSERT(endpoint.address() != address() + || (!m_dst_name.empty() && m_version == 5)); + + m_remote_endpoint = endpoint; + + // the connect is split up in the following steps: + // 1. resolve name of proxy server + // 2. connect to proxy server + // 3. if version == 5: + // 3.1 send SOCKS5 authentication method message + // 3.2 read SOCKS5 authentication response + // 3.3 send username+password + // 4. send SOCKS command message + + ADD_OUTSTANDING_ASYNC("socks5_stream::name_lookup"); + m_resolver.async_resolve(m_hostname, to_string(m_port).data(), wrap_allocator( + [this](error_code const& ec, tcp::resolver::results_type ips, Handler hn) { + name_lookup(ec, std::move(ips), std::move(hn)); + }, std::move(handler))); + } + +private: + + template + void name_lookup(error_code const& e, tcp::resolver::results_type ips + , Handler h) + { + COMPLETE_ASYNC("socks5_stream::name_lookup"); + if (handle_error(e, std::move(h))) return; + + auto i = ips.begin(); + if (!m_sock.is_open()) + { + error_code ec; + m_sock.open(i->endpoint().protocol(), ec); + if (handle_error(ec, std::move(h))) return; + } + + // TODO: we could bind the socket here, since we know what the + // target endpoint is of the proxy + ADD_OUTSTANDING_ASYNC("socks5_stream::connected"); + m_sock.async_connect(i->endpoint(), wrap_allocator( + [this](error_code const& ec, Handler hn) + { connected(ec, std::move(hn)); }, std::move(h))); + } + + template + void connected(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::connected"); + if (handle_error(e, std::move(h))) return; + + using namespace libtorrent::aux; + if (m_version == 5) + { + // send SOCKS5 authentication methods + m_buffer.resize(m_user.empty()?3:4); + char* p = &m_buffer[0]; + write_uint8(5, p); // SOCKS VERSION 5 + if (m_user.empty()) + { + write_uint8(1, p); // 1 authentication method (no auth) + write_uint8(0, p); // no authentication + } + else + { + write_uint8(2, p); // 2 authentication methods + write_uint8(0, p); // no authentication + write_uint8(2, p); // username/password + } + ADD_OUTSTANDING_ASYNC("socks5_stream::handshake1"); + async_write(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake1(ec, std::move(hn)); + }, std::move(h))); + } + else if (m_version == 4) + { + socks_connect(std::move(h)); + } + else + { + std::move(h)(error_code(socks_error::unsupported_version)); + } + } + + template + void handshake1(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::handshake1"); + if (handle_error(e, std::move(h))) return; + + ADD_OUTSTANDING_ASYNC("socks5_stream::handshake2"); + m_buffer.resize(2); + async_read(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake2(ec, std::move(hn)); + }, std::move(h))); + } + + template + void handshake2(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::handshake2"); + if (handle_error(e, std::move(h))) return; + + using namespace libtorrent::aux; + + char* p = &m_buffer[0]; + int version = read_uint8(p); + int method = read_uint8(p); + + if (version < m_version) + { + std::move(h)(error_code(socks_error::unsupported_version)); + return; + } + + if (method == 0) + { + socks_connect(std::move(h)); + } + else if (method == 2) + { + if (m_user.empty()) + { + std::move(h)(error_code(socks_error::username_required)); + return; + } + + // start sub-negotiation + m_buffer.resize(m_user.size() + m_password.size() + 3); + p = &m_buffer[0]; + write_uint8(1, p); + TORRENT_ASSERT(m_user.size() < 0x100); + write_uint8(uint8_t(m_user.size()), p); + write_string(m_user, p); + TORRENT_ASSERT(m_password.size() < 0x100); + write_uint8(uint8_t(m_password.size()), p); + write_string(m_password, p); + + ADD_OUTSTANDING_ASYNC("socks5_stream::handshake3"); + async_write(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t const, Handler hn) { + handshake3(ec, std::move(hn)); + }, std::move(h))); + } + else + { + std::move(h)(error_code(socks_error::unsupported_authentication_method)); + return; + } + } + + template + void handshake3(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::handshake3"); + if (handle_error(e, std::move(h))) return; + + ADD_OUTSTANDING_ASYNC("socks5_stream::handshake4"); + m_buffer.resize(2); + async_read(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + handshake4(ec, std::move(hn)); + }, std::move(h))); + } + + template + void handshake4(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::handshake4"); + if (handle_error(e, std::move(h))) return; + + using namespace libtorrent::aux; + + char* p = &m_buffer[0]; + int version = read_uint8(p); + int status = read_uint8(p); + + if (version != 1) + { + std::move(h)(error_code(socks_error::unsupported_authentication_version)); + return; + } + + if (status != 0) + { + std::move(h)(error_code(socks_error::authentication_error)); + return; + } + + std::vector().swap(m_buffer); + socks_connect(std::move(h)); + } + + template + void socks_connect(Handler h) + { + using namespace libtorrent::aux; + + if (m_version == 5) + { + // send SOCKS5 connect command + m_buffer.resize(6 + (!m_dst_name.empty() + ? m_dst_name.size() + 1 + :(aux::is_v4(m_remote_endpoint) ? 4 : 16))); + char* p = &m_buffer[0]; + write_uint8(5, p); // SOCKS VERSION 5 + write_uint8(std::uint8_t(m_command), p); // CONNECT command + write_uint8(0, p); // reserved + if (!m_dst_name.empty()) + { + write_uint8(3, p); // address type + TORRENT_ASSERT(m_dst_name.size() < 0x100); + write_uint8(uint8_t(m_dst_name.size()), p); + std::copy(m_dst_name.begin(), m_dst_name.end(), p); + p += m_dst_name.size(); + } + else + { + // we either need a hostname or a valid endpoint + TORRENT_ASSERT(m_remote_endpoint.address() != address()); + + write_uint8(aux::is_v4(m_remote_endpoint) ? 1 : 4, p); // address type + write_address(m_remote_endpoint.address(), p); + } + write_uint16(m_remote_endpoint.port(), p); + } + else if (m_version == 4) + { + // SOCKS4 only supports IPv4 + if (!aux::is_v4(m_remote_endpoint)) + { + std::move(h)(error_code(boost::asio::error::address_family_not_supported)); + return; + } + m_buffer.resize(m_user.size() + 9); + char* p = &m_buffer[0]; + write_uint8(4, p); // SOCKS VERSION 4 + write_uint8(std::uint8_t(m_command), p); // CONNECT command + write_uint16(m_remote_endpoint.port(), p); + write_uint32(m_remote_endpoint.address().to_v4().to_uint(), p); + std::copy(m_user.begin(), m_user.end(), p); + p += m_user.size(); + write_uint8(0, p); // 0-terminator + } + else + { + std::move(h)(error_code(socks_error::unsupported_version)); + return; + } + + ADD_OUTSTANDING_ASYNC("socks5_stream::connect1"); + async_write(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + connect1(ec, std::move(hn)); + }, std::move(h))); + } + + template + void connect1(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::connect1"); + if (handle_error(e, std::move(h))) return; + + if (m_version == 5) + m_buffer.resize(6 + 4); // assume an IPv4 address + else if (m_version == 4) + m_buffer.resize(8); + + ADD_OUTSTANDING_ASYNC("socks5_stream::connect2"); + async_read(m_sock, boost::asio::buffer(m_buffer), wrap_allocator( + [this](error_code const& ec, std::size_t, Handler hn) { + connect2(ec, std::move(hn)); + }, std::move(h))); + } + + template + void connect2(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::connect2"); + if (handle_error(e, std::move(h))) return; + + using namespace libtorrent::aux; + + char const* p = &m_buffer[0]; + int const version = read_uint8(p); + int const response = read_uint8(p); + + if (m_version == 5) + { + if (version < m_version) + { + std::move(h)(error_code(socks_error::unsupported_version)); + return; + } + if (response != 0) + { + error_code ec(socks_error::general_failure); + switch (response) + { + case 2: ec = boost::asio::error::no_permission; break; + case 3: ec = boost::asio::error::network_unreachable; break; + case 4: ec = boost::asio::error::host_unreachable; break; + case 5: ec = boost::asio::error::connection_refused; break; + case 6: ec = boost::asio::error::timed_out; break; + case 7: ec = socks_error::command_not_supported; break; + case 8: ec = boost::asio::error::address_family_not_supported; break; + } + std::move(h)(ec); + return; + } + p += 1; // reserved + int const atyp = read_uint8(p); + // read the proxy IP it was bound to (this is variable length depending + // on address type) + if (atyp == 1) + { + std::vector().swap(m_buffer); + std::move(h)(e); + return; + } + std::size_t extra_bytes = 0; + if (atyp == 4) + { + // IPv6 + extra_bytes = 12; + } + else if (atyp == 3) + { + // hostname with length prefix + extra_bytes = read_uint8(p) - 3; + } + else + { + std::move(h)(error_code(boost::asio::error::address_family_not_supported)); + return; + } + m_buffer.resize(m_buffer.size() + extra_bytes); + + ADD_OUTSTANDING_ASYNC("socks5_stream::connect3"); + TORRENT_ASSERT(extra_bytes > 0); + async_read(m_sock, boost::asio::buffer(&m_buffer[m_buffer.size() - extra_bytes], extra_bytes) + , wrap_allocator([this](error_code const& ec, std::size_t, Handler hn) { + connect3(ec, std::move(hn)); + }, std::move(h))); + } + else if (m_version == 4) + { + if (version != 0) + { + std::move(h)(error_code(socks_error::general_failure)); + return; + } + + // access granted + if (response == 90) + { + std::vector().swap(m_buffer); + std::move(h)(e); + return; + } + + error_code ec(socks_error::general_failure); + switch (response) + { + case 91: ec = boost::asio::error::connection_refused; break; + case 92: ec = socks_error::no_identd; break; + case 93: ec = socks_error::identd_error; break; + } + std::move(h)(ec); + } + } + + template + void connect3(error_code const& e, Handler h) + { + COMPLETE_ASYNC("socks5_stream::connect3"); + using namespace libtorrent::aux; + + if (handle_error(e, std::move(h))) return; + + std::vector().swap(m_buffer); + std::move(h)(e); + } + + // send and receive buffer + std::vector m_buffer; + // proxy authentication + std::string m_user; + std::string m_password; + std::string m_dst_name; + + int m_version; + + // the socks command to send for this connection (connect or udp associate) + int m_command; +}; + +} + +#endif diff --git a/include/libtorrent/span.hpp b/include/libtorrent/span.hpp new file mode 100644 index 0000000..82f5c77 --- /dev/null +++ b/include/libtorrent/span.hpp @@ -0,0 +1,194 @@ +/* + +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2016-2020, Arvid Norberg +Copyright (c) 2017, Steven Siloti +Copyright (c) 2019, Michael Cronenworth +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SPAN_HPP_INCLUDED +#define TORRENT_SPAN_HPP_INCLUDED + +#include +#include +#include +#include "libtorrent/assert.hpp" + +namespace libtorrent { + +namespace aux { + + template + struct compatible_type + { + // conversions that are OK + // int -> int + // int const -> int const + // int -> int const + // int -> int volatile + // int -> int const volatile + // int volatile -> int const volatile + // int const -> int const volatile + + static const bool value = std::is_same::value + || std::is_same::type>::value + || std::is_same::type>::value + || std::is_same::type>::value + ; + }; +} + + template + struct span + { + using difference_type = std::ptrdiff_t; + using index_type = std::ptrdiff_t; + + span() noexcept : m_ptr(nullptr), m_len(0) {} + + template ::value>::type> + span(span const& v) noexcept // NOLINT + : m_ptr(v.data()), m_len(v.size()) {} + + span(T& p) noexcept : m_ptr(&p), m_len(1) {} // NOLINT + span(T* p, difference_type const l) noexcept : m_ptr(p), m_len(l) // NOLINT + { TORRENT_ASSERT(l >= 0); } + + template + span(std::array& arr) noexcept // NOLINT + : m_ptr(arr.data()), m_len(static_cast(arr.size())) {} + + // this is necessary until C++17, where data() returns a non-const pointer + template + span(std::basic_string& str) noexcept // NOLINT + : m_ptr(&str[0]), m_len(static_cast(str.size())) {} + + template + span(U (&arr)[N]) noexcept // NOLINT + : m_ptr(&arr[0]), m_len(N) {} + + // anything with a .data() member function is considered a container + // but only if the value type is compatible with T + template ().data())>::type + , typename = typename std::enable_if::value>::type> + span(Cont& c) // NOLINT + : m_ptr(c.data()), m_len(static_cast(c.size())) {} + + // allow construction from const containers if T is const + // this allows const spans to be constructed from a temporary container + template ().data())>::type + , typename = typename std::enable_if::value + && std::is_const::value>::type> + span(Cont const& c) // NOLINT + : m_ptr(c.data()), m_len(static_cast(c.size())) {} + + template ::value>::type> + span& operator=(span const& rhs) noexcept + { + m_ptr = rhs.data(); + m_len = rhs.size(); + return *this; + } + + index_type size() const noexcept { return m_len; } + bool empty() const noexcept { return m_len == 0; } + T* data() const noexcept { return m_ptr; } + + using const_iterator = T const*; + using const_reverse_iterator = std::reverse_iterator; + using iterator = T*; + using reverse_iterator = std::reverse_iterator; + + T* begin() const noexcept { return m_ptr; } + T* end() const noexcept { return m_ptr + m_len; } + reverse_iterator rbegin() const noexcept { return reverse_iterator(end()); } + reverse_iterator rend() const noexcept { return reverse_iterator(begin()); } + + T& front() const noexcept { TORRENT_ASSERT(m_len > 0); return m_ptr[0]; } + T& back() const noexcept { TORRENT_ASSERT(m_len > 0); return m_ptr[m_len - 1]; } + + span first(difference_type const n) const + { + TORRENT_ASSERT(size() >= n); + return { data(), n }; + } + + span last(difference_type const n) const + { + TORRENT_ASSERT(size() >= n); + return { data() + size() - n, n }; + } + + span subspan(index_type const offset) const + { + TORRENT_ASSERT(size() >= offset); + return { data() + offset, size() - offset }; + } + + span subspan(index_type const offset, difference_type const count) const + { + TORRENT_ASSERT(count >= 0); + TORRENT_ASSERT(size() >= offset); + TORRENT_ASSERT(size() >= offset + count); + return { data() + offset, count }; + } + + T& operator[](index_type const idx) const + { + TORRENT_ASSERT(idx < m_len); + TORRENT_ASSERT(idx >= 0); + return m_ptr[idx]; + } + + private: + T* m_ptr; + difference_type m_len; + }; + + template + inline bool operator==(span const& lhs, span const& rhs) + { + return lhs.size() == rhs.size() + && (lhs.data() == rhs.data() || std::equal(lhs.begin(), lhs.end(), rhs.begin())); + } + + template + inline bool operator!=(span const& lhs, span const& rhs) + { + return lhs.size() != rhs.size() + || (lhs.data() != rhs.data() && !std::equal(lhs.begin(), lhs.end(), rhs.begin())); + } +} + +#endif // TORRENT_SPAN_HPP_INCLUDED diff --git a/include/libtorrent/ssl.hpp b/include/libtorrent/ssl.hpp new file mode 100644 index 0000000..044ee8c --- /dev/null +++ b/include/libtorrent/ssl.hpp @@ -0,0 +1,186 @@ +/* + +Copyright (c) 2020, Paul-Louis Ageneau +Copyright (c) 2018, Alexandre Janniaux +Copyright (c) 2020, Arvid Norberg +Copyright (c) 2020, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SSL_HPP_INCLUDED +#define TORRENT_SSL_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/aux_/export.hpp" + +#if TORRENT_USE_SSL + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include + +#ifndef BOOST_NO_EXCEPTIONS +#include +#endif + +#ifdef TORRENT_USE_OPENSSL +#include // for OPENSSL_VERSION_NUMBER +#if OPENSSL_VERSION_NUMBER < 0x1000000fL +#error OpenSSL too old, use a recent version with SNI support +#endif +#ifdef TORRENT_WINDOWS +// because openssl includes winsock.h, we must include winsock2.h first +#include +#endif +#include +#include +#include +#include +#endif + +#ifdef TORRENT_USE_GNUTLS +#include +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#ifdef TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#endif + +#include +#include +#include +#include + +namespace libtorrent { +namespace ssl { + +using error_code = boost::system::error_code; + +#if defined TORRENT_USE_OPENSSL +#if defined TORRENT_BUILD_SIMULATOR + using sim::asio::ssl::context; + using sim::asio::ssl::stream_base; + using sim::asio::ssl::stream; +#else + using boost::asio::ssl::context; + using boost::asio::ssl::stream_base; + using boost::asio::ssl::stream; +#endif +using boost::asio::ssl::verify_context; +#if BOOST_VERSION >= 107300 +using boost::asio::ssl::host_name_verification; +#else +using host_name_verification = boost::asio::ssl::rfc2818_verification; +#endif + +using native_context_type = SSL_CTX*; +using native_stream_type = SSL*; +using context_handle_type = native_context_type; +using stream_handle_type = native_stream_type; + +typedef int (*server_name_callback_type)(SSL* s, int*, void* arg); + +#elif defined TORRENT_USE_GNUTLS +using boost::asio::gnutls::context; +using boost::asio::gnutls::stream_base; +using boost::asio::gnutls::stream; +using boost::asio::gnutls::verify_context; +using boost::asio::gnutls::host_name_verification; + +using native_context_type = context::native_handle_type; +using native_stream_type = stream_base::native_handle_type; +using context_handle_type = context*; +using stream_handle_type = stream_base*; + +typedef bool (*server_name_callback_type)(stream_handle_type handle, std::string const& name, void* arg); + +#endif + +namespace error { + +#if defined TORRENT_USE_OPENSSL +using boost::asio::error::get_ssl_category; +using boost::asio::ssl::error::get_stream_category; +#elif defined TORRENT_USE_GNUTLS +using boost::asio::gnutls::error::get_ssl_category; +using boost::asio::gnutls::error::get_stream_category; +#endif + +} + +inline context_handle_type get_handle(context &c) +{ +#if defined TORRENT_USE_OPENSSL + return c.native_handle(); +#elif defined TORRENT_USE_GNUTLS + return &c; +#endif +} + +template +stream_handle_type get_handle(stream& s) +{ +#if defined TORRENT_USE_OPENSSL + return s.native_handle(); +#elif defined TORRENT_USE_GNUTLS + return &s; +#endif +} + +template +context_handle_type get_context_handle(stream& s) +{ +#if defined TORRENT_USE_OPENSSL + return SSL_get_SSL_CTX(s.native_handle()); +#elif defined TORRENT_USE_GNUTLS + return &s.get_context(); +#endif +} + +TORRENT_EXTRA_EXPORT void set_trust_certificate(native_context_type nc, string_view pem, error_code &ec); + +TORRENT_EXTRA_EXPORT void set_server_name_callback(context_handle_type c, server_name_callback_type cb, void* arg, error_code& ec); +TORRENT_EXTRA_EXPORT void set_host_name(stream_handle_type s, std::string const& name, error_code& ec); + +TORRENT_EXTRA_EXPORT void set_context(stream_handle_type s, context_handle_type c); +TORRENT_EXTRA_EXPORT bool has_context(stream_handle_type s, context_handle_type c); +TORRENT_EXTRA_EXPORT context_handle_type get_context(stream_handle_type s); + +} // ssl +} // libtorrent + +#endif // TORRENT_USE_SSL + +#endif diff --git a/include/libtorrent/ssl_stream.hpp b/include/libtorrent/ssl_stream.hpp new file mode 100644 index 0000000..4e7fadd --- /dev/null +++ b/include/libtorrent/ssl_stream.hpp @@ -0,0 +1,355 @@ +/* + +Copyright (c) 2008, 2010-2020, Arvid Norberg +Copyright (c) 2018, Alden Torres +Copyright (c) 2018, Alexandre Janniaux +Copyright (c) 2020, Paul-Louis Ageneau +Copyright (c) 2021, YenForYang +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SSL_STREAM_HPP_INCLUDED +#define TORRENT_SSL_STREAM_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_SSL + +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/ssl.hpp" + +#include + +#include + +namespace libtorrent { + +template +struct ssl_stream +{ + ssl_stream(io_context& io_context, ssl::context& ctx) + : m_sock(new ssl::stream(io_context, ctx)) + {} + + template + ssl_stream(S&& s, ssl::context& ctx) + : m_sock(new ssl::stream(std::forward(s), ctx)) + {} + + ssl_stream(ssl_stream&&) = default; + + using sock_type = typename ssl::stream; + using next_layer_type = typename sock_type::next_layer_type; + using lowest_layer_type = typename Stream::lowest_layer_type; + using endpoint_type = typename Stream::endpoint_type; + using protocol_type = typename Stream::protocol_type; +#if BOOST_VERSION >= 106600 + using executor_type = typename sock_type::executor_type; + executor_type get_executor() { return m_sock->get_executor(); } +#endif + + ssl::stream_handle_type handle() + { + return ssl::get_handle(*m_sock); + } + + ssl::context_handle_type context_handle() + { + return ssl::get_context_handle(*m_sock); + } + + void set_host_name(std::string const& name) + { + error_code ec; + set_host_name(name, ec); + if (ec) boost::throw_exception(boost::system::system_error(ec)); + } + + void set_host_name(std::string const& name, error_code& ec) + { + ssl::set_host_name(handle(), name, ec); + } + + template + void set_verify_callback(T const& fun, error_code& ec) + { + m_sock->set_verify_callback(fun, ec); + } + + template + void async_connect(endpoint_type const& endpoint, Handler const& h) + { + // the connect is split up in the following steps: + // 1. connect to peer + // 2. perform SSL client handshake + + m_sock->next_layer().async_connect(endpoint, wrap_allocator( + [this](error_code const& ec, Handler hn) { + connected(ec, std::move(hn)); + }, std::move(h))); + } + + template + void async_accept_handshake(Handler const& h) + { + // this is used for accepting SSL connections + m_sock->async_handshake(ssl::stream_base::server, wrap_allocator( + [this](error_code const& ec, Handler hn) { + handshake(ec, std::move(hn)); + }, std::move(h))); + } + + void accept_handshake(error_code& ec) + { + // this is used for accepting SSL connections + m_sock->handshake(ssl::stream_base::server, ec); + } + + template + void async_shutdown(Handler handler) + { + error_code ec; + m_sock->next_layer().cancel(ec); + m_sock->async_shutdown(std::move(handler)); + } + + void shutdown(error_code& ec) + { + m_sock->shutdown(ec); + } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler handler) + { + m_sock->async_read_some(buffers, std::move(handler)); + } + + template + std::size_t read_some(Mutable_Buffers const& buffers, error_code& ec) + { + return m_sock->read_some(buffers, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void set_option(SettableSocketOption const& opt) + { + m_sock->next_layer().set_option(opt); + } +#endif + + template + void set_option(SettableSocketOption const& opt, error_code& ec) + { + m_sock->next_layer().set_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void get_option(GettableSocketOption& opt) + { + m_sock->next_layer().get_option(opt); + } +#endif + + template + void get_option(GettableSocketOption& opt, error_code& ec) + { + m_sock->next_layer().get_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + std::size_t read_some(Mutable_Buffers const& buffers) + { + return m_sock->read_some(buffers); + } + + template + void io_control(IO_Control_Command& ioc) + { + m_sock->next_layer().io_control(ioc); + } +#endif + + template + void io_control(IO_Control_Command& ioc, error_code& ec) + { + m_sock->next_layer().io_control(ioc, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void non_blocking(bool b) { m_sock->next_layer().non_blocking(b); } +#endif + + void non_blocking(bool b, error_code& ec) + { m_sock->next_layer().non_blocking(b, ec); } + + template + void async_write_some(Const_Buffers const& buffers, Handler handler) + { + m_sock->async_write_some(buffers, std::move(handler)); + } + + template + std::size_t write_some(Const_Buffers const& buffers, error_code& ec) + { + return m_sock->write_some(buffers, ec); + } + + // the SSL stream may cache 17 kiB internally, and there's no way of + // asking how large its buffer is. 17 kiB isn't very much though, so it + // seems fine to potentially over-estimate the number of bytes available. +#ifndef BOOST_NO_EXCEPTIONS + std::size_t available() const + { return 17 * 1024 + const_cast(*m_sock).next_layer().available(); } +#endif + + std::size_t available(error_code& ec) const + { return 17 * 1024 + const_cast(*m_sock).next_layer().available(ec); } + +#ifndef BOOST_NO_EXCEPTIONS + void bind(endpoint_type const& endpoint) + { + m_sock->next_layer().bind(endpoint); + } +#endif + + void bind(endpoint_type const& endpoint, error_code& ec) + { + m_sock->next_layer().bind(endpoint, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void open(protocol_type const& p) + { + m_sock->next_layer().open(p); + } +#endif + + void open(protocol_type const& p, error_code& ec) + { + m_sock->next_layer().open(p, ec); + } + + bool is_open() const + { + return m_sock->next_layer().is_open(); + } + +#ifndef BOOST_NO_EXCEPTIONS + void close() + { + m_sock->next_layer().close(); + } +#endif + + void close(error_code& ec) + { + m_sock->next_layer().close(ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type remote_endpoint() const + { + return m_sock->next_layer().remote_endpoint(); + } +#endif + + endpoint_type remote_endpoint(error_code& ec) const + { + return m_sock->next_layer().remote_endpoint(ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type local_endpoint() const + { + return m_sock->next_layer().local_endpoint(); + } +#endif + + endpoint_type local_endpoint(error_code& ec) const + { + return m_sock->next_layer().local_endpoint(ec); + } + + lowest_layer_type& lowest_layer() + { + return m_sock->lowest_layer(); + } + + lowest_layer_type const& lowest_layer() const + { + return m_sock->lowest_layer(); + } + + next_layer_type& next_layer() + { + return m_sock->next_layer(); + } + + next_layer_type const& next_layer() const + { + return m_sock->next_layer(); + } + +private: + + template + void connected(error_code const& e, Handler h) + { + if (e) + { + h(e); + return; + } + + m_sock->async_handshake(ssl::stream_base::client, wrap_allocator( + [this](error_code const& ec, Handler hn) { + handshake(ec, std::move(hn)); + }, std::move(h))); + } + + template + void handshake(error_code const& e, Handler h) + { + h(e); + } + + // to make us movable + std::unique_ptr> m_sock; +}; + +} + +#endif // TORRENT_USE_SSL + +#endif diff --git a/include/libtorrent/stack_allocator.hpp b/include/libtorrent/stack_allocator.hpp new file mode 100644 index 0000000..ec94f46 --- /dev/null +++ b/include/libtorrent/stack_allocator.hpp @@ -0,0 +1,96 @@ +/* + +Copyright (c) 2012, 2015-2019, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STACK_ALLOCATOR +#define TORRENT_STACK_ALLOCATOR + +#include "libtorrent/assert.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" + +#include // for va_list +#include // for vsnprintf +#include + +namespace libtorrent { +namespace aux { + + struct allocation_slot + { + allocation_slot() noexcept : m_idx(-1) {} + allocation_slot(allocation_slot const&) noexcept = default; + allocation_slot(allocation_slot&&) noexcept = default; + allocation_slot& operator=(allocation_slot const&) & = default; + allocation_slot& operator=(allocation_slot&&) & noexcept = default; + bool operator==(allocation_slot const& s) const { return m_idx == s.m_idx; } + bool operator!=(allocation_slot const& s) const { return m_idx != s.m_idx; } + friend struct stack_allocator; + private: + explicit allocation_slot(int idx) noexcept : m_idx(idx) {} + int val() const { return m_idx; } + int m_idx; + }; + + struct TORRENT_EXTRA_EXPORT stack_allocator + { + stack_allocator() {} + + // non-copyable + stack_allocator(stack_allocator const&) = delete; + stack_allocator& operator=(stack_allocator const&) = delete; + stack_allocator(stack_allocator&&) = default; + stack_allocator& operator=(stack_allocator&&) & = default; + + allocation_slot copy_string(string_view str); + allocation_slot copy_string(char const* str); + + allocation_slot format_string(char const* fmt, va_list v); + + allocation_slot copy_buffer(span buf); + allocation_slot allocate(int bytes); + char* ptr(allocation_slot idx); + char const* ptr(allocation_slot idx) const; + void swap(stack_allocator& rhs); + void reset(); + + private: + + vector m_storage; + }; + +} +} + +#endif diff --git a/include/libtorrent/stat.hpp b/include/libtorrent/stat.hpp new file mode 100644 index 0000000..5bdd1f3 --- /dev/null +++ b/include/libtorrent/stat.hpp @@ -0,0 +1,287 @@ +/* + +Copyright (c) 2003-2005, 2007-2012, 2014-2017, 2019, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STAT_HPP_INCLUDED +#define TORRENT_STAT_HPP_INCLUDED + +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent { + + class TORRENT_EXTRA_EXPORT stat_channel + { + public: + + stat_channel() + : m_total_counter(0) + , m_counter(0) + , m_5_sec_average(0) + {} + + void operator+=(stat_channel const& s) + { + TORRENT_ASSERT(m_counter < (std::numeric_limits::max)() - s.m_counter); + m_counter += s.m_counter; + TORRENT_ASSERT(m_total_counter < (std::numeric_limits::max)() - s.m_counter); + m_total_counter += s.m_counter; + } + + void add(int count) + { + TORRENT_ASSERT(count >= 0); + + TORRENT_ASSERT(m_counter < (std::numeric_limits::max)() - count); + m_counter += count; + TORRENT_ASSERT(m_total_counter < (std::numeric_limits::max)() - count); + m_total_counter += count; + } + + // should be called once every second + void second_tick(int tick_interval_ms); + std::int32_t rate() const { return m_5_sec_average; } + std::int32_t low_pass_rate() const { return m_5_sec_average; } + + std::int64_t total() const { return m_total_counter; } + + void offset(std::int64_t c) + { + TORRENT_ASSERT(m_total_counter < (std::numeric_limits::max)() - c); + m_total_counter += c; + } + + std::int32_t counter() const { return m_counter; } + + void clear() + { + m_counter = 0; + m_5_sec_average = 0; + m_total_counter = 0; + } + + private: + + // total counters + std::int64_t m_total_counter; + + // the accumulator for this second. + std::int32_t m_counter; + + // sliding average + std::int32_t m_5_sec_average; + }; + + class TORRENT_EXTRA_EXPORT stat + { + public: + void operator+=(const stat& s) + { + for (int i = 0; i < num_channels; ++i) + m_stat[i] += s.m_stat[i]; + } + + void sent_syn(bool ipv6) + { + m_stat[upload_ip_protocol].add(ipv6 ? 60 : 40); + } + + void received_synack(bool ipv6) + { + // we received SYN-ACK and also sent ACK back + m_stat[download_ip_protocol].add(ipv6 ? 60 : 40); + m_stat[upload_ip_protocol].add(ipv6 ? 60 : 40); + } + + void received_bytes(int bytes_payload, int bytes_protocol) + { + TORRENT_ASSERT(bytes_payload >= 0); + TORRENT_ASSERT(bytes_protocol >= 0); + + m_stat[download_payload].add(bytes_payload); + m_stat[download_protocol].add(bytes_protocol); + } + + void sent_bytes(int bytes_payload, int bytes_protocol) + { + TORRENT_ASSERT(bytes_payload >= 0); + TORRENT_ASSERT(bytes_protocol >= 0); + + m_stat[upload_payload].add(bytes_payload); + m_stat[upload_protocol].add(bytes_protocol); + } + + // and IP packet was received or sent + // account for the overhead caused by it + void trancieve_ip_packet(int bytes_transferred, bool ipv6) + { + // one TCP/IP packet header for the packet + // sent or received, and one for the ACK + // The IPv4 header is 20 bytes + // and IPv6 header is 40 bytes + const int header = (ipv6 ? 40 : 20) + 20; + const int mtu = 1500; + const int packet_size = mtu - header; + const int overhead = (std::max)(1, (bytes_transferred + packet_size - 1) / packet_size) * header; + m_stat[download_ip_protocol].add(overhead); + m_stat[upload_ip_protocol].add(overhead); + } + + int upload_ip_overhead() const { return m_stat[upload_ip_protocol].counter(); } + int download_ip_overhead() const { return m_stat[download_ip_protocol].counter(); } + + // should be called once every second + void second_tick(int tick_interval_ms) + { + for (int i = 0; i < num_channels; ++i) + m_stat[i].second_tick(tick_interval_ms); + } + + int low_pass_upload_rate() const + { + return m_stat[upload_payload].low_pass_rate() + + m_stat[upload_protocol].low_pass_rate() + + m_stat[upload_ip_protocol].low_pass_rate(); + } + + int low_pass_download_rate() const + { + return m_stat[download_payload].low_pass_rate() + + m_stat[download_protocol].low_pass_rate() + + m_stat[download_ip_protocol].low_pass_rate(); + } + + int upload_rate() const + { + return m_stat[upload_payload].rate() + + m_stat[upload_protocol].rate() + + m_stat[upload_ip_protocol].rate(); + } + + int download_rate() const + { + return m_stat[download_payload].rate() + + m_stat[download_protocol].rate() + + m_stat[download_ip_protocol].rate(); + } + + std::int64_t total_upload() const + { + return m_stat[upload_payload].total() + + m_stat[upload_protocol].total() + + m_stat[upload_ip_protocol].total(); + } + + std::int64_t total_download() const + { + return m_stat[download_payload].total() + + m_stat[download_protocol].total() + + m_stat[download_ip_protocol].total(); + } + + int upload_payload_rate() const + { return m_stat[upload_payload].rate(); } + int download_payload_rate() const + { return m_stat[download_payload].rate(); } + + std::int64_t total_payload_upload() const + { return m_stat[upload_payload].total(); } + std::int64_t total_payload_download() const + { return m_stat[download_payload].total(); } + + std::int64_t total_protocol_upload() const + { return m_stat[upload_protocol].total(); } + std::int64_t total_protocol_download() const + { return m_stat[download_protocol].total(); } + + std::int64_t total_transfer(int channel) const + { return m_stat[channel].total(); } + int transfer_rate(int channel) const + { return m_stat[channel].rate(); } + + // this is used to offset the statistics when a + // peer_connection is opened and have some previous + // transfers from earlier connections. + void add_stat(std::int64_t downloaded, std::int64_t uploaded) + { + m_stat[download_payload].offset(downloaded); + m_stat[upload_payload].offset(uploaded); + } + + int last_payload_downloaded() const + { return m_stat[download_payload].counter(); } + int last_payload_uploaded() const + { return m_stat[upload_payload].counter(); } + int last_protocol_downloaded() const + { return m_stat[download_protocol].counter(); } + int last_protocol_uploaded() const + { return m_stat[upload_protocol].counter(); } + + // these are the channels we keep stats for + enum + { + // TODO: 3 everything but payload counters and rates could probably be + // removed from here + upload_payload, + upload_protocol, + download_payload, + download_protocol, + upload_ip_protocol, + download_ip_protocol, + num_channels + }; + + void clear() + { + for (int i = 0; i < num_channels; ++i) + m_stat[i].clear(); + } + + stat_channel const& operator[](int i) const + { + TORRENT_ASSERT(i >= 0 && i < num_channels); + return m_stat[i]; + } + + private: + + stat_channel m_stat[num_channels]; + }; + +} + +#endif // TORRENT_STAT_HPP_INCLUDED diff --git a/include/libtorrent/stat_cache.hpp b/include/libtorrent/stat_cache.hpp new file mode 100644 index 0000000..c24e7ce --- /dev/null +++ b/include/libtorrent/stat_cache.hpp @@ -0,0 +1,108 @@ +/* + +Copyright (c) 2010, 2013-2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STAT_CACHE_HPP +#define TORRENT_STAT_CACHE_HPP + +#include +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/vector.hpp" + +namespace libtorrent { + + struct TORRENT_EXTRA_EXPORT stat_cache + { + stat_cache(); + ~stat_cache(); + + void reserve(int num_files); + + // returns the size of the file unless an error occurs, in which case ec + // is set to indicate the error + std::int64_t get_filesize(file_index_t i, file_storage const& fs + , std::string const& save_path, error_code& ec); + + void set_dirty(file_index_t i); + + void clear(); + + // internal + enum + { + not_in_cache = -1, + file_error = -2 // (first index in m_errors) + }; + + // internal + void set_cache(file_index_t i, std::int64_t size); + void set_error(file_index_t i, error_code const& ec); + + private: + + void set_cache_impl(file_index_t i, std::int64_t size); + void set_error_impl(file_index_t i, error_code const& ec); + + // returns the index to the specified error. Either an existing one or a + // newly added entry + int add_error(error_code const& ec); + + struct stat_cache_t + { + explicit stat_cache_t(std::int64_t s): file_size(s) {} + + // the size of the file. Negative values have special meaning. -1 means + // not-in-cache (i.e. there's no data for this file in the cache). + // lower values (larger negative values) indicate that an error + // occurred while stat()ing the file. The positive value is an index + // into m_errors, that recorded the actual error. + std::int64_t file_size; + }; + + mutable std::mutex m_mutex; + + // one entry per file + aux::vector m_stat_cache; + + // These are the errors that have happened when stating files. Each entry + // that had an error, refers to an index into this vector. + std::vector m_errors; + }; +} + +#endif // TORRENT_STAT_CACHE_HPP diff --git a/include/libtorrent/storage.hpp b/include/libtorrent/storage.hpp new file mode 100644 index 0000000..fd593aa --- /dev/null +++ b/include/libtorrent/storage.hpp @@ -0,0 +1,7 @@ + +#ifndef TORRENT_STORAGE_HPP_INCLUDED +#define TORRENT_STORAGE_HPP_INCLUDED + +#error "the disk I/O subsystem has been overhauled in libtorrent 2.0. storage_interface is no longer a customization point, customize disk_interface instead" + +#endif diff --git a/include/libtorrent/storage_defs.hpp b/include/libtorrent/storage_defs.hpp new file mode 100644 index 0000000..45a090f --- /dev/null +++ b/include/libtorrent/storage_defs.hpp @@ -0,0 +1,159 @@ +/* + +Copyright (c) 2023, Vladimir Golovnev +Copyright (c) 2006-2007, 2009, 2013-2014, 2016-2020, 2022, Arvid Norberg +Copyright (c) 2016, 2021, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STORAGE_DEFS_HPP_INCLUDE +#define TORRENT_STORAGE_DEFS_HPP_INCLUDE + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/download_priority.hpp" +#include +#include + +namespace libtorrent { + + using storage_index_t = aux::strong_typedef; + + // types of storage allocation used for add_torrent_params::storage_mode. + enum storage_mode_t + { + // All pieces will be written to their final position, all files will be + // allocated in full when the torrent is first started. This mode minimizes + // fragmentation but could be a costly operation. + storage_mode_allocate, + + // All pieces will be written to the place where they belong and sparse files + // will be used. This is the recommended, and default mode. + storage_mode_sparse + }; + + // return values from check_fastresume, and move_storage + enum class status_t : std::uint8_t + { + no_error, + fatal_disk_error, + need_full_check, + file_exist, + + // hidden + mask = 0xf, + + // this is not an enum value, but a flag that can be set in the return + // from async_check_files, in case an existing file was found larger than + // specified in the torrent. i.e. it has garbage at the end + // the status_t field is used for this to preserve ABI. + oversized_file = 0x10, + }; + + // internal + inline status_t operator|(status_t lhs, status_t rhs) + { + return status_t(static_cast(lhs) | static_cast(rhs)); + } + inline status_t operator&(status_t lhs, status_t rhs) + { + return status_t(static_cast(lhs) & static_cast(rhs)); + } + inline status_t operator~(status_t lhs) + { + return status_t(~static_cast(lhs)); + } + + // flags for async_move_storage + enum class move_flags_t : std::uint8_t + { + // replace any files in the destination when copying + // or moving the storage + always_replace_files, + + // if any files that we want to copy exist in the destination + // exist, fail the whole operation and don't perform + // any copy or move. There is an inherent race condition + // in this mode. The files are checked for existence before + // the operation starts. In between the check and performing + // the copy, the destination files may be created, in which + // case they are replaced. + fail_if_exist, + + // if any file exist in the target, take those files instead + // of the ones we may have in the source. + dont_replace, + + // don't move any source files, just forget about them + // and begin checking files at new save path + reset_save_path, + + // don't move any source files, just change save path + // and continue working without any checks + reset_save_path_unchecked + }; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + enum deprecated_move_flags_t + { + always_replace_files TORRENT_DEPRECATED_ENUM, + fail_if_exist TORRENT_DEPRECATED_ENUM, + dont_replace TORRENT_DEPRECATED_ENUM + }; +#endif + + // a parameter pack used to construct the storage for a torrent, used in + // disk_interface + struct TORRENT_EXPORT storage_params + { + storage_params(file_storage const& f, file_storage const* mf + , std::string const& sp, storage_mode_t const sm + , aux::vector const& prio + , sha1_hash const& ih) + : files(f) + , mapped_files(mf) + , path(sp) + , mode(sm) + , priorities(prio) + , info_hash(ih) + {} + file_storage const& files; + file_storage const* mapped_files = nullptr; // optional + std::string const& path; + storage_mode_t mode{storage_mode_sparse}; + aux::vector const& priorities; + sha1_hash info_hash; + }; +} + +#endif diff --git a/include/libtorrent/string_util.hpp b/include/libtorrent/string_util.hpp new file mode 100644 index 0000000..0a1765f --- /dev/null +++ b/include/libtorrent/string_util.hpp @@ -0,0 +1,153 @@ +/* + +Copyright (c) 2012, 2014-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2016, Steven Siloti +Copyright (c) 2017, Pavel Pimenov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STRING_UTIL_HPP_INCLUDED +#define TORRENT_STRING_UTIL_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/error_code.hpp" + +#include +#include +#include +#include +#include // for std::array + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT bool is_alpha(char c); + + TORRENT_EXTRA_EXPORT + std::array::digits10> + to_string(std::int64_t n); + + // internal + inline bool is_digit(char c) + { return c >= '0' && c <= '9'; } + inline void ensure_trailing_slash(std::string& url) + { + if (url.empty() || url[url.size() - 1] != '/') + url += '/'; + } + + // internal + TORRENT_EXTRA_EXPORT string_view strip_string(string_view in); + + TORRENT_EXTRA_EXPORT bool is_print(char c); + TORRENT_EXTRA_EXPORT bool is_space(char c); + TORRENT_EXTRA_EXPORT char to_lower(char c); + + TORRENT_EXTRA_EXPORT bool string_begins_no_case(char const* s1, char const* s2); + TORRENT_EXTRA_EXPORT bool string_equal_no_case(string_view s1, string_view s2); + + TORRENT_EXTRA_EXPORT void url_random(span dest); + + TORRENT_EXTRA_EXPORT bool string_ends_with(string_view s1, string_view s2); + + // Returns offset at which src matches target. + // If no sync found, return -1 + TORRENT_EXTRA_EXPORT int search(span src, span target); + + struct listen_interface_t + { + std::string device; + int port; + bool ssl; + bool local; + friend bool operator==(listen_interface_t const& lhs, listen_interface_t const& rhs) + { + return lhs.device == rhs.device + && lhs.port == rhs.port + && lhs.ssl == rhs.ssl + && lhs.local == rhs.local; + } + }; + + // this parses the string that's used as the listen_interfaces setting. + // it is a comma-separated list of IP or device names with ports. For + // example: "eth0:6881,eth1:6881" or "127.0.0.1:6881" + TORRENT_EXTRA_EXPORT std::vector parse_listen_interfaces( + std::string const& in, std::vector& errors); + +#if TORRENT_ABI_VERSION == 1 \ + || !defined TORRENT_DISABLE_LOGGING + TORRENT_EXTRA_EXPORT std::string print_listen_interfaces( + std::vector const& in); +#endif + + // this parses the string that's used as the listen_interfaces setting. + // it is a comma-separated list of IP or device names with ports. For + // example: "eth0:6881,eth1:6881" or "127.0.0.1:6881" + TORRENT_EXTRA_EXPORT void parse_comma_separated_string_port( + std::string const& in, std::vector>& out); + + // this parses the string that's used as the outgoing_interfaces setting. + // it is a comma separated list of IPs and device names. For example: + // "eth0, eth1, 127.0.0.1" + TORRENT_EXTRA_EXPORT void parse_comma_separated_string( + std::string const& in, std::vector& out); + + // strdup is not part of the C standard. Some systems + // don't have it and it won't be available when building + // in strict ANSI mode + TORRENT_EXTRA_EXPORT char* allocate_string_copy(string_view str); + + // searches for separator ('sep') in the string 'last'. + // if found, returns the string_view representing the range from the start of + // `last` up to (but not including) the separator. The second return value is + // the remainder of the string, starting one character after the separator. + // if no separator is found, the whole string is returned and the second + // return value is an empty string_view. + TORRENT_EXTRA_EXPORT std::pair split_string(string_view last, char sep); + + // same as split_string, but if one sub-string starts with a double quote + // (") separators are ignored until the end double-quote. Unless if the + // separator itself is a double quote. + TORRENT_EXTRA_EXPORT std::pair split_string_quotes( + string_view last, char const sep); + + // removes whitespaces at the beginning of the string, in-place + TORRENT_EXTRA_EXPORT void ltrim(std::string& s); + +#if TORRENT_USE_I2P + + TORRENT_EXTRA_EXPORT bool is_i2p_url(std::string const& url); + +#endif +} + +#endif diff --git a/include/libtorrent/string_view.hpp b/include/libtorrent/string_view.hpp new file mode 100644 index 0000000..5460134 --- /dev/null +++ b/include/libtorrent/string_view.hpp @@ -0,0 +1,109 @@ +/* + +Copyright (c) 2016-2017, 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STRING_VIEW_HPP_INCLUDED +#define TORRENT_STRING_VIEW_HPP_INCLUDED + +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +// TODO: replace this by the standard string_view in C++17 + +#if BOOST_VERSION < 106100 +#include +#include // for strchr +namespace libtorrent { + +using string_view = boost::string_ref; +using wstring_view = boost::wstring_ref; + +// internal +inline string_view::size_type find_first_of(string_view const v, char const c + , string_view::size_type pos) +{ + while (pos < v.size()) + { + if (v[pos] == c) return pos; + ++pos; + } + return string_view::npos; +} + +// internal +inline string_view::size_type find_first_of(string_view const v, char const* c + , string_view::size_type pos) +{ + while (pos < v.size()) + { + if (std::strchr(c, v[pos]) != nullptr) return pos; + ++pos; + } + return string_view::npos; +} +} +#else +#include +namespace libtorrent { + +using string_view = boost::string_view; +using wstring_view = boost::wstring_view; + +// internal +inline string_view::size_type find_first_of(string_view const v, char const c + , string_view::size_type pos) +{ + return v.find_first_of(c, pos); +} + +// internal +inline string_view::size_type find_first_of(string_view const v, char const* c + , string_view::size_type pos) +{ + return v.find_first_of(c, pos); +} +} +#endif + +namespace libtorrent { + +inline namespace literals { + + constexpr string_view operator "" _sv(char const* str, std::size_t len) + { return string_view(str, len); } +} +} + + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif diff --git a/include/libtorrent/tailqueue.hpp b/include/libtorrent/tailqueue.hpp new file mode 100644 index 0000000..39e4cd2 --- /dev/null +++ b/include/libtorrent/tailqueue.hpp @@ -0,0 +1,214 @@ +/* + +Copyright (c) 2010, 2013-2017, 2019, 2022, Arvid Norberg +Copyright (c) 2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TAILQUEUE_HPP +#define TORRENT_TAILQUEUE_HPP + +#include "libtorrent/assert.hpp" +#include // for std::move + +namespace libtorrent { + + template + struct tailqueue_node + { + tailqueue_node() : next(nullptr) {} + T* next; + }; + + template + inline N* postinc(N*& e) + { + N* ret = e; + e = static_cast(ret->next); + return ret; + } + + template + struct tailqueue_iterator + { + template friend struct tailqueue; + + T* get() const { return m_current; } + void next() { m_current = m_current->next; } + + private: + explicit tailqueue_iterator(T* cur) + : m_current(cur) {} + // the current element + T* m_current; + }; + + template + struct tailqueue + { + tailqueue(): m_first(nullptr), m_last(nullptr), m_size(0) {} + + tailqueue(tailqueue const&) = delete; + tailqueue(tailqueue&& t): m_first(t.m_first), m_last(t.m_last), m_size(t.m_size) + { + t.m_first = nullptr; + t.m_last = nullptr; + t.m_size = 0; + } + + tailqueue& operator=(tailqueue const&) = delete; + tailqueue& operator=(tailqueue&& t) + { + TORRENT_ASSERT(m_first == nullptr); + TORRENT_ASSERT(m_last == nullptr); + TORRENT_ASSERT(m_size == 0); + + m_first = t.m_first; + m_last = t.m_last; + m_size = t.m_size; + + t.m_first = nullptr; + t.m_last = nullptr; + t.m_size = 0; + return *this; + } + + tailqueue_iterator iterate() const + { return tailqueue_iterator(m_first); } + + tailqueue_iterator iterate() + { return tailqueue_iterator(m_first); } + + void append(tailqueue rhs) & + { + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + TORRENT_ASSERT(rhs.m_last == nullptr || rhs.m_last->next == nullptr); + + if (rhs.m_first == nullptr) return; + + if (m_first == nullptr) + { + swap(rhs); + return; + } + + m_last->next = rhs.m_first; + m_last = rhs.m_last; + m_size += rhs.m_size; + + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + } + + void prepend(tailqueue rhs) & + { + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + TORRENT_ASSERT(rhs.m_last == nullptr || rhs.m_last->next == nullptr); + TORRENT_ASSERT((m_last == nullptr) == (m_first == nullptr)); + TORRENT_ASSERT((rhs.m_last == nullptr) == (rhs.m_first == nullptr)); + + if (rhs.m_first == nullptr) return; + + if (m_first == nullptr) + { + swap(rhs); + return; + } + + swap(rhs); + append(std::move(rhs)); + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + TORRENT_ASSERT(rhs.m_last == nullptr || rhs.m_last->next == nullptr); + } + + T* pop_front() + { + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + T* e = m_first; + m_first = m_first->next; + if (e == m_last) m_last = nullptr; + e->next = nullptr; + --m_size; + return e; + } + void push_front(T* e) & + { + TORRENT_ASSERT(e->next == nullptr); + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + e->next = m_first; + m_first = e; + if (!m_last) m_last = e; + ++m_size; + } + void push_back(T* e) & + { + TORRENT_ASSERT(e->next == nullptr); + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + if (m_last) m_last->next = e; + else m_first = e; + m_last = e; + e->next = nullptr; + ++m_size; + } + T* get_all() & + { + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + T* e = m_first; + m_first = nullptr; + m_last = nullptr; + m_size = 0; + return e; + } + void swap(tailqueue& rhs) + { + std::swap(m_first, rhs.m_first); + std::swap(m_last, rhs.m_last); + std::swap(m_size, rhs.m_size); + } + int size() const { TORRENT_ASSERT(m_size >= 0); return m_size; } + bool empty() const { TORRENT_ASSERT(m_size >= 0); return m_size == 0; } + T* first() const& { TORRENT_ASSERT(m_size > 0); return m_first; } + T* last() const& { TORRENT_ASSERT(m_size > 0); return m_last; } + private: + T* m_first; + T* m_last; + int m_size; + }; +} + +namespace std { + +template +void swap(libtorrent::tailqueue& lhs, libtorrent::tailqueue& rhs) +{ + lhs.swap(rhs); +} + +} + +#endif // TAILQUEUE_HPP diff --git a/include/libtorrent/time.hpp b/include/libtorrent/time.hpp new file mode 100644 index 0000000..7cf3880 --- /dev/null +++ b/include/libtorrent/time.hpp @@ -0,0 +1,92 @@ +/* + +Copyright (c) 2007, 2009, 2014-2017, 2019-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TIME_HPP_INCLUDED +#define TORRENT_TIME_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include +#include + +#if defined TORRENT_BUILD_SIMULATOR +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include "simulator/simulator.hpp" +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + +namespace libtorrent { + +#if defined TORRENT_BUILD_SIMULATOR + using clock_type = sim::chrono::high_resolution_clock; +#else + using clock_type = std::chrono::high_resolution_clock; +#endif + + using time_point = clock_type::time_point; + using time_duration = clock_type::duration; + + // 32 bit versions of time_point and duration, with second resolution + using milliseconds32 = std::chrono::duration>; + using seconds32 = std::chrono::duration; + using minutes32 = std::chrono::duration>; + using time_point32 = std::chrono::time_point; + + using seconds = std::chrono::seconds; + using milliseconds = std::chrono::milliseconds; + using microseconds = std::chrono::microseconds; + using minutes = std::chrono::minutes; + using hours = std::chrono::hours; + using std::chrono::duration_cast; + using std::chrono::time_point_cast; + + // internal + inline time_point min_time() { return (time_point::min)(); } + + // internal + inline time_point max_time() { return (time_point::max)(); } + + template + std::int64_t total_seconds(T td) + { return duration_cast(td).count(); } + + template + std::int64_t total_milliseconds(T td) + { return duration_cast(td).count(); } + + template + std::int64_t total_microseconds(T td) + { return duration_cast(td).count(); } + +} + +#endif // TORRENT_TIME_HPP_INCLUDED diff --git a/include/libtorrent/torrent.hpp b/include/libtorrent/torrent.hpp new file mode 100644 index 0000000..226b6a9 --- /dev/null +++ b/include/libtorrent/torrent.hpp @@ -0,0 +1,1814 @@ +/* + +Copyright (c) 2003-2022, Arvid Norberg +Copyright (c) 2003, Daniel Wallin +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016-2020, Alden Torres +Copyright (c) 2017, AllSeeingEyeTolledEweSew +Copyright (c) 2017, Falcosc +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2018-2019, Steven Siloti +Copyright (c) 2018, d-komarov +Copyright (c) 2019, ghbplayer +Copyright (c) 2020, Paul-Louis Ageneau +Copyright (c) 2021, AdvenT +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_HPP_INCLUDE +#define TORRENT_TORRENT_HPP_INCLUDE + +#include +#include +#include +#include +#include +#include // for numeric_limits +#include // for unique_ptr + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/fwd.hpp" +#include "libtorrent/optional.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/peer_list.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/hash_picker.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/bandwidth_limit.hpp" +#include "libtorrent/aux_/bandwidth_queue_entry.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/peer_class_set.hpp" +#include "libtorrent/link.hpp" +#include "libtorrent/vector_utils.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/piece_block.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/aux_/file_progress.hpp" +#include "libtorrent/aux_/suggest_piece.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/deferred_handler.hpp" +#include "libtorrent/aux_/allocating_handler.hpp" +#include "libtorrent/aux_/announce_entry.hpp" +#include "libtorrent/extensions.hpp" // for add_peer_flags_t +#include "libtorrent/ssl.hpp" + +#ifdef TORRENT_SSL_PEERS +// there is no forward declaration header for asio +namespace boost { +namespace asio { +namespace ssl { + class context; + class verify_context; +} +} +} +#endif + +#if TORRENT_COMPLETE_TYPES_REQUIRED +#include "libtorrent/peer_connection.hpp" +#endif + +// define as 0 to disable. 1 enables debug output of the pieces and requested +// blocks. 2 also enables trace output of the time critical piece picking +// logic +#define TORRENT_DEBUG_STREAMING 0 + +namespace libtorrent { + + class http_parser; + struct tracker_request; + struct bt_peer_connection; + + using web_seed_flag_t = flags::bitfield_flag; + + // internal + enum class waste_reason + { + piece_timed_out, piece_cancelled, piece_unknown, piece_seed + , piece_end_game, piece_closing + , max + }; + + TORRENT_EXTRA_EXPORT std::int64_t calc_bytes(file_storage const& fs, piece_count const& pc); + +#ifndef TORRENT_DISABLE_STREAMING + struct time_critical_piece + { + // when this piece was first requested + time_point first_requested; + // when this piece was last requested + time_point last_requested; + // by what time we want this piece + time_point deadline; + // 1 = send alert with piece data when available + deadline_flags_t flags; + // how many peers it's been requested from + int peers; + // the piece index + piece_index_t piece; +#if TORRENT_DEBUG_STREAMING > 0 + // the number of multiple requests are allowed + // to blocks still not downloaded (debugging only) + int timed_out; +#endif + bool operator<(time_critical_piece const& rhs) const + { return deadline < rhs.deadline; } + }; +#endif // TORRENT_DISABLE_STREAMING + + // this is the internal representation of web seeds + struct web_seed_t : web_seed_entry + { + explicit web_seed_t(web_seed_entry const& wse); + web_seed_t(std::string const& url_, web_seed_entry::type_t type_ + , std::string const& auth_ = std::string() + , web_seed_entry::headers_t const& extra_headers_ = web_seed_entry::headers_t()); + + // if this is > now, we can't reconnect yet + time_point32 retry = aux::time_now32(); + + // if the hostname of the web seed has been resolved, + // these are its IP addresses + std::vector endpoints; + + // this is the peer_info field used for the + // connection, just to count hash failures + // it's also used to hold the peer_connection + // pointer, when the web seed is connected + ipv4_peer peer_info{tcp::endpoint(), true, {}}; + + // this is initialized to true, but if we discover the + // server not to support it, it's set to false, and we + // make larger requests. + bool supports_keepalive = true; + + // this indicates whether or not we're resolving the + // hostname of this URL + bool resolving = false; + + // if the user wanted to remove this while + // we were resolving it. In this case, we set + // the removed flag to true, to make the resolver + // callback remove it + bool removed = false; + + // this indicates whether this web seed has any files. A server that only + // redirects to other servers for instance, may not have any files and + // once we've seen all redirects, there's no point in connecting to it + // again. + bool interesting = true; + + // if this is true, this URL was created by a redirect and should not be + // saved in the resume data + bool ephemeral = false; + + // this is set to true when this web seed was created from a redirect + // from a global IP, and SSRF mitigation is enabled. It prevents this + // web seed from resolving to any local network IPs. + bool no_local_ips = false; + + // This means we want to preserve the web seed in resume data, but not + // use it for the remainder of this session. For example: + // this web seed maye have responded with a redirect. The redirected + // URLs have been added as ephemeral. If an ephemeral URL is redirected, + // its web seed entry is removed. It could also mean we don't support + // the URL protocol, but maybe another client may support it. + bool disabled = false; + + // if the web server doesn't support keepalive or a block request was + // interrupted, the block received so far is kept here for the next + // connection to pick up + peer_request restart_request = { piece_index_t(-1), -1, -1}; + aux::vector restart_piece; + + // this maps file index to a URL it has been redirected to. If an entry is + // missing, it means it has not been redirected and the full path should + // be constructed normally based on the filename. All redirections are + // relative to the web seed hostname root. + std::map redirects; + + // if this bitfield is non-empty, it represents the files this web server + // has. + typed_bitfield have_files; +#if defined __GNUC__ && defined _GLIBCXX_DEBUG + // this works around a bug in libstdc++'s checked iterators + // http://stackoverflow.com/questions/22915325/avoiding-self-assignment-in-stdshuffle + web_seed_t& operator=(web_seed_t&& rhs) noexcept + { + if (&rhs == this) return *this; + + web_seed_entry::operator=(std::move(rhs)); + retry = std::move(rhs.retry); + endpoints = std::move(rhs.endpoints); + peer_info = std::move(rhs.peer_info); + supports_keepalive = std::move(rhs.supports_keepalive); + resolving = std::move(rhs.resolving); + removed = std::move(rhs.removed); + ephemeral = std::move(rhs.ephemeral); + no_local_ips = std::move(rhs.no_local_ips); + disabled = std::move(rhs.disabled); + restart_request = std::move(rhs.restart_request); + restart_piece = std::move(rhs.restart_piece); + redirects = std::move(rhs.redirects); + have_files = std::move(rhs.have_files); + return *this; + } + + web_seed_t& operator=(web_seed_t const&) = default; + web_seed_t(web_seed_t const&) = default; +#endif + }; + + struct TORRENT_EXTRA_EXPORT torrent_hot_members + { + torrent_hot_members(aux::session_interface& ses + , add_torrent_params const& p, bool session_paused); + + protected: + // the piece picker. This is allocated lazily. When we don't + // have anything in the torrent (for instance, if it hasn't + // been started yet) or if we have everything, there is no + // picker. It's allocated on-demand the first time we need + // it in torrent::need_picker(). In order to tell the + // difference between having everything and nothing in + // the case there is no piece picker, see m_have_all. + std::unique_ptr m_picker; + + std::unique_ptr m_hash_picker; + + // TODO: make this a raw pointer. perhaps keep the shared_ptr + // around further down the object to maintain an owner + std::shared_ptr m_torrent_file; + + // This is the sum of all non-pad file sizes. In the next major version + // this is stored in file_storage and no longer need to be kept here. + std::int64_t m_size_on_disk = 0; + + // a back reference to the session + // this torrent belongs to. + aux::session_interface& m_ses; + + // this vector is sorted at all times, by the pointer value. + // use sorted_insert() and sorted_find() on it. The GNU STL + // implementation on Darwin uses significantly less memory to + // represent a vector than a set, and this set is typically + // relatively small, and it's cheap to copy pointers. + aux::vector m_connections; + + // the scrape data from the tracker response, this + // is optional and may be 0xffffff + std::uint32_t m_complete:24; + + // set to true when this torrent may not download anything + bool m_upload_mode:1; + + // this is set to false as long as the connections + // of this torrent haven't been initialized. If we + // have metadata from the start, connections are + // initialized immediately, if we didn't have metadata, + // they are initialized right after files_checked(). + // valid_resume_data() will return false as long as + // the connections aren't initialized, to avoid + // them from altering the piece-picker before it + // has been initialized with files_checked(). + bool m_connections_initialized:1; + + // is set to true when the torrent has + // been aborted. + bool m_abort:1; + + // is true if this torrent has allows having peers + bool m_paused:1; + + // is true if the session is paused, in which case the torrent is + // effectively paused as well. + bool m_session_paused:1; + +#ifndef TORRENT_DISABLE_SHARE_MODE + // this is set when the torrent is in share-mode + bool m_share_mode:1; +#endif + + // this is true if we have all pieces. If it's false, + // it means we either don't have any pieces, or, if + // there is a piece_picker object present, it contains + // the state of how many pieces we have + bool m_have_all:1; + + // set to true when this torrent has been paused but + // is waiting to finish all current download requests + // before actually closing all connections, when in graceful pause mode, + // m_paused is also true. + bool m_graceful_pause_mode:1; + + // state subscription. If set, a pointer to this torrent will be added + // to the session_impl::m_torrent_lists[torrent_state_updates] + // whenever this torrent's state changes (any state). + bool m_state_subscription:1; + + // the maximum number of connections for this torrent + std::uint32_t m_max_connections:24; + + // the state of this torrent (queued, checking, downloading, etc.) + std::uint32_t m_state:3; + + std::unique_ptr m_peer_list; + }; + + // a torrent is a class that holds information + // for a specific download. It updates itself against + // the tracker + struct TORRENT_EXTRA_EXPORT torrent + : private single_threaded + , private torrent_hot_members + , request_callback + , peer_class_set + , std::enable_shared_from_this + { + // add_torrent_params may contain large merkle trees that are best + // moved. Deleting the const& overload ensures that it's always moved in. + torrent(aux::session_interface& ses, bool session_paused, add_torrent_params&& p); + torrent(aux::session_interface&, bool, add_torrent_params const& p) = delete; + ~torrent() override; + + // This may be called from multiple threads + info_hash_t const& info_hash() const { return m_info_hash; } + + bool is_deleted() const { return m_deleted; } + + // starts the announce timer + void start(); + + void added() + { + TORRENT_ASSERT(m_added == false); + m_added = true; + update_gauge(); + } + + void removed() + { + TORRENT_ASSERT(m_added == true); + m_added = false; + set_queue_position(no_pos); + // make sure we decrement the gauge counter for this torrent + update_gauge(); + } + + bool is_added() const { return m_added; } + + // returns which stats gauge this torrent currently + // has incremented. + int current_stats_state() const; + +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extension(std::shared_ptr); + void remove_extension(std::shared_ptr); + void add_extension_fun(std::function(torrent_handle const&, client_data_t)> const& ext + , client_data_t userdata); + void notify_extension_add_peer(tcp::endpoint const& ip + , peer_source_flags_t src, add_peer_flags_t flags); +#endif + + peer_connection* find_lowest_ranking_peer() const; + +#if TORRENT_USE_ASSERTS + bool has_peer(peer_connection const* p) const + { return sorted_find(m_connections, p) != m_connections.end(); } + bool is_single_thread() const { return single_threaded::is_single_thread(); } +#endif + + // this is called when the torrent has metadata. + // it will initialize the storage and the piece-picker + void init(); + + void load_merkle_trees(aux::vector, file_index_t> t + , aux::vector, file_index_t> mask + , aux::vector, file_index_t> verified); + + // find the peer that introduced us to the given endpoint. This is + // used when trying to holepunch. We need the introducer so that we + // can send a rendezvous connect message + bt_peer_connection* find_introducer(tcp::endpoint const& ep) const; + + // if we're connected to a peer at ep, return its peer connection + // only count BitTorrent peers + bt_peer_connection* find_peer(tcp::endpoint const& ep) const; + peer_connection* find_peer(peer_id const& pid); + + // checks to see if this peer id is used in one of our own outgoing + // connections. + bool is_self_connection(peer_id const& pid) const; + + void on_resume_data_checked(status_t status, storage_error const& error); + void on_force_recheck(status_t status, storage_error const& error); + void on_piece_hashed(aux::vector block_hashes + , piece_index_t piece, sha1_hash const& piece_hash + , storage_error const& error); + void files_checked(); + void start_checking(); + + void start_announcing(); + void stop_announcing(); + + void send_upload_only(); + +#ifndef TORRENT_DISABLE_SHARE_MODE + void send_share_mode(); + void set_share_mode(bool s); + bool share_mode() const { return m_share_mode; } +#endif + + // TODO: make graceful pause also finish all sending blocks + // before disconnecting + bool graceful_pause() const { return m_graceful_pause_mode; } + + torrent_flags_t flags() const; + void set_flags(torrent_flags_t flags, torrent_flags_t mask); + + void set_upload_mode(bool b); + bool upload_mode() const { return m_upload_mode || m_graceful_pause_mode; } + bool is_upload_only() const { return is_finished() || upload_mode(); } + + int seed_rank(aux::session_settings const& s) const; + + void add_piece(piece_index_t piece, char const* data, add_piece_flags_t flags); + void add_piece_async(piece_index_t piece, std::vector data, add_piece_flags_t flags); + void on_disk_write_complete(storage_error const& error + , peer_request const& p); + + void set_progress_ppm(int p) { m_progress_ppm = std::uint32_t(p); } + struct read_piece_struct + { + boost::shared_array piece_data; + int blocks_left; + bool fail; + error_code error; + }; + void read_piece(piece_index_t); + void on_disk_read_complete(disk_buffer_holder, storage_error const& + , peer_request const&, std::shared_ptr); + + storage_mode_t storage_mode() const; + + // this will flag the torrent as aborted. The main + // loop in session_impl will check for this state + // on all torrents once every second, and take + // the necessary actions then. + void abort(); + bool is_aborted() const { return m_abort; } + void panic(); + + void new_external_ip(); + + torrent_status::state_t state() const + { return torrent_status::state_t(m_state); } + void set_state(torrent_status::state_t s); + + aux::session_settings const& settings() const; + aux::session_interface& session() { return m_ses; } + + void set_sequential_download(bool sd); + bool is_sequential_download() const + { return m_sequential_download || m_auto_sequential; } + + void queue_up(); + void queue_down(); + void set_queue_position(queue_position_t p); + queue_position_t queue_position() const { return m_sequence_number; } + // used internally + void set_queue_position_impl(queue_position_t const p) + { + if (m_sequence_number == p) return; + m_sequence_number = p; + state_updated(); + } + + void second_tick(int tick_interval_ms); + + // see if we need to connect to web seeds, and if so, + // connect to them + void maybe_connect_web_seeds(); + + std::string name() const; + + stat statistics() const { return m_stat; } + boost::optional bytes_left() const; + + void bytes_done(torrent_status& st, status_flags_t) const; + + void sent_bytes(int bytes_payload, int bytes_protocol); + void received_bytes(int bytes_payload, int bytes_protocol); + void trancieve_ip_packet(int bytes, bool ipv6); + void sent_syn(bool ipv6); + void received_synack(bool ipv6); + + void set_ip_filter(std::shared_ptr ipf); + void privileged_port_updated(); + void port_filter_updated(); + ip_filter const* get_ip_filter() { return m_ip_filter.get(); } + + std::string resolve_filename(file_index_t file) const; + void handle_exception(); + + enum class disk_class { none, write }; + void handle_disk_error(string_view job_name + , storage_error const& error, peer_connection* c = nullptr + , disk_class rw = disk_class::none); + void handle_inconsistent_hashes(piece_index_t piece); + void clear_error(); + + void set_error(error_code const& ec, file_index_t file); + bool has_error() const { return !!m_error; } + error_code error() const { return m_error; } + + void flush_cache(); + void pause(pause_flags_t flags = {}); + void resume(); + + void set_session_paused(bool b); + void set_paused(bool b, pause_flags_t flags = {}); + void set_announce_to_dht(bool b) { m_announce_to_dht = b; } + void set_announce_to_trackers(bool b) { m_announce_to_trackers = b; } + void set_announce_to_lsd(bool b) { m_announce_to_lsd = b; } + + void stop_when_ready(bool b); + + time_point32 started() const { return m_started; } + void step_session_time(int seconds); + void do_pause(bool was_paused = false); + void do_resume(); + + seconds32 finished_time() const; + seconds32 active_time() const; + seconds32 seeding_time() const; + seconds32 upload_mode_time() const; + + bool is_paused() const; + bool is_torrent_paused() const { return m_paused; } + void force_recheck(); + void save_resume_data(resume_data_flags_t flags); + + bool need_save_resume_data(resume_data_flags_t flags) const + { + return bool(m_need_save_resume_data & flags); + } + + void set_need_save_resume(resume_data_flags_t const flag) + { + // every category sets this bit. TODO: make this flag a combination + // of the other ones + m_need_save_resume_data |= torrent_handle::only_if_modified; + + if (m_need_save_resume_data & flag) return; + m_need_save_resume_data |= flag; + state_updated(); + } + + bool is_auto_managed() const { return m_auto_managed; } + void auto_managed(bool a); + + bool should_check_files() const; + + bool delete_files(remove_flags_t options); + void peers_erased(std::vector const& peers); + +#if TORRENT_ABI_VERSION == 1 +#if !TORRENT_NO_FPU + void file_progress_float(aux::vector& fp); +#endif +#endif // TORRENT_ABI_VERSION + + void post_piece_availability(); + void piece_availability(aux::vector& avail) const; + + void set_piece_priority(piece_index_t index, download_priority_t priority); + download_priority_t piece_priority(piece_index_t index) const; + + void prioritize_pieces(aux::vector const& pieces); + void prioritize_piece_list(std::vector> const& pieces); + void piece_priorities(aux::vector*) const; + + void set_file_priority(file_index_t index, download_priority_t priority); + download_priority_t file_priority(file_index_t index) const; + + void on_file_priority(storage_error const& err, aux::vector prios); + void prioritize_files(aux::vector files); + void file_priorities(aux::vector*) const; + +#ifndef TORRENT_DISABLE_STREAMING + void cancel_non_critical(); + void set_piece_deadline(piece_index_t piece, int t, deadline_flags_t flags); + void reset_piece_deadline(piece_index_t piece); + void clear_time_critical(); +#endif // TORRENT_DISABLE_STREAMING + + void update_piece_priorities( + aux::vector const& file_prios); + + void post_status(status_flags_t flags); + void status(torrent_status* st, status_flags_t flags); + + // this torrent changed state, if the user is subscribing to + // it, add it to the m_state_updates list in session_impl + void state_updated(); + + void file_progress(aux::vector& fp, file_progress_flags_t flags); + void post_file_progress(file_progress_flags_t flags); + +#if TORRENT_ABI_VERSION == 1 + void use_interface(std::string net_interface); +#endif + + void connect_to_url_seed(std::list::iterator); + bool connect_to_peer(torrent_peer*, bool ignore_limit = false); + + int priority() const; +#if TORRENT_ABI_VERSION == 1 + void set_priority(int prio); +#endif // TORRENT_ABI_VERSION + +// -------------------------------------------- + // BANDWIDTH MANAGEMENT + + void set_upload_limit(int limit); + int upload_limit() const; + void set_download_limit(int limit); + int download_limit() const; + + peer_class_t peer_class() const { return m_peer_class; } + + void set_max_uploads(int limit, bool state_update = true); + int max_uploads() const { return int(m_max_uploads); } + void set_max_connections(int limit, bool state_update = true); + int max_connections() const { return int(m_max_connections); } + +// -------------------------------------------- + // PEER MANAGEMENT + + // A web seed entry that's added because of a redirect is flagged as + // ephemeral. We don't want to save these in the resume data. + static constexpr web_seed_flag_t ephemeral = 0_bit; + // A web seed entry with this flag set is not allowed to resolve to an + // IP on a local network. This is part of SSRF mitigation, as it may be + // malicious + static constexpr web_seed_flag_t no_local_ips = 1_bit; + + // add_web_seed won't add duplicates. If we have already added an entry + // with this URL, we'll get back the existing entry + web_seed_t* add_web_seed(std::string const& url + , web_seed_t::type_t type + , std::string const& auth = std::string() + , web_seed_t::headers_t const& extra_headers = web_seed_entry::headers_t() + , web_seed_flag_t flags = {}); + + void remove_web_seed(std::string const& url, web_seed_t::type_t type); + + void retry_web_seed(peer_connection* p, boost::optional retry = boost::none); + + void remove_web_seed_conn(peer_connection* peer); + + std::set web_seeds(web_seed_entry::type_t type) const; + + bool free_upload_slots() const + { return m_num_uploads < m_max_uploads; } + + bool choke_peer(peer_connection& c); + bool unchoke_peer(peer_connection& c, bool optimistic = false); + + void trigger_unchoke() noexcept; + void trigger_optimistic_unchoke() noexcept; + + // used by peer_connection to attach itself to a torrent + // since incoming connections don't know what torrent + // they're a part of until they have received an info_hash. + // false means attach failed + bool attach_peer(peer_connection* p); + + // this will remove the peer and make sure all + // the pieces it had have their reference counter + // decreased in the piece_picker + void remove_peer(std::shared_ptr p) noexcept; + + // cancel requests to this block from any peer we're + // connected to on this torrent + void cancel_block(piece_block block); + + bool want_tick() const; + void update_want_tick(); + void update_state_list(); + + bool want_peers() const; + bool want_peers_download() const; + bool want_peers_finished() const; + + void update_want_peers(); + void update_want_scrape(); + void update_gauge(); + + bool try_connect_peer(); + torrent_peer* add_peer(tcp::endpoint const& adr + , peer_source_flags_t source, pex_flags_t flags = {}); + bool ban_peer(torrent_peer* tp); + void update_peer_port(int port, torrent_peer* p, peer_source_flags_t src); + void set_seed(torrent_peer* p, bool s); + void clear_failcount(torrent_peer* p); + std::pair find_peers(address const& a); + + // the number of peers that belong to this torrent + int num_peers() const { return int(m_connections.size() - m_peers_to_disconnect.size()); } + int num_seeds() const; + int num_downloaders() const; + + using peer_iterator = std::vector::iterator; + using const_peer_iterator = std::vector::const_iterator; + + const_peer_iterator begin() const { return m_connections.begin(); } + const_peer_iterator end() const { return m_connections.end(); } + + peer_iterator begin() { return m_connections.begin(); } + peer_iterator end() { return m_connections.end(); } + +#if TORRENT_ABI_VERSION == 1 + void get_full_peer_list(std::vector* v) const; +#endif + void post_peer_info(); + void get_peer_info(std::vector* v); + void get_download_queue(std::vector* queue) const; + void post_download_queue(); + + void update_auto_sequential(); + private: + void remove_connection(peer_connection const* p); + public: +// -------------------------------------------- + // TRACKER MANAGEMENT + + // these are callbacks called by the tracker_connection instance + // (either http_tracker_connection or udp_tracker_connection) + // when this torrent got a response from its tracker request + // or when a failure occurred + void tracker_response( + tracker_request const& r + , address const& tracker_ip + , std::list
    const& tracker_ips + , struct tracker_response const& resp) override; + void tracker_request_error(tracker_request const& r + , error_code const& ec, operation_t op, const std::string& msg + , seconds32 retry_interval) override; + void tracker_warning(tracker_request const& req + , std::string const& msg) override; + void tracker_scrape_response(tracker_request const& req + , int complete, int incomplete, int downloaded, int downloaders) override; + + void update_scrape_state(); + +#if TORRENT_ABI_VERSION == 1 + // if no password and username is set + // this will return an empty string, otherwise + // it will concatenate the login and password + // ready to be sent over http (but without + // base64 encoding). + std::string tracker_login() const; +#endif + + // generate the tracker key for this torrent. + // The key is passed to http trackers as ``&key=``. + std::uint32_t tracker_key() const; + + // if we need a connect boost, connect some peers + // immediately + void do_connect_boost(); + + // forcefully sets next_announce to the current time + void force_tracker_request(time_point, int tracker_idx, reannounce_flags_t flags); + void scrape_tracker(int idx, bool user_triggered); + void announce_with_tracker(event_t = event_t::none); + +#ifndef TORRENT_DISABLE_DHT + void dht_announce(); +#endif + +#if TORRENT_ABI_VERSION == 1 + // sets the username and password that will be sent to + // the tracker + void set_tracker_login(std::string const& name, std::string const& pw); +#endif + + aux::announce_entry* find_tracker(std::string const& url); +// -------------------------------------------- + // PIECE MANAGEMENT + +#ifndef TORRENT_DISABLE_SHARE_MODE + void recalc_share_mode(); +#endif + +#ifndef TORRENT_DISABLE_SUPERSEEDING + bool super_seeding() const + { + // we're not super seeding if we're not a seed + return m_super_seeding; + } + + void set_super_seeding(bool on); + piece_index_t get_piece_to_super_seed(typed_bitfield const&); +#endif + + // returns true if we have downloaded the given piece + // but not necessarily flushed it to disk + bool have_piece(piece_index_t index) const + { + if (!valid_metadata()) return false; + if (!has_picker()) return m_have_all; + return m_picker->have_piece(index); + } + + // returns true if we have downloaded the given piece + bool user_have_piece(piece_index_t index) const + { + if (!valid_metadata()) return false; + if (index < piece_index_t{0} || index >= m_torrent_file->end_piece()) return false; + if (!has_picker()) return m_have_all; + return m_picker->have_piece(index); + } + +#ifndef TORRENT_DISABLE_PREDICTIVE_PIECES + // a predictive piece is a piece that we might + // not have yet, but still announced to peers, anticipating that + // we'll have it very soon + bool is_predictive_piece(piece_index_t index) const + { + return std::binary_search(m_predictive_pieces.begin(), m_predictive_pieces.end(), index); + } +#endif // TORRENT_DISABLE_PREDICTIVE_PIECES + + private: + + // called when we learn that we have a piece + // only once per piece + void we_have(piece_index_t index, bool loading_resume = false); + + // process the v2 block hashes for a piece + boost::tribool on_blocks_hashed(piece_index_t piece + , span block_hashes); + + public: + + // the number of pieces that have passed + // hash check, but aren't necessarily + // flushed to disk yet + int num_have() const + { + // pretend we have every piece when in seed mode + if (m_seed_mode) return m_torrent_file->num_pieces(); + if (has_picker()) return m_picker->have().num_pieces; + if (m_have_all) return m_torrent_file->num_pieces(); + return 0; + } + + // when we get a have message, this is called for that piece + void peer_has(piece_index_t index, peer_connection const* peer); + + // when we get a bitfield message, this is called for that piece + void peer_has(typed_bitfield const& bits, peer_connection const* peer); + + void peer_has_all(peer_connection const* peer); + + void peer_lost(piece_index_t index, peer_connection const* peer); + void peer_lost(typed_bitfield const& bits + , peer_connection const* peer); + + int block_size() const + { + return valid_metadata() + ? (std::min)(m_torrent_file->piece_length(), default_block_size) + : default_block_size; + } + peer_request to_req(piece_block const& p) const; + + void disconnect_all(error_code const& ec, operation_t op); + int disconnect_peers(int num, error_code const& ec); + + // called every time a block is marked as finished in the + // piece picker. We might have completed the torrent and + // we can delete the piece picker + void maybe_done_flushing(); + + // this is called when the torrent has completed + // the download. It will post an event, disconnect + // all seeds and let the tracker know we're finished. + void completed(); + +#if TORRENT_USE_I2P + void on_i2p_resolve(error_code const& ec, char const* dest); + bool is_i2p() const { return m_i2p; } +#endif + + // this is the asio callback that is called when a name + // lookup for a PEER is completed. + void on_peer_name_lookup(error_code const& + , std::vector
    const& + , int port + , protocol_version); + + // this is the asio callback that is called when a name + // lookup for a WEB SEED is completed. + void on_name_lookup(error_code const& + , std::vector
    const& + , int port + , std::list::iterator); + + void connect_web_seed(std::list::iterator web, tcp::endpoint a); + + // this is the asio callback that is called when a name + // lookup for a proxy for a web seed is completed. + void on_proxy_name_lookup(error_code const& e + , std::vector
    const& addrs + , std::list::iterator web, int port); + + // re-evaluates whether this torrent should be considered inactive or not + void on_inactivity_tick(error_code const& ec); + + + // calculate the instantaneous inactive state (the externally facing + // inactive state is not instantaneous, but low-pass filtered) + bool is_inactive_internal() const; + + // remove a web seed, or schedule it for removal in case there + // are outstanding operations on it + void remove_web_seed_iter(std::list::iterator web); + + // this is called when the torrent has finished. i.e. + // all the pieces we have not filtered have been downloaded. + // If no pieces are filtered, this is called first and then + // completed() is called immediately after it. + void finished(); + + // This is the opposite of finished. It is called if we used + // to be finished but enabled some files for download so that + // we wasn't finished anymore. + void resume_download(); + + void verify_piece(piece_index_t piece); + void on_piece_verified(aux::vector block_hashes + , piece_index_t piece + , sha1_hash const& piece_hash, storage_error const& error); + + // this is called whenever a peer in this swarm becomes interesting + // it is responsible for issuing a block request, if appropriate + void peer_is_interesting(peer_connection& c); + + // piece_passed is called when a piece passes the hash check + // this will tell all peers that we just got his piece + // and also let the piece picker know that we have this piece + // so it wont pick it for download + void piece_passed(piece_index_t index); + + // piece_failed is called when a piece fails the hash check + // for failures detected with v2 hashes the failing blocks(s) + // are specified in blocks + // *blocks must be sorted in ascending order* + void piece_failed(piece_index_t index, std::vector blocks = std::vector()); + + // the peers in "peers" participated in sending a bad piece. If + // "known_bad_peer" is true, we know for sure the peers are guilty, + // otherwise only one may be guilty (meaning we can't unconditionally + // disconnect) + void penalize_peers(std::set const& peers + , piece_index_t index + , bool known_bad_peer); + + // this is the handler for hash failure piece synchronization + // i.e. resetting the piece + void on_piece_sync(piece_index_t piece, std::vector const& blocks); + + // this is the handler for write failure piece synchronization + void on_piece_fail_sync(piece_index_t piece, piece_block b); + + void add_redundant_bytes(int b, waste_reason reason); + void add_failed_bytes(int b); + + // this is true if we have all the pieces, but not necessarily flushed them to disk + bool is_seed() const; + + // this is true if we have all the pieces that we want + // the pieces don't necessarily need to be flushed to disk + bool is_finished() const; + + bool is_inactive() const; + + std::string save_path() const; + aux::alert_manager& alerts() const; + piece_picker& picker() + { + TORRENT_ASSERT(m_picker.get()); + return *m_picker; + } + piece_picker const& picker() const + { + TORRENT_ASSERT(m_picker.get()); + return *m_picker; + } + void need_picker(); + bool has_picker() const + { + return m_picker.get() != nullptr; + } + + hash_picker& get_hash_picker() + { + TORRENT_ASSERT(m_hash_picker.get()); + return *m_hash_picker; + } + hash_picker const& get_hash_picker() const + { + TORRENT_ASSERT(m_hash_picker.get()); + return *m_hash_picker; + } + + void need_hash_picker(); + bool has_hash_picker() const + { + return m_hash_picker.get() != nullptr; + } + + void update_max_failcount() + { + if (!m_peer_list) return; + torrent_state st = get_peer_list_state(); + m_peer_list->set_max_failcount(&st); + } + int num_known_peers() const { return m_peer_list ? m_peer_list->num_peers() : 0; } + int num_connect_candidates() const { return m_peer_list ? m_peer_list->num_connect_candidates() : 0; } + + void clear_peers(); + + bool has_storage() const { return bool(m_storage); } + storage_index_t storage() const { return m_storage; } + + torrent_info const& torrent_file() const + { return *m_torrent_file; } + + hash_request pick_hashes(peer_connection* peer); + std::vector get_hashes(hash_request const& req) const; + bool add_hashes(hash_request const& req, span hashes); + void hashes_rejected(hash_request const& req); + void verify_block_hashes(piece_index_t index); + + std::shared_ptr get_torrent_file() const; + + std::shared_ptr get_torrent_copy_with_hashes() const; + + std::vector> get_piece_layers() const; + + void post_trackers(); + std::vector trackers() const; + + // this sets all the "enabled" states on all trackers, giving them + // all one more chance of being tried + void enable_all_trackers(); + + void replace_trackers(std::vector const& urls); + + // returns true if the tracker was added, and false if it was already + // in the tracker list (in which case the source was added to the + // entry in the list) + bool add_tracker(announce_entry const& url); + + torrent_handle get_handle(); + + void write_resume_data(resume_data_flags_t const flags, add_torrent_params& ret) const; + + void seen_complete() { m_last_seen_complete = aux::posix_time(); } + int time_since_complete() const { return int(aux::posix_time() - m_last_seen_complete); } + time_t last_seen_complete() const { return m_last_seen_complete; } + + template + void wrap(Fun f, Args&&... a); + + // LOGGING +#ifndef TORRENT_DISABLE_LOGGING + bool should_log() const override; + void debug_log(const char* fmt, ...) const noexcept override TORRENT_FORMAT(2,3); + + void log_to_all_peers(char const* message); + time_point m_dht_start_time; +#endif + + // DEBUG +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + +// -------------------------------------------- + // RESOURCE MANAGEMENT + + // flags are defined in storage.hpp + void move_storage(std::string const& save_path, move_flags_t flags); + + // renames the file with the given index to the new name + // the name may include a directory path + // posts alert to indicate success or failure + void rename_file(file_index_t index, std::string name); + + // unless this returns true, new connections must wait + // with their initialization. + bool ready_for_connections() const + { return m_connections_initialized; } + bool valid_metadata() const + { return m_torrent_file->is_valid(); } + bool are_files_checked() const + { return m_files_checked; } + + error_code initialize_merkle_trees(); + + // parses the info section from the given + // bencoded tree and moves the torrent + // to the checker thread for initial checking + // of the storage. + // a return value of false indicates an error + bool set_metadata(span metadata); + + queue_position_t sequence_number() const { return m_sequence_number; } + + bool seed_mode() const { return m_seed_mode; } + + enum class seed_mode_t { check_files, skip_checking }; + + void leave_seed_mode(seed_mode_t checking); + + bool all_verified() const + { return int(m_num_verified) == m_torrent_file->num_pieces(); } + bool verifying_piece(piece_index_t const piece) const + { return m_verifying.get_bit(piece); } + void verifying(piece_index_t const piece) + { + TORRENT_ASSERT(m_verifying.get_bit(piece) == false); + m_verifying.set_bit(piece); + } + bool verified_piece(piece_index_t piece) const + { return m_verified.get_bit(piece); } + void verified(piece_index_t piece); + + // this is called once periodically for torrents + // that are not private + void lsd_announce(); + + void update_last_upload() { m_last_upload = aux::time_now32(); } + + void set_apply_ip_filter(bool b); + bool apply_ip_filter() const { return m_apply_ip_filter; } + +#ifndef TORRENT_DISABLE_PREDICTIVE_PIECES + std::vector const& predictive_pieces() const + { return m_predictive_pieces; } + + // this is called whenever we predict to have this piece + // within one second + void predicted_have_piece(piece_index_t index, int milliseconds); +#endif + + void clear_in_state_update() + { + TORRENT_ASSERT(m_links[aux::session_interface::torrent_state_updates].in_list()); + m_links[aux::session_interface::torrent_state_updates].clear(); + } + + void inc_num_connecting(torrent_peer* pp) + { + ++m_num_connecting; + if (pp->seed) ++m_num_connecting_seeds; + } + void dec_num_connecting(torrent_peer* pp) + { + TORRENT_ASSERT(m_num_connecting > 0); + --m_num_connecting; + if (pp->seed) + { + TORRENT_ASSERT(m_num_connecting_seeds > 0); + --m_num_connecting_seeds; + } + TORRENT_ASSERT(m_num_connecting <= int(m_connections.size())); + } + + bool is_ssl_torrent() const { return m_ssl_torrent; } +#ifdef TORRENT_SSL_PEERS + void set_ssl_cert(std::string const& certificate + , std::string const& private_key + , std::string const& dh_params + , std::string const& passphrase); + void set_ssl_cert_buffer(std::string const& certificate + , std::string const& private_key + , std::string const& dh_params); + ssl::context* ssl_ctx() const { return m_ssl_ctx.get(); } +#endif + + int num_time_critical_pieces() const + { +#ifndef TORRENT_DISABLE_STREAMING + return int(m_time_critical_pieces.size()); +#else + return 0; +#endif + } + + int get_suggest_pieces(std::vector& p + , typed_bitfield const& bits + , int const n) + { + return m_suggest_pieces.get_pieces(p, bits, n); + } + void add_suggest_piece(piece_index_t index); + + client_data_t get_userdata() const { return m_userdata; } + + static constexpr int no_gauge_state = 0xf; + + private: + + void on_exception(std::exception const& e); + void on_error(error_code const& ec); + + // trigger deferred disconnection of peers + void on_remove_peers() noexcept; + + void ip_filter_updated(); + + void inc_stats_counter(int c, int value = 1); + + // initialize the torrent_state structure passed to peer_list + // member functions. Don't forget to also call peers_erased() + // on the erased member after the peer_list call + torrent_state get_peer_list_state(); + + void construct_storage(); + void update_list(torrent_list_index_t list, bool in); + + void on_files_deleted(storage_error const& error); + void on_torrent_paused(); + void on_storage_moved(status_t status, std::string const& path + , storage_error const& error); + void on_file_renamed(std::string const& filename + , file_index_t file_idx + , storage_error const& error); + void on_cache_flushed(bool manually_triggered); + + // this is used when a torrent is being removed.It synchronizes with the + // disk thread + void on_torrent_aborted(); + + // upload and download rate limits for the torrent + void set_limit_impl(int limit, int channel, bool state_update = true); + int limit_impl(int channel) const; + + int deprioritize_tracker(int tracker_index); + + void update_peer_interest(bool was_finished); + void prioritize_udp_trackers(); + + void update_tracker_timer(time_point32 now); + + void on_tracker_announce(error_code const& ec); + +#ifndef TORRENT_DISABLE_DHT + static void on_dht_announce_response_disp(std::weak_ptr t + , protocol_version v, std::vector const& peers); + void on_dht_announce_response(protocol_version v, std::vector const& peers); + bool should_announce_dht() const; +#endif + +#ifndef TORRENT_DISABLE_STREAMING + void remove_time_critical_piece(piece_index_t piece, bool finished = false); + void remove_time_critical_pieces(aux::vector const& priority); + void request_time_critical_pieces(); +#endif // TORRENT_DISABLE_STREAMING + + void need_peer_list(); + + std::shared_ptr m_ip_filter; + + // all time totals of uploaded and downloaded payload + // stored in resume data + std::int64_t m_total_uploaded = 0; + std::int64_t m_total_downloaded = 0; + + // the number of bytes of pad files + std::int64_t m_padding_bytes = 0; + + // this is a handle that keeps the storage object in the disk io subsystem + // alive, as well as the index referencing the storage/torrent in the disk + // I/O. When this destructs, the torrent will be removed from the disk + // subsystem. + storage_holder m_storage; + +#ifdef TORRENT_SSL_PEERS + std::unique_ptr m_ssl_ctx; + + bool verify_peer_cert(bool const preverified, ssl::verify_context& ctx); + + void init_ssl(string_view cert); +#endif + + void setup_peer_class(); + + // The list of web seeds in this torrent. Seeds with fatal errors are + // removed from the set. It's important that iterators are not + // invalidated as entries are added and removed from this list, hence the + // std::list + std::list m_web_seeds; + +#ifndef TORRENT_DISABLE_EXTENSIONS + std::list> m_extensions; +#endif + + // used for tracker announces + deadline_timer m_tracker_timer; + + // used to detect when we are active or inactive for long enough + // to trigger the auto-manage logic + deadline_timer m_inactivity_timer; + + // this is the upload and download statistics for the whole torrent. + // it's updated from all its peers once every second. + libtorrent::stat m_stat; + + // ----------------------------- + + // this vector is allocated lazily. If no file priorities are + // ever changed, this remains empty. Any unallocated slot + // implicitly means the file has priority 4. + // TODO: this wastes 5 bits per file + aux::vector m_file_priority; + + // any file priority updates attempted while another file priority update + // is in-progress/outstanding with the disk I/O thread, are queued up in + // this dictionary. Once the outstanding update comes back, all of these + // are applied in one batch + std::map m_deferred_file_priorities; + + // this object is used to track download progress of individual files + aux::file_progress m_file_progress; + + // a queue of the most recent low-availability pieces we accessed on disk. + // These are good candidates for suggesting other peers to request from + // us. + aux::suggest_piece m_suggest_pieces; + + aux::vector m_trackers; + +#ifndef TORRENT_DISABLE_STREAMING + // this list is sorted by time_critical_piece::deadline + std::vector m_time_critical_pieces; +#endif + + std::string m_trackerid; +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.1 + std::string m_username; + std::string m_password; +#endif + + std::string m_save_path; + +#ifndef TORRENT_DISABLE_PREDICTIVE_PIECES + // this is a list of all pieces that we have announced + // as having, without actually having yet. If we receive + // a request for a piece in this list, we need to hold off + // on responding until we have completed the piece and + // verified its hash. If the hash fails, send reject to + // peers with outstanding requests, and dont_have to other + // peers. This vector is ordered, to make lookups fast. + + // TODO: 3 factor out predictive pieces and all operations on it into a + // separate class (to use as member here instead) + std::vector m_predictive_pieces; +#endif + + // v2 merkle tree for each file + aux::vector m_merkle_trees; + + // the performance counters of this session + counters& m_stats_counters; + + // each bit represents a piece. a set bit means + // the piece has had its hash verified. This + // is only used in seed mode (when m_seed_mode + // is true) + typed_bitfield m_verified; + + // this means there is an outstanding, async, operation + // to verify each piece that has a 1 + typed_bitfield m_verifying; + + // set if there's an error on this torrent + error_code m_error; + + // used if there is any resume data. Some of the information from the + // add_torrent_params struct are needed later in the torrent object's life + // cycle, and not in the constructor. So we need to save if away here + std::unique_ptr m_add_torrent_params; + + // if the torrent is started without metadata, it may + // still be given a name until the metadata is received + // once the metadata is received this field will no + // longer be used and will be reset + std::unique_ptr m_name; + + // the posix time this torrent was added and when + // it was completed. If the torrent isn't yet + // completed, m_completed_time is 0 + std::time_t m_added_time; + std::time_t m_completed_time; + + // this was the last time _we_ saw a seed in this swarm + std::time_t m_last_seen_complete = 0; + + // this is the time last any of our peers saw a seed + // in this swarm + std::time_t m_swarm_last_seen_complete = 0; + + // keep a copy if the info-hash here, so it can be accessed from multiple + // threads, and be cheap to access from the client + info_hash_t m_info_hash; + + public: + // these are the lists this torrent belongs to. For more + // details about each list, see session_impl.hpp. Each list + // represents a group this torrent belongs to and makes it + // efficient to enumerate only torrents belonging to a specific + // group. Such as torrents that want peer connections or want + // to be ticked etc. + + // TODO: 3 factor out the links (as well as update_list() to a separate + // class that torrent can inherit) + aux::array + m_links; + + private: + + // m_num_verified = m_verified.count() + std::uint32_t m_num_verified = 0; + + // if this torrent is running, this was the time + // when it was started. This is used to have a + // bias towards keeping seeding torrents that + // recently was started, to avoid oscillation + // this is specified at a second granularity + time_point32 m_started = aux::time_now32(); + + // if we're a seed, this is the timestamp of when we became one + time_point32 m_became_seed = aux::time_now32(); + + // if we're finished, this is the timestamp of when we finished + time_point32 m_became_finished = aux::time_now32(); + + // when checking, this is the first piece we have not + // issued a hash job for + piece_index_t m_checking_piece{0}; + + // the number of pieces we completed the check of + piece_index_t m_num_checked_pieces{0}; + + // if the error occurred on a file, this is the index of that file + // there are a few special cases, when this is negative. See + // set_error() + file_index_t m_error_file; + + // the average time it takes to download one time critical piece + std::int32_t m_average_piece_time = 0; + + // the average piece download time deviation + std::int32_t m_piece_time_deviation = 0; + + // the number of bytes that has been + // downloaded that failed the hash-test + std::int64_t m_total_failed_bytes = 0; + std::int64_t m_total_redundant_bytes = 0; + + // the sequence number for this torrent, this is a + // monotonically increasing number for each added torrent + queue_position_t m_sequence_number; + + // used to post a message to defer disconnecting peers + std::vector> m_peers_to_disconnect; + aux::deferred_handler m_deferred_disconnect; + aux::handler_storage m_deferred_handler_storage; + + // these are the peer IDs we've used for our outgoing peer connections for + // this torrent. If we get an incoming peer claiming to have one of these, + // it's a connection to ourself, and we should reject it. + std::set m_outgoing_pids; + + // for torrents who have a bandwidth limit, this is != 0 + // and refers to a peer_class in the session. + peer_class_t m_peer_class{0}; + + // of all peers in m_connections, this is the number + // of peers that are outgoing and still waiting to + // complete the connection. This is used to possibly + // kick out these connections when we get incoming + // connections (if we've reached the connection limit) + std::uint16_t m_num_connecting = 0; + + // this is the peer id we generate when we add the torrent. Peers won't + // use this (they generate their own peer ids) but this is used in case + // the tracker returns peer IDs, to identify ourself in the peer list to + // avoid connecting back to it. + peer_id m_peer_id; + + // ============================== + // The following members are specifically + // ordered to make the 24 bit members + // properly 32 bit aligned by inserting + // 8 bits after each one + // ============================== + + // if we're currently in upload-mode this is the time timestamp of when + // we entered it + time_point32 m_upload_mode_time = aux::time_now32(); + + // true when this torrent should announce to + // trackers + bool m_announce_to_trackers:1; + + // true when this torrent should announce to + // the local network + bool m_announce_to_lsd:1; + + // is set to true every time there is an incoming + // connection to this torrent + bool m_has_incoming:1; + + // this is set to true when the files are checked + // before the files are checked, we don't try to + // connect to peers + bool m_files_checked:1; + + // determines the storage state for this torrent. + unsigned int m_storage_mode:2; + + // this is true while tracker announcing is enabled + // is is disabled while paused and checking files + bool m_announcing:1; + + // this is true when the torrent has been added to the session. Before + // then, it isn't included in the counters (session_stats) + bool m_added:1; + + // this is > 0 while the tracker deadline timer + // is in use. i.e. one or more trackers are waiting + // for a reannounce + std::int8_t m_waiting_tracker = 0; + +// ---- + + // total time we've been active on this torrent. i.e. either (trying to) + // download or seed. does not count time when the torrent is stopped or + // paused. specified in seconds. This only track time _before_ we started + // the torrent this last time. When the torrent is paused, this counter is + // incremented to include this current session. + seconds32 m_active_time{0}; + + // the index to the last tracker that worked + std::int8_t m_last_working_tracker = -1; + +// ---- + + // total time we've been finished with this torrent. + // does not count when the torrent is stopped or paused. + seconds32 m_finished_time{0}; + + // in case the piece picker hasn't been constructed + // when this settings is set, this variable will keep + // its value until the piece picker is created + bool m_sequential_download:1; + + // this is set if the auto_sequential setting is true and this swarm + // satisfies the criteria to be considered high-availability. i.e. if + // there's mostly seeds in the swarm, download the files sequentially + // for improved disk I/O performance. + bool m_auto_sequential:1; + + // this means we haven't verified the file content + // of the files we're seeding. the m_verified bitfield + // indicates which pieces have been verified and which + // haven't + bool m_seed_mode:1; + +#ifndef TORRENT_DISABLE_SUPERSEEDING + // if this is true, we're currently super seeding this + // torrent. + bool m_super_seeding:1; +#endif + + // if this is set, whenever transitioning into a downloading/seeding state + // from a non-downloading/seeding state, the torrent is paused. + bool m_stop_when_ready:1; + + // when this is true, this torrent participates in the DHT + bool m_enable_dht:1; + + // when this is true, this torrent participates in local service discovery + bool m_enable_lsd:1; + + bool m_i2p:1; +// ---- + + // total time we've been available as a seed on this torrent. + // does not count when the torrent is stopped or paused. This value only + // accounts for the time prior to the current start of the torrent. When + // the torrent is paused, this counter is incremented to account for the + // additional seeding time. + seconds32 m_seeding_time{0}; + +// ---- + + // the maximum number of uploads for this torrent + std::uint32_t m_max_uploads:24; + + // bits set to indicate which category of resume data state has updated + resume_data_flags_t m_need_save_resume_data; + +// ---- + + // the number of unchoked peers in this torrent + unsigned int m_num_uploads:24; + + // 4 unused bits + + // when this is true, this torrent supports peer exchange + bool m_enable_pex:1; + + // set to true if the session IP filter applies to this + // torrent or not. Defaults to true. + bool m_apply_ip_filter:1; + + // this is true when our effective inactive state is different from our + // actual inactive state. Whenever this state changes, there is a + // quarantine period until we change the effective state. This is to avoid + // flapping. If the state changes back during this period, we cancel the + // quarantine + bool m_pending_active_change:1; + + // this is set to true if all piece layers were successfully loaded and + // validated. Only for v2 torrents + // TODO: this member can probably be removed + bool m_v2_piece_layers_validated:1; + +// ---- + + // this is set to the connect boost quota for this torrent. + // After having received this many priority peer connection attempts, it + // falls back onto the steady state peer connection logic, driven by the + // session tick. Each tracker response, as long as this is non-zero, will + // attempt to connect to peers immediately and decrement the counter. + // We give torrents a connect boost when they are first added and then + // every time they resume from being paused. + std::uint8_t m_connect_boost_counter; + +// ---- + + // the scrape data from the tracker response, this + // is optional and may be 0xffffff + std::uint32_t m_incomplete:24; + + // true when the torrent should announce to + // the DHT + bool m_announce_to_dht:1; + + // even if we're not built to support SSL torrents, + // remember that this is an SSL torrent, so that we don't + // accidentally start seeding it without any authentication. + bool m_ssl_torrent:1; + + // this is set to true if we're trying to delete the + // files belonging to it. When set, don't write any + // more blocks to disk! + bool m_deleted:1; + +// ---- + + // the timestamp of the last piece passed for this torrent specified in + // seconds since epoch. + time_point32 m_last_download{seconds32(0)}; + + // the number of peer connections to seeds. This should be the same as + // counting the peer connections that say true for is_seed() + std::uint16_t m_num_seeds = 0; + + // this is the number of peers that are seeds, and count against + // m_num_seeds, but have not yet been connected + std::uint16_t m_num_connecting_seeds = 0; + + // the timestamp of the last byte uploaded from this torrent specified in + // seconds since epoch. + time_point32 m_last_upload{seconds32(0)}; + + // user data as passed in by add_torrent_params + client_data_t m_userdata; + +// ---- + + // if this is true, libtorrent may pause and resume + // this torrent depending on queuing rules. Torrents + // started with auto_managed flag set may be added in + // a paused state in case there are no available + // slots. + bool m_auto_managed:1; + + // the current stats gauge this torrent counts against + std::uint32_t m_current_gauge_state:4; + + // set to true while moving the storage + bool m_moving_storage:1; + + // this is true if this torrent is considered inactive from the + // queuing mechanism's point of view. If a torrent doesn't transfer + // at high enough rates, it's inactive. + bool m_inactive:1; + +// ---- + + // the scrape data from the tracker response, this + // is optional and may be 0xffffff + std::uint32_t m_downloaded:24; + +#if TORRENT_ABI_VERSION == 1 + // the timestamp of the last scrape request to one of the trackers in + // this torrent specified in session_time. This is signed because it must + // be able to represent time before the session started + time_point32 m_last_scrape{seconds32(0)}; +#endif + +// ---- + + // progress parts per million (the number of + // millionths of completeness) + std::uint32_t m_progress_ppm:20; + + // set to true once init() completes successfully. This is important to + // track in case it fails and need to be retried if the client clears + // the torrent error + bool m_torrent_initialized:1; + + // this is set to true while waiting for an async_set_file_priority + bool m_outstanding_file_priority:1; + + // set to true if we've sent an event=completed to any tracker. This will + // prevent us from sending it again to anyone + bool m_complete_sent:1; + +#if TORRENT_USE_ASSERTS + // set to true when torrent is start()ed. It may only be started once + bool m_was_started = false; + bool m_outstanding_check_files = false; + + // this is set to true while we're looping over m_connections. We may not + // mutate the list while doing this + mutable int m_iterating_connections = 0; +#endif + }; +} + +#endif // TORRENT_TORRENT_HPP_INCLUDED diff --git a/include/libtorrent/torrent_flags.hpp b/include/libtorrent/torrent_flags.hpp new file mode 100644 index 0000000..90ee420 --- /dev/null +++ b/include/libtorrent/torrent_flags.hpp @@ -0,0 +1,334 @@ +/* + +Copyright (c) 2017-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_FLAGS_HPP +#define TORRENT_TORRENT_FLAGS_HPP + +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/flags.hpp" + +namespace libtorrent { + +using torrent_flags_t = flags::bitfield_flag; + +namespace torrent_flags { + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_push.hpp" +#endif + + // If ``seed_mode`` is set, libtorrent will assume that all files + // are present for this torrent and that they all match the hashes in + // the torrent file. Each time a peer requests to download a block, + // the piece is verified against the hash, unless it has been verified + // already. If a hash fails, the torrent will automatically leave the + // seed mode and recheck all the files. The use case for this mode is + // if a torrent is created and seeded, or if the user already know + // that the files are complete, this is a way to avoid the initial + // file checks, and significantly reduce the startup time. + // + // Setting ``seed_mode`` on a torrent without metadata (a + // .torrent file) is a no-op and will be ignored. + // + // It is not possible to *set* the ``seed_mode`` flag on a torrent after it has + // been added to a session. It is possible to *clear* it though. + constexpr torrent_flags_t seed_mode = 0_bit; + + // If ``upload_mode`` is set, the torrent will be initialized in + // upload-mode, which means it will not make any piece requests. This + // state is typically entered on disk I/O errors, and if the torrent + // is also auto managed, it will be taken out of this state + // periodically (see ``settings_pack::optimistic_disk_retry``). + // + // This mode can be used to avoid race conditions when + // adjusting priorities of pieces before allowing the torrent to start + // downloading. + // + // If the torrent is auto-managed (``auto_managed``), the torrent + // will eventually be taken out of upload-mode, regardless of how it + // got there. If it's important to manually control when the torrent + // leaves upload mode, don't make it auto managed. + constexpr torrent_flags_t upload_mode = 1_bit; + + // determines if the torrent should be added in *share mode* or not. + // Share mode indicates that we are not interested in downloading the + // torrent, but merely want to improve our share ratio (i.e. increase + // it). A torrent started in share mode will do its best to never + // download more than it uploads to the swarm. If the swarm does not + // have enough demand for upload capacity, the torrent will not + // download anything. This mode is intended to be safe to add any + // number of torrents to, without manual screening, without the risk + // of downloading more than is uploaded. + // + // A torrent in share mode sets the priority to all pieces to 0, + // except for the pieces that are downloaded, when pieces are decided + // to be downloaded. This affects the progress bar, which might be set + // to "100% finished" most of the time. Do not change file or piece + // priorities for torrents in share mode, it will make it not work. + // + // The share mode has one setting, the share ratio target, see + // ``settings_pack::share_mode_target`` for more info. + constexpr torrent_flags_t share_mode = 2_bit; + + // determines if the IP filter should apply to this torrent or not. By + // default all torrents are subject to filtering by the IP filter + // (i.e. this flag is set by default). This is useful if certain + // torrents needs to be exempt for some reason, being an auto-update + // torrent for instance. + constexpr torrent_flags_t apply_ip_filter = 3_bit; + + // specifies whether or not the torrent is paused. i.e. it won't connect to the tracker or any of the peers + // until it's resumed. Note that a paused torrent that also has the + // auto_managed flag set can be started at any time by libtorrent's queuing + // logic. See queuing_. + constexpr torrent_flags_t paused = 4_bit; + + // If the torrent is auto-managed (``auto_managed``), the torrent + // may be resumed at any point, regardless of how it paused. If it's + // important to manually control when the torrent is paused and + // resumed, don't make it auto managed. + // + // If ``auto_managed`` is set, the torrent will be queued, + // started and seeded automatically by libtorrent. When this is set, + // the torrent should also be started as paused. The default queue + // order is the order the torrents were added. They are all downloaded + // in that order. For more details, see queuing_. + constexpr torrent_flags_t auto_managed = 5_bit; + + // used in add_torrent_params to indicate that it's an error to attempt + // to add a torrent that's already in the session. If it's not considered an + // error, a handle to the existing torrent is returned. + // This flag is not saved by write_resume_data(), since it is only meant for + // adding torrents. + constexpr torrent_flags_t duplicate_is_error = 6_bit; + + // on by default and means that this torrent will be part of state + // updates when calling post_torrent_updates(). + // This flag is not saved by write_resume_data(). + constexpr torrent_flags_t update_subscribe = 7_bit; + + // sets the torrent into super seeding/initial seeding mode. If the torrent + // is not a seed, this flag has no effect. + constexpr torrent_flags_t super_seeding = 8_bit; + + // sets the sequential download state for the torrent. In this mode the + // piece picker will pick pieces with low index numbers before pieces with + // high indices. The actual pieces that are picked depend on other factors + // still, such as which pieces a peer has and whether it is in parole mode + // or "prefer whole pieces"-mode. Sequential mode is not ideal for streaming + // media. For that, see set_piece_deadline() instead. + constexpr torrent_flags_t sequential_download = 9_bit; + + // When this flag is set, the torrent will *force stop* whenever it + // transitions from a non-data-transferring state into a data-transferring + // state (referred to as being ready to download or seed). This is useful + // for torrents that should not start downloading or seeding yet, but want + // to be made ready to do so. A torrent may need to have its files checked + // for instance, so it needs to be started and possibly queued for checking + // (auto-managed and started) but as soon as it's done, it should be + // stopped. + // + // *Force stopped* means auto-managed is set to false and it's paused. As + // if the auto_manages flag is cleared and the paused flag is set on the torrent. + // + // Note that the torrent may transition into a downloading state while + // setting this flag, and since the logic is edge triggered you may + // miss the edge. To avoid this race, if the torrent already is in a + // downloading state when this call is made, it will trigger the + // stop-when-ready immediately. + // + // When the stop-when-ready logic fires, the flag is cleared. Any + // subsequent transitions between downloading and non-downloading states + // will not be affected, until this flag is set again. + // + // The behavior is more robust when setting this flag as part of adding + // the torrent. See add_torrent_params. + // + // The stop-when-ready flag fixes the inherent race condition of waiting + // for the state_changed_alert and then call pause(). The download/seeding + // will most likely start in between posting the alert and receiving the + // call to pause. + // + // A downloading state is one where peers are being connected. Which means + // just downloading the metadata via the ``ut_metadata`` extension counts + // as a downloading state. In order to stop a torrent once the metadata + // has been downloaded, instead set all file priorities to dont_download + constexpr torrent_flags_t stop_when_ready = 10_bit; + + // when this flag is set, the tracker list in the add_torrent_params + // object override any trackers from the torrent file. If the flag is + // not set, the trackers from the add_torrent_params object will be + // added to the list of trackers used by the torrent. + // This flag is set by read_resume_data() if there are trackers present in + // the resume data file. This effectively makes the trackers saved in the + // resume data take precedence over the original trackers. This includes if + // there's an empty list of trackers, to support the case where they were + // explicitly removed in the previous session. + // This flag is not saved by write_resume_data() + constexpr torrent_flags_t override_trackers = 11_bit; + + // If this flag is set, the web seeds from the add_torrent_params + // object will override any web seeds in the torrent file. If it's not + // set, web seeds in the add_torrent_params object will be added to the + // list of web seeds used by the torrent. + // This flag is set by read_resume_data() if there are web seeds present in + // the resume data file. This effectively makes the web seeds saved in the + // resume data take precedence over the original ones. This includes if + // there's an empty list of web seeds, to support the case where they were + // explicitly removed in the previous session. + // This flag is not saved by write_resume_data() + constexpr torrent_flags_t override_web_seeds = 12_bit; + + // if this flag is set (which it is by default) the torrent will be + // considered needing to save its resume data immediately, in the + // category if_metadata_changed. See resume_data_flags_t and + // save_resume_data() for details. + // + // This flag is cleared by a successful call to save_resume_data() + // This flag is not saved by write_resume_data(), since it represents an + // ephemeral state of a running torrent. + constexpr torrent_flags_t need_save_resume = 13_bit; + +#if TORRENT_ABI_VERSION == 1 + // indicates that this torrent should never be unloaded from RAM, even + // if unloading torrents are allowed in general. Setting this makes + // the torrent exempt from loading/unloading management. + TORRENT_DEPRECATED constexpr torrent_flags_t pinned = 14_bit; + + // If ``override_resume_data`` is set, flags set for this torrent + // in this ``add_torrent_params`` object will take precedence over + // whatever states are saved in the resume data. For instance, the + // ``paused``, ``auto_managed``, ``sequential_download``, ``seed_mode``, + // ``super_seeding``, ``max_uploads``, ``max_connections``, + // ``upload_limit`` and ``download_limit`` are all affected by this + // flag. The intention of this flag is to have any field in + // add_torrent_params configuring the torrent override the corresponding + // configuration from the resume file, with the one exception of save + // resume data, which has its own flag (for historic reasons). + // "file_priorities" and "save_path" are not affected by this flag. + TORRENT_DEPRECATED constexpr torrent_flags_t override_resume_data = 15_bit; + + // defaults to on and specifies whether tracker URLs loaded from + // resume data should be added to the trackers in the torrent or + // replace the trackers. When replacing trackers (i.e. this flag is not + // set), any trackers passed in via add_torrent_params are also + // replaced by any trackers in the resume data. The default behavior is + // to have the resume data override the .torrent file _and_ the + // trackers added in add_torrent_params. + TORRENT_DEPRECATED constexpr torrent_flags_t merge_resume_trackers = 16_bit; + + // if this flag is set, the save path from the resume data file, if + // present, is honored. This defaults to not being set, in which + // case the save_path specified in add_torrent_params is always used. + TORRENT_DEPRECATED constexpr torrent_flags_t use_resume_save_path = 17_bit; + + // defaults to on and specifies whether web seed URLs loaded from + // resume data should be added to the ones in the torrent file or + // replace them. No distinction is made between the two different kinds + // of web seeds (`BEP 17`_ and `BEP 19`_). When replacing web seeds + // (i.e. when this flag is not set), any web seeds passed in via + // add_torrent_params are also replaced. The default behavior is to + // have any web seeds in the resume data take precedence over whatever + // is passed in here as well as the .torrent file. + TORRENT_DEPRECATED constexpr torrent_flags_t merge_resume_http_seeds = 18_bit; +#endif + + // set this flag to disable DHT for this torrent. This lets you have the DHT + // enabled for the whole client, and still have specific torrents not + // participating in it. i.e. not announcing to the DHT nor picking up peers + // from it. + constexpr torrent_flags_t disable_dht = 19_bit; + + // set this flag to disable local service discovery for this torrent. + constexpr torrent_flags_t disable_lsd = 20_bit; + + // set this flag to disable peer exchange for this torrent. + constexpr torrent_flags_t disable_pex = 21_bit; + + // if this flag is set, the resume data will be assumed to be correct + // without validating it against any files on disk. This may be used when + // restoring a session by loading resume data from disk. It will save time + // and also delay any hard disk errors until files are actually needed. If + // the resume data cannot be trusted, or if a torrent is added for the first + // time to some save path that may already have some of the files, this flag + // should not be set. + constexpr torrent_flags_t no_verify_files = 22_bit; + + // default all file priorities to dont_download. This is useful for adding + // magnet links where the number of files is unknown, but the + // file_priorities is still set for some files. Any file not covered by + // the file_priorities list will be set to normal download priority, + // unless this flag is set, in which case they will be set to 0 + // (dont_download). + constexpr torrent_flags_t default_dont_download = 23_bit; + + // this flag makes the torrent be considered an "i2p torrent" for purposes + // of the allow_i2p_mixed setting. When mixing regular peers and i2p peers + // is disabled, i2p torrents won't add normal peers to its peer list. + // Note that non i2p torrents may still allow i2p peers (on the off-chance + // that a tracker return them and the session is configured with a SAM + // connection). + // This flag is set automatically when adding a torrent that has at least + // one tracker whose hostname ends with .i2p. + // It's also set by parse_magnet_uri() if the tracker list contains such + // URL. + constexpr torrent_flags_t i2p_torrent = 24_bit; + + // all torrent flags combined. Can conveniently be used when creating masks + // for flags + constexpr torrent_flags_t all = torrent_flags_t::all(); + + // internal + constexpr torrent_flags_t default_flags = + torrent_flags::update_subscribe + | torrent_flags::auto_managed + | torrent_flags::paused + | torrent_flags::apply_ip_filter + | torrent_flags::need_save_resume +#if TORRENT_ABI_VERSION == 1 + | torrent_flags::pinned + | torrent_flags::merge_resume_http_seeds + | torrent_flags::merge_resume_trackers +#endif + ; + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + +} // torrent_flags +} // libtorrent + +#endif + diff --git a/include/libtorrent/torrent_handle.hpp b/include/libtorrent/torrent_handle.hpp new file mode 100644 index 0000000..6ffd49f --- /dev/null +++ b/include/libtorrent/torrent_handle.hpp @@ -0,0 +1,1430 @@ +/* + +Copyright (c) 2019, Amir Abrams +Copyright (c) 2003-2022, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2015, 2018, Steven Siloti +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2017, 2020, AllSeeingEyeTolledEweSew +Copyright (c) 2017, Falcosc +Copyright (c) 2019, Andrei Kurushin +Copyright (c) 2019, ghbplayer +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_HANDLE_HPP_INCLUDED +#define TORRENT_TORRENT_HANDLE_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#if TORRENT_ABI_VERSION == 1 +// for deprecated force_reannounce +#include +#endif +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/fwd.hpp" +#include "libtorrent/socket.hpp" // tcp::endpoint +#include "libtorrent/span.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/torrent_flags.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/peer_info.hpp" // for peer_source_flags_t +#include "libtorrent/download_priority.hpp" +#include "libtorrent/pex_flags.hpp" +#include "libtorrent/client_data.hpp" +#include "libtorrent/address.hpp" // for address_v4 and address_v6 + +namespace libtorrent { +namespace aux { + struct session_impl; +} + +#if TORRENT_ABI_VERSION == 1 + struct peer_list_entry; +#endif + struct torrent; + struct client_data_t; + +#ifndef BOOST_NO_EXCEPTIONS + [[noreturn]] void throw_invalid_handle(); +#endif + + using status_flags_t = flags::bitfield_flag; + using add_piece_flags_t = flags::bitfield_flag; + using pause_flags_t = flags::bitfield_flag; + using deadline_flags_t = flags::bitfield_flag; + using resume_data_flags_t = flags::bitfield_flag; + using reannounce_flags_t = flags::bitfield_flag; + using queue_position_t = aux::strong_typedef; + using file_progress_flags_t = flags::bitfield_flag; + + // holds the state of a block in a piece. Who we requested + // it from and how far along we are at downloading it. + struct TORRENT_EXPORT block_info + { + // this is the enum used for the block_info::state field. + enum block_state_t + { + // This block has not been downloaded or requested form any peer. + none, + // The block has been requested, but not completely downloaded yet. + requested, + // The block has been downloaded and is currently queued for being + // written to disk. + writing, + // The block has been written to disk. + finished + }; + + private: + union addr_t + { + address_v4::bytes_type v4; + address_v6::bytes_type v6; + }; + addr_t addr; + + std::uint16_t port; + public: + + // The peer is the ip address of the peer this block was downloaded from. + void set_peer(tcp::endpoint const& ep); + tcp::endpoint peer() const; + + // the number of bytes that have been received for this block + unsigned bytes_progress:15; + + // the total number of bytes in this block. + unsigned block_size:15; + + // the state this block is in (see block_state_t) + unsigned state:2; + + // the number of peers that is currently requesting this block. Typically + // this is 0 or 1, but at the end of the torrent blocks may be requested + // by more peers in parallel to speed things up. + unsigned num_peers:14; + private: + // the type of the addr union + bool is_v6_addr:1; + }; + + // This class holds information about pieces that have outstanding requests + // or outstanding writes + struct TORRENT_EXPORT partial_piece_info + { +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_push.hpp" + partial_piece_info() = default; + partial_piece_info(partial_piece_info&&) noexcept = default; + partial_piece_info(partial_piece_info const&) = default; + partial_piece_info& operator=(partial_piece_info const&) & = default; + partial_piece_info& operator=(partial_piece_info&&) & noexcept = default; +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + // the index of the piece in question. ``blocks_in_piece`` is the number + // of blocks in this particular piece. This number will be the same for + // most pieces, but + // the last piece may have fewer blocks than the standard pieces. + piece_index_t piece_index; + + // the number of blocks in this piece + int blocks_in_piece; + + // the number of blocks that are in the finished state + int finished; + + // the number of blocks that are in the writing state + int writing; + + // the number of blocks that are in the requested state + int requested; + + // this is an array of ``blocks_in_piece`` number of + // items. One for each block in the piece. + // + // .. warning:: This is a pointer that points to an array + // that's owned by the session object. The next time + // get_download_queue() is called, it will be invalidated. + // In the case of piece_info_alert, these pointers point into the alert + // object itself, and will be invalidated when the alert destruct. + block_info const* blocks; + +#if TORRENT_ABI_VERSION == 1 + // the speed classes. These may be used by the piece picker to + // coalesce requests of similar download rates + enum state_t { none, slow, medium, fast }; + + // the download speed class this piece falls into. + // this is used internally to cluster peers of the same + // speed class together when requesting blocks. + // + // set to either ``fast``, ``medium``, ``slow`` or ``none``. It tells + // which download rate category the peers downloading this piece falls + // into. ``none`` means that no peer is currently downloading any part of + // the piece. Peers prefer picking pieces from the same category as + // themselves. The reason for this is to keep the number of partially + // downloaded pieces down. Pieces set to ``none`` can be converted into + // any of ``fast``, ``medium`` or ``slow`` as soon as a peer want to + // download from it. + TORRENT_DEPRECATED state_t piece_state; +#endif + }; + + // for std::hash (and to support using this type in unordered_map etc.) + TORRENT_EXPORT std::size_t hash_value(torrent_handle const& h); + + // You will usually have to store your torrent handles somewhere, since it's + // the object through which you retrieve information about the torrent and + // aborts the torrent. + // + // .. warning:: + // Any member function that returns a value or fills in a value has to be + // made synchronously. This means it has to wait for the main thread to + // complete the query before it can return. This might potentially be + // expensive if done from within a GUI thread that needs to stay + // responsive. Try to avoid querying for information you don't need, and + // try to do it in as few calls as possible. You can get most of the + // interesting information about a torrent from the + // torrent_handle::status() call. + // + // The default constructor will initialize the handle to an invalid state. + // Which means you cannot perform any operation on it, unless you first + // assign it a valid handle. If you try to perform any operation on an + // uninitialized handle, it will throw ``invalid_handle``. + // + // .. warning:: + // All operations on a torrent_handle may throw system_error + // exception, in case the handle is no longer referring to a torrent. + // There is one exception is_valid() will never throw. Since the torrents + // are processed by a background thread, there is no guarantee that a + // handle will remain valid between two calls. + // + struct TORRENT_EXPORT torrent_handle + { + friend struct aux::session_impl; + friend struct session_handle; + friend struct torrent; + TORRENT_EXPORT friend std::size_t hash_value(torrent_handle const& th); + + // constructs a torrent handle that does not refer to a torrent. + // i.e. is_valid() will return false. + torrent_handle() noexcept = default; + + // hidden + torrent_handle(torrent_handle const& t) = default; + torrent_handle(torrent_handle&& t) noexcept = default; + torrent_handle& operator=(torrent_handle const&) & = default; + torrent_handle& operator=(torrent_handle&&) & noexcept = default; + + +#if TORRENT_ABI_VERSION == 1 + using flags_t = add_piece_flags_t; + using status_flags_t = libtorrent::status_flags_t; + using pause_flags_t = libtorrent::pause_flags_t; + using save_resume_flags_t = libtorrent::resume_data_flags_t; + using reannounce_flags_t = libtorrent::reannounce_flags_t; +#endif + + // instruct libtorrent to overwrite any data that may already have been + // downloaded with the data of the new piece being added. Using this + // flag when adding a piece that is actively being downloaded from other + // peers may have some unexpected consequences, as blocks currently + // being downloaded from peers may not be replaced. + static constexpr add_piece_flags_t overwrite_existing = 0_bit; + + // This function will write ``data`` to the storage as piece ``piece``, + // as if it had been downloaded from a peer. + // + // By default, data that's already been downloaded is not overwritten by + // this buffer. If you trust this data to be correct (and pass the piece + // hash check) you may pass the overwrite_existing flag. This will + // instruct libtorrent to overwrite any data that may already have been + // downloaded with this data. + // + // Since the data is written asynchronously, you may know that is passed + // or failed the hash check by waiting for piece_finished_alert or + // hash_failed_alert. + // + // Adding pieces while the torrent is being checked (i.e. in + // torrent_status::checking_files state) is not supported. + // + // The overload taking a raw pointer to the data is a blocking call. It + // won't return until the libtorrent thread has copied the data into its + // disk write buffer. ``data`` is expected to point to a buffer of as + // many bytes as the size of the specified piece. See + // file_storage::piece_size(). + // + // The data in the buffer is copied and passed on to the disk IO thread + // to be written at a later point. + // + // The overload taking a ``std::vector`` is not blocking, it will + // send the buffer to the main thread and return immediately. + void add_piece(piece_index_t piece, char const* data, add_piece_flags_t flags = {}) const; + void add_piece(piece_index_t piece, std::vector data, add_piece_flags_t flags = {}) const; + + // This function starts an asynchronous read operation of the specified + // piece from this torrent. You must have completed the download of the + // specified piece before calling this function. + // + // When the read operation is completed, it is passed back through an + // alert, read_piece_alert. Since this alert is a response to an explicit + // call, it will always be posted, regardless of the alert mask. + // + // Note that if you read multiple pieces, the read operations are not + // guaranteed to finish in the same order as you initiated them. + void read_piece(piece_index_t piece) const; + + // Returns true if this piece has been completely downloaded and written + // to disk, and false otherwise. + bool have_piece(piece_index_t piece) const; + +#if TORRENT_ABI_VERSION == 1 + // internal + TORRENT_DEPRECATED + void get_full_peer_list(std::vector& v) const; +#endif + + // Query information about connected peers for this torrent. If the + // torrent_handle is invalid, it will throw a system_error exception. + // + // ``post_peer_info()`` is asynchronous and will trigger the posting of + // a peer_info_alert. The alert contain a list of peer_info objects, one + // for each connected peer. + // + // ``get_peer_info()`` is synchronous and takes a reference to a vector + // that will be cleared and filled with one entry for each peer + // connected to this torrent, given the handle is valid. Each entry in + // the vector contains information about that particular peer. See + // peer_info. + void post_peer_info() const; + void get_peer_info(std::vector& v) const; + + // calculates ``distributed_copies``, ``distributed_full_copies`` and + // ``distributed_fraction``. + static constexpr status_flags_t query_distributed_copies = 0_bit; + + // includes partial downloaded blocks in ``total_done`` and + // ``total_wanted_done``. + static constexpr status_flags_t query_accurate_download_counters = 1_bit; + + // includes ``last_seen_complete``. + static constexpr status_flags_t query_last_seen_complete = 2_bit; + // populate the ``pieces`` field in torrent_status. + static constexpr status_flags_t query_pieces = 3_bit; + // includes ``verified_pieces`` (only applies to torrents in *seed + // mode*). + static constexpr status_flags_t query_verified_pieces = 4_bit; + // includes ``torrent_file``, which is all the static information from + // the .torrent file. + static constexpr status_flags_t query_torrent_file = 5_bit; + // includes ``name``, the name of the torrent. This is either derived + // from the .torrent file, or from the ``&dn=`` magnet link argument + // or possibly some other source. If the name of the torrent is not + // known, this is an empty string. + static constexpr status_flags_t query_name = 6_bit; + // includes ``save_path``, the path to the directory the files of the + // torrent are saved to. + static constexpr status_flags_t query_save_path = 7_bit; + + // ``status()`` will return a structure with information about the status + // of this torrent. If the torrent_handle is invalid, it will throw + // system_error exception. See torrent_status. The ``flags`` + // argument filters what information is returned in the torrent_status. + // Some information in there is relatively expensive to calculate, and if + // you're not interested in it (and see performance issues), you can + // filter them out. + // + // The ``status()`` function will block until the internal libtorrent + // thread responds with the torrent_status object. To avoid blocking, + // instead call ``post_status()``. It will trigger posting of a + // state_update_alert with a single torrent_status object for this + // torrent. + // + // In order to get regular updates for torrents whose status changes, + // consider calling session::post_torrent_updates()`` instead. + // + // By default everything is included. The flags you can use to decide + // what to *include* are defined in this class. + torrent_status status(status_flags_t flags = status_flags_t::all()) const; + void post_status(status_flags_t flags = status_flags_t::all()) const; + + // ``post_download_queue()`` triggers a download_queue_alert to be + // posted. + // ``get_download_queue()`` is a synchronous call and returns a vector + // with information about pieces that are partially downloaded or not + // downloaded but partially requested. See partial_piece_info for the + // fields in the returned vector. + void post_download_queue() const; + std::vector get_download_queue() const; + void get_download_queue(std::vector& queue) const; + + // used to ask libtorrent to send an alert once the piece has been + // downloaded, by passing alert_when_available. When set, the + // read_piece_alert alert will be delivered, with the piece data, when + // it's downloaded. + static constexpr deadline_flags_t alert_when_available = 0_bit; + + // This function sets or resets the deadline associated with a specific + // piece index (``index``). libtorrent will attempt to download this + // entire piece before the deadline expires. This is not necessarily + // possible, but pieces with a more recent deadline will always be + // prioritized over pieces with a deadline further ahead in time. The + // deadline (and flags) of a piece can be changed by calling this + // function again. + // + // If the piece is already downloaded when this call is made, nothing + // happens, unless the alert_when_available flag is set, in which case it + // will have the same effect as calling read_piece() for ``index``. + // + // ``deadline`` is the number of milliseconds until this piece should be + // completed. + // + // ``reset_piece_deadline`` removes the deadline from the piece. If it + // hasn't already been downloaded, it will no longer be considered a + // priority. + // + // ``clear_piece_deadlines()`` removes deadlines on all pieces in + // the torrent. As if reset_piece_deadline() was called on all pieces. + void set_piece_deadline(piece_index_t index, int deadline, deadline_flags_t flags = {}) const; + void reset_piece_deadline(piece_index_t index) const; + void clear_piece_deadlines() const; + +#if TORRENT_ABI_VERSION == 1 + // This sets the bandwidth priority of this torrent. The priority of a + // torrent determines how much bandwidth its peers are assigned when + // distributing upload and download rate quotas. A high number gives more + // bandwidth. The priority must be within the range [0, 255]. + // + // The default priority is 0, which is the lowest priority. + // + // To query the priority of a torrent, use the + // ``torrent_handle::status()`` call. + // + // Torrents with higher priority will not necessarily get as much + // bandwidth as they can consume, even if there's is more quota. Other + // peers will still be weighed in when bandwidth is being distributed. + // With other words, bandwidth is not distributed strictly in order of + // priority, but the priority is used as a weight. + // + // Peers whose Torrent has a higher priority will take precedence when + // distributing unchoke slots. This is a strict prioritisation where + // every interested peer on a high priority torrent will be unchoked + // before any other, lower priority, torrents have any peers unchoked. + // deprecated in 1.2 + TORRENT_DEPRECATED + void set_priority(int prio) const; + +#if !TORRENT_NO_FPU + // fills the specified vector with the download progress [0, 1] + // of each file in the torrent. The files are ordered as in + // the torrent_info. + TORRENT_DEPRECATED + void file_progress(std::vector& progress) const; +#endif + + TORRENT_DEPRECATED + void file_status(std::vector& status) const; +#endif + +#if TORRENT_ABI_VERSION <= 2 + using file_progress_flags_t = libtorrent::file_progress_flags_t; +#endif + // only calculate file progress at piece granularity. This makes + // the file_progress() call cheaper and also only takes bytes that + // have passed the hash check into account, so progress cannot + // regress in this mode. + static constexpr file_progress_flags_t piece_granularity = 0_bit; + + // This function fills in the supplied vector, or returns a vector, with + // the number of bytes downloaded of each file in this torrent. The + // progress values are ordered the same as the files in the + // torrent_info. + // + // This operation is not very cheap. Its complexity is *O(n + mj)*. + // Where *n* is the number of files, *m* is the number of currently + // downloading pieces and *j* is the number of blocks in a piece. + // + // The ``flags`` parameter can be used to specify the granularity of the + // file progress. If left at the default value of 0, the progress will be + // as accurate as possible, but also more expensive to calculate. If + // ``torrent_handle::piece_granularity`` is specified, the progress will + // be specified in piece granularity. i.e. only pieces that have been + // fully downloaded and passed the hash check count. When specifying + // piece granularity, the operation is a lot cheaper, since libtorrent + // already keeps track of this internally and no calculation is required. + void file_progress(std::vector& progress, file_progress_flags_t flags = {}) const; + std::vector file_progress(file_progress_flags_t flags = {}) const; + void post_file_progress(file_progress_flags_t flags) const; + + // This function returns a vector with status about files + // that are open for this torrent. Any file that is not open + // will not be reported in the vector, i.e. it's possible that + // the vector is empty when returning, if none of the files in the + // torrent are currently open. + // + // See open_file_state + std::vector file_status() const; + + // If the torrent is in an error state (i.e. ``torrent_status::error`` is + // non-empty), this will clear the error and start the torrent again. + void clear_error() const; + + // ``trackers()`` returns the list of trackers for this torrent. The + // announce entry contains both a string ``url`` which specify the + // announce url for the tracker as well as an int ``tier``, which is + // specifies the order in which this tracker is tried. If you want + // libtorrent to use another list of trackers for this torrent, you can + // use ``replace_trackers()`` which takes a list of the same form as the + // one returned from ``trackers()`` and will replace it. If you want an + // immediate effect, you have to call force_reannounce(). See + // announce_entry. + // + // ``post_trackers()`` is the asynchronous version of ``trackers()``. It + // will trigger a tracker_list_alert to be posted. + // + // ``add_tracker()`` will look if the specified tracker is already in the + // set. If it is, it doesn't do anything. If it's not in the current set + // of trackers, it will insert it in the tier specified in the + // announce_entry. + // + // The updated set of trackers will be saved in the resume data, and when + // a torrent is started with resume data, the trackers from the resume + // data will replace the original ones. + std::vector trackers() const; + void replace_trackers(std::vector const&) const; + void add_tracker(announce_entry const&) const; + void post_trackers() const; + + // TODO: 3 unify url_seed and http_seed with just web_seed, using the + // web_seed_entry. + + // ``add_url_seed()`` adds another url to the torrent's list of url + // seeds. If the given url already exists in that list, the call has no + // effect. The torrent will connect to the server and try to download + // pieces from it, unless it's paused, queued, checking or seeding. + // ``remove_url_seed()`` removes the given url if it exists already. + // ``url_seeds()`` return a set of the url seeds currently in this + // torrent. Note that URLs that fails may be removed automatically from + // the list. + // + // See http-seeding_ for more information. + void add_url_seed(std::string const& url) const; + void remove_url_seed(std::string const& url) const; + std::set url_seeds() const; + + // These functions are identical as the ``*_url_seed()`` variants, but + // they operate on `BEP 17`_ web seeds instead of `BEP 19`_. + // + // See http-seeding_ for more information. + void add_http_seed(std::string const& url) const; + void remove_http_seed(std::string const& url) const; + std::set http_seeds() const; + + // add the specified extension to this torrent. The ``ext`` argument is + // a function that will be called from within libtorrent's context + // passing in the internal torrent object and the specified userdata + // pointer. The function is expected to return a shared pointer to + // a torrent_plugin instance. + void add_extension( + std::function(torrent_handle const&, client_data_t)> const& ext + , client_data_t userdata = client_data_t{}); + + // ``set_metadata`` expects the *info* section of metadata. i.e. The + // buffer passed in will be hashed and verified against the info-hash. If + // it fails, a ``metadata_failed_alert`` will be generated. If it passes, + // a ``metadata_received_alert`` is generated. The function returns true + // if the metadata is successfully set on the torrent, and false + // otherwise. If the torrent already has metadata, this function will not + // affect the torrent, and false will be returned. + bool set_metadata(span metadata) const; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + bool set_metadata(char const* metadata, int size) const + { return set_metadata({metadata, size}); } +#endif + + // Returns true if this handle refers to a valid torrent and false if it + // hasn't been initialized or if the torrent it refers to has been + // removed from the session AND destructed. + // + // To tell if the torrent_handle is in the session, use + // torrent_handle::in_session(). This will return true before + // session_handle::remove_torrent() is called, and false + // afterward. + // + // Clients should only use is_valid() to determine if the result of + // session::find_torrent() was successful. + // + // Unlike other member functions which return a value, is_valid() + // completes immediately, without blocking on a result from the + // network thread. Also unlike other functions, it never throws + // the system_error exception. + bool is_valid() const; + + // will delay the disconnect of peers that we're still downloading + // outstanding requests from. The torrent will not accept any more + // requests and will disconnect all idle peers. As soon as a peer is done + // transferring the blocks that were requested from it, it is + // disconnected. This is a graceful shut down of the torrent in the sense + // that no downloaded bytes are wasted. + static constexpr pause_flags_t graceful_pause = 0_bit; + + // hidden + TORRENT_DEPRECATED static constexpr pause_flags_t clear_disk_cache = 1_bit; + + // ``pause()``, and ``resume()`` will disconnect all peers and reconnect + // all peers respectively. When a torrent is paused, it will however + // remember all share ratios to all peers and remember all potential (not + // connected) peers. Torrents may be paused automatically if there is a + // file error (e.g. disk full) or something similar. See + // file_error_alert. + // + // For possible values of the ``flags`` parameter, see pause_flags_t. + // + // To know if a torrent is paused or not, call + // ``torrent_handle::flags()`` and check for the + // ``torrent_status::paused`` flag. + // + // .. note:: + // Torrents that are auto-managed may be automatically resumed again. It + // does not make sense to pause an auto-managed torrent without making it + // not auto-managed first. Torrents are auto-managed by default when added + // to the session. For more information, see queuing_. + // + void pause(pause_flags_t flags = {}) const; + void resume() const; + + // sets and gets the torrent state flags. See torrent_flags_t. + // The ``set_flags`` overload that take a mask will affect all + // flags part of the mask, and set their values to what the + // ``flags`` argument is set to. This allows clearing and + // setting flags in a single function call. + // The ``set_flags`` overload that just takes flags, sets all + // the specified flags and leave any other flags unchanged. + // ``unset_flags`` clears the specified flags, while leaving + // any other flags unchanged. + // + // The `seed_mode` flag is special, it can only be cleared once the + // torrent has been added, and it can only be set as part of the + // add_torrent_params flags, when adding the torrent. + torrent_flags_t flags() const; + void set_flags(torrent_flags_t flags, torrent_flags_t mask) const; + void set_flags(torrent_flags_t flags) const; + void unset_flags(torrent_flags_t flags) const; + + // Instructs libtorrent to flush all the disk caches for this torrent and + // close all file handles. This is done asynchronously and you will be + // notified that it's complete through cache_flushed_alert. + // + // Note that by the time you get the alert, libtorrent may have cached + // more data for the torrent, but you are guaranteed that whatever cached + // data libtorrent had by the time you called + // ``torrent_handle::flush_cache()`` has been written to disk. + void flush_cache() const; + + // ``force_recheck`` puts the torrent back in a state where it assumes to + // have no resume data. All peers will be disconnected and the torrent + // will stop announcing to the tracker. The torrent will be added to the + // checking queue, and will be checked (all the files will be read and + // compared to the piece hashes). Once the check is complete, the torrent + // will start connecting to peers again, as normal. + // The torrent will be placed last in queue, i.e. its queue position + // will be the highest of all torrents in the session. + void force_recheck() const; + + // the disk cache will be flushed before creating the resume data. + // This avoids a problem with file timestamps in the resume data in + // case the cache hasn't been flushed yet. + static constexpr resume_data_flags_t flush_disk_cache = 0_bit; + + // the resume data will contain the metadata from the torrent file as + // well. This is useful for clients that don't keep .torrent files + // around separately, or for torrents that were added via a magnet link. + static constexpr resume_data_flags_t save_info_dict = 1_bit; + + // this flag has the same behavior as the combination of: + // if_counters_changed | if_download_progress | if_config_changed | + // if_state_changed | if_metadata_changed + static constexpr resume_data_flags_t only_if_modified = 2_bit; + + // save resume data if any counters has changed since the last time + // resume data was saved. This includes upload/download counters, active + // time counters and scrape data. A torrent that is not paused will have + // its active time counters incremented continuously. + static constexpr resume_data_flags_t if_counters_changed = 3_bit; + + // save the resume data if any blocks have been downloaded since the + // last time resume data was saved. This includes: + // * checking existing files on disk + // * downloading a block from a peer + static constexpr resume_data_flags_t if_download_progress = 4_bit; + + // save the resume data if configuration options changed since last time + // the resume data was saved. This includes: + // * file- or piece priorities + // * upload- and download rate limits + // * change max-uploads (unchoke slots) + // * change max connection limit + // * enable/disable peer-exchange, local service discovery or DHT + // * enable/disable apply IP-filter + // * enable/disable auto-managed + // * enable/disable share-mode + // * enable/disable sequential-mode + // * files renamed + // * storage moved (save_path changed) + static constexpr resume_data_flags_t if_config_changed = 5_bit; + + // save the resume data if torrent state has changed since last time the + // resume data was saved. This includes: + // * upload mode + // * paused state + // * super-seeding + // * seed-mode + static constexpr resume_data_flags_t if_state_changed = 6_bit; + + // save the resume data if any *metadata* changed since the last time + // resume data was saved. This includes: + // * add/remove web seeds + // * add/remove trackers + // * receiving metadata for a magnet link + static constexpr resume_data_flags_t if_metadata_changed = 7_bit; + + // ``save_resume_data()`` asks libtorrent to generate fast-resume data for + // this torrent. The fast resume data (stored in an add_torrent_params + // object) can be used to resume a torrent in the next session without + // having to check all files for which pieces have been downloaded. It + // can also be used to save a .torrent file for a torrent_handle. + // + // This operation is asynchronous, ``save_resume_data`` will return + // immediately. The resume data is delivered when it's done through a + // save_resume_data_alert. + // + // The operation will fail, and post a save_resume_data_failed_alert + // instead, in the following cases: + // + // 1. The torrent is in the process of being removed. + // 2. No torrent state has changed since the last saving of resume + // data, and the only_if_modified flag is set. + // metadata (see libtorrent's metadata-from-peers_ extension) + // + // Note that some counters may be outdated by the time you receive the fast resume data + // + // When saving resume data because of shutting down, make sure not to + // remove_torrent() before you receive the save_resume_data_alert. + // There's no need to pause the session or torrent when saving resume + // data. + // + // The paused state of a torrent is saved in the resume data, so pausing + // all torrents before saving resume data will all torrents be restored + // in a paused state. + // + //.. note:: + // It is typically a good idea to save resume data whenever a torrent + // is completed or paused. If you save resume data for torrents when they are + // paused, you can accelerate the shutdown process by not saving resume + // data again for those torrents. Completed torrents should have their + // resume data saved when they complete and on exit, since their + // statistics might be updated. + // + // Example code to pause and save resume data for all torrents and wait + // for the alerts: + // + // .. code:: c++ + // + // extern int outstanding_resume_data; // global counter of outstanding resume data + // std::vector handles = ses.get_torrents(); + // for (torrent_handle const& h : handles) try + // { + // h.save_resume_data(torrent_handle::only_if_modified); + // ++outstanding_resume_data; + // } + // catch (lt::system_error const& e) + // { + // // the handle was invalid, ignore this one and move to the next + // } + // + // while (outstanding_resume_data > 0) + // { + // alert const* a = ses.wait_for_alert(seconds(30)); + // + // // if we don't get an alert within 30 seconds, abort + // if (a == nullptr) break; + // + // std::vector alerts; + // ses.pop_alerts(&alerts); + // + // for (alert* i : alerts) + // { + // if (alert_cast(i)) + // { + // process_alert(i); + // --outstanding_resume_data; + // continue; + // } + // + // save_resume_data_alert const* rd = alert_cast(i); + // if (rd == nullptr) + // { + // process_alert(i); + // continue; + // } + // + // std::ofstream out((rd->params.save_path + // + "/" + rd->params.name + ".fastresume").c_str() + // , std::ios_base::binary); + // std::vector buf = write_resume_data_buf(rd->params); + // out.write(buf.data(), buf.size()); + // --outstanding_resume_data; + // } + // } + // + //.. note:: + // Note how ``outstanding_resume_data`` is a global counter in this + // example. This is deliberate, otherwise there is a race condition for + // torrents that was just asked to save their resume data, they posted + // the alert, but it has not been received yet. Those torrents would + // report that they don't need to save resume data again, and skipped by + // the initial loop, and thwart the counter otherwise. + void save_resume_data(resume_data_flags_t flags = {}) const; + + // This function returns true if anything that is stored in the resume + // data has changed since the last time resume data was saved. + // The overload that takes ``flags`` let you ask if specific categories + // of properties have changed. These flags have the same behavior as in + // the save_resume_data() call. + // + // This is a *blocking* call. It will wait for a response from + // libtorrent's main thread. A way to avoid blocking is to instead + // call save_resume_data() directly, specifying the conditions under + // which resume data should be saved. + // + //.. note:: + // A torrent's resume data is considered saved as soon as the + // save_resume_data_alert is posted. It is important to make sure this + // alert is received and handled in order for this function to be + // meaningful. + bool need_save_resume_data() const; + bool need_save_resume_data(resume_data_flags_t flags) const; + + // Every torrent that is added is assigned a queue position exactly one + // greater than the greatest queue position of all existing torrents. + // Torrents that are being seeded have -1 as their queue position, since + // they're no longer in line to be downloaded. + // + // When a torrent is removed or turns into a seed, all torrents with + // greater queue positions have their positions decreased to fill in the + // space in the sequence. + // + // ``queue_position()`` returns the torrent's position in the download + // queue. The torrents with the smallest numbers are the ones that are + // being downloaded. The smaller number, the closer the torrent is to the + // front of the line to be started. + // + // The queue position is also available in the torrent_status. + // + // The ``queue_position_*()`` functions adjust the torrents position in + // the queue. Up means closer to the front and down means closer to the + // back of the queue. Top and bottom refers to the front and the back of + // the queue respectively. + queue_position_t queue_position() const; + void queue_position_up() const; + void queue_position_down() const; + void queue_position_top() const; + void queue_position_bottom() const; + + // updates the position in the queue for this torrent. The relative order + // of all other torrents remain intact but their numerical queue position + // shifts to make space for this torrent's new position + void queue_position_set(queue_position_t p) const; + + // For SSL torrents, use this to specify a path to a .pem file to use as + // this client's certificate. The certificate must be signed by the + // certificate in the .torrent file to be valid. + // + // The set_ssl_certificate_buffer() overload takes the actual certificate, + // private key and DH params as strings, rather than paths to files. + // + // ``cert`` is a path to the (signed) certificate in .pem format + // corresponding to this torrent. + // + // ``private_key`` is a path to the private key for the specified + // certificate. This must be in .pem format. + // + // ``dh_params`` is a path to the Diffie-Hellman parameter file, which + // needs to be in .pem format. You can generate this file using the + // openssl command like this: ``openssl dhparam -outform PEM -out + // dhparams.pem 512``. + // + // ``passphrase`` may be specified if the private key is encrypted and + // requires a passphrase to be decrypted. + // + // Note that when a torrent first starts up, and it needs a certificate, + // it will suspend connecting to any peers until it has one. It's + // typically desirable to resume the torrent after setting the SSL + // certificate. + // + // If you receive a torrent_need_cert_alert, you need to call this to + // provide a valid cert. If you don't have a cert you won't be allowed to + // connect to any peers. + void set_ssl_certificate(std::string const& certificate + , std::string const& private_key + , std::string const& dh_params + , std::string const& passphrase = ""); + void set_ssl_certificate_buffer(std::string const& certificate + , std::string const& private_key + , std::string const& dh_params); + + // torrent_file() returns a pointer to the torrent_info object + // associated with this torrent. The torrent_info object may be a copy + // of the internal object. If the torrent doesn't have metadata, the + // pointer will not be initialized (i.e. a nullptr). The torrent may be + // in a state without metadata only if it was started without a .torrent + // file, e.g. by being added by magnet link. + // + // Note that the torrent_info object returned here may be a different + // instance than the one added to the session, with different attributes + // like piece layers, dht nodes and trackers. A torrent_info object does + // not round-trip cleanly when added to a session. + // + // If you want to save a .torrent file from the torrent_handle, instead + // call save_resume_data() and write_torrent_file() the + // add_torrent_params object passed back in the alert. + // + // torrent_file_with_hashes() returns a *copy* of the internal + // torrent_info and piece layer hashes (if it's a v2 torrent). The piece + // layers will only be included if they are available. If this torrent + // was added from a .torrent file with piece layers or if it's seeding, + // the piece layers are available. This function is more expensive than + // torrent_file() since it needs to make copies of this information. + // + // The torrent_file_with_hashes() is here for backwards compatibility + // when constructing a create_torrent object from a torrent_info that's + // in a session. Prefer save_resume_data() + write_torrent_file(). + // + // Note that a torrent added from a magnet link may not have the full + // merkle trees for all files, and hence not have the complete piece + // layers. In that state, you cannot create a .torrent file even from + // the torrent_info returned from torrent_file_with_hashes(). Once the + // torrent completes downloading all files, becoming a seed, you can + // make a .torrent file from it. + std::shared_ptr torrent_file() const; + std::shared_ptr torrent_file_with_hashes() const; + + // returns the piece layers for all files in the torrent. If this is a + // v1 torrent (and doesn't have any piece layers) it returns an empty + // vector. This is a blocking call that will synchronize with the + // libtorrent network thread. + std::vector> piece_layers() const; + +#if TORRENT_ABI_VERSION == 1 + + // ================ start deprecation ============ + + // deprecated in 1.2 + // use set_flags() and unset_flags() instead + TORRENT_DEPRECATED + void stop_when_ready(bool b) const; + TORRENT_DEPRECATED + void set_upload_mode(bool b) const; + TORRENT_DEPRECATED + void set_share_mode(bool b) const; + TORRENT_DEPRECATED + void apply_ip_filter(bool b) const; + TORRENT_DEPRECATED + void auto_managed(bool m) const; + TORRENT_DEPRECATED + void set_pinned(bool p) const; + TORRENT_DEPRECATED + void set_sequential_download(bool sd) const; + + + // deprecated in 1.0 + // use status() instead (with query_save_path) + TORRENT_DEPRECATED + std::string save_path() const; + + // deprecated in 1.0 + // use status() instead (with query_name) + // returns the name of this torrent, in case it doesn't + // have metadata it returns the name assigned to it + // when it was added. + TORRENT_DEPRECATED + std::string name() const; + + // use torrent_file() instead + TORRENT_DEPRECATED + const torrent_info& get_torrent_info() const; + + // deprecated in 0.16, feature will be removed + TORRENT_DEPRECATED + int get_peer_upload_limit(tcp::endpoint ip) const; + TORRENT_DEPRECATED + int get_peer_download_limit(tcp::endpoint ip) const; + TORRENT_DEPRECATED + void set_peer_upload_limit(tcp::endpoint ip, int limit) const; + TORRENT_DEPRECATED + void set_peer_download_limit(tcp::endpoint ip, int limit) const; + + // deprecated in 0.16, feature will be removed + TORRENT_DEPRECATED + void set_ratio(float up_down_ratio) const; + + // deprecated in 0.16. use flags() instead + TORRENT_DEPRECATED + bool is_seed() const; + TORRENT_DEPRECATED + bool is_finished() const; + TORRENT_DEPRECATED + bool is_paused() const; + TORRENT_DEPRECATED + bool is_auto_managed() const; + TORRENT_DEPRECATED + bool is_sequential_download() const; + TORRENT_DEPRECATED + bool has_metadata() const; + TORRENT_DEPRECATED + bool super_seeding() const; + + // deprecated in 0.14 + // use save_resume_data() instead. It is async. and + // will return the resume data in an alert + TORRENT_DEPRECATED + entry write_resume_data() const; + + // ``use_interface()`` sets the network interface this torrent will use + // when it opens outgoing connections. By default, it uses the same + // interface as the session uses to listen on. The parameter must be a + // string containing one or more, comma separated, ip-address (either an + // IPv4 or IPv6 address). When specifying multiple interfaces, the + // torrent will round-robin which interface to use for each outgoing + // connection. This is useful for clients that are multi-homed. + TORRENT_DEPRECATED + void use_interface(const char* net_interface) const; + // ================ end deprecation ============ +#endif + + // The piece availability is the number of peers that we are connected + // that has advertised having a particular piece. This is the information + // that libtorrent uses in order to prefer picking rare pieces. + // + // ``post_piece_availability()`` will trigger a piece_availability_alert + // to be posted. + // + // ``piece_availability()`` fills the specified ``std::vector`` + // with the availability for each piece in this torrent. libtorrent does + // not keep track of availability for seeds, so if the torrent is + // seeding the availability for all pieces is reported as 0. + void post_piece_availability() const; + void piece_availability(std::vector& avail) const; + + // These functions are used to set and get the priority of individual + // pieces. By default all pieces have priority 4. That means that the + // random rarest first algorithm is effectively active for all pieces. + // You may however change the priority of individual pieces. There are 8 + // priority levels. 0 means not to download the piece at all. Otherwise, + // lower priority values means less likely to be picked. Piece priority + // takes precedence over piece availability. Every piece with priority 7 + // will be attempted to be picked before a priority 6 piece and so on. + // + // The default priority of pieces is 4. + // + // Piece priorities can not be changed for torrents that have not + // downloaded the metadata yet. Magnet links won't have metadata + // immediately. see the metadata_received_alert. + // + // ``piece_priority`` sets or gets the priority for an individual piece, + // specified by ``index``. + // + // ``prioritize_pieces`` takes a vector of integers, one integer per + // piece in the torrent. All the piece priorities will be updated with + // the priorities in the vector. + // The second overload of ``prioritize_pieces`` that takes a vector of pairs + // will update the priorities of only select pieces, and leave all other + // unaffected. Each pair is (piece, priority). That is, the first item is + // the piece index and the second item is the priority of that piece. + // Invalid entries, where the piece index or priority is out of range, are + // not allowed. + // + // ``get_piece_priorities`` returns a vector with one element for each piece + // in the torrent. Each element is the current priority of that piece. + // + // It's possible to cancel the effect of *file* priorities by setting the + // priorities for the affected pieces. Care has to be taken when mixing + // usage of file- and piece priorities. + void piece_priority(piece_index_t index, download_priority_t priority) const; + download_priority_t piece_priority(piece_index_t index) const; + void prioritize_pieces(std::vector const& pieces) const; + void prioritize_pieces(std::vector> const& pieces) const; + std::vector get_piece_priorities() const; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + void prioritize_pieces(std::vector const& pieces) const; + TORRENT_DEPRECATED + void prioritize_pieces(std::vector> const& pieces) const; + TORRENT_DEPRECATED + std::vector piece_priorities() const; +#endif + + // ``index`` must be in the range [0, number_of_files). + // + // ``file_priority()`` queries or sets the priority of file ``index``. + // + // ``prioritize_files()`` takes a vector that has at as many elements as + // there are files in the torrent. Each entry is the priority of that + // file. The function sets the priorities of all the pieces in the + // torrent based on the vector. + // + // ``get_file_priorities()`` returns a vector with the priorities of all + // files. + // + // The priority values are the same as for piece_priority(). See + // download_priority_t. + // + // Whenever a file priority is changed, all other piece priorities are + // reset to match the file priorities. In order to maintain special + // priorities for particular pieces, piece_priority() has to be called + // again for those pieces. + // + // You cannot set the file priorities on a torrent that does not yet have + // metadata or a torrent that is a seed. ``file_priority(int, int)`` and + // prioritize_files() are both no-ops for such torrents. + // + // Since changing file priorities may involve disk operations (of moving + // files in- and out of the part file), the internal accounting of file + // priorities happen asynchronously. i.e. setting file priorities and then + // immediately querying them may not yield the same priorities just set. + // To synchronize with the priorities taking effect, wait for the + // file_prio_alert. + // + // When combining file- and piece priorities, the resume file will record + // both. When loading the resume data, the file priorities will be applied + // first, then the piece priorities. + // + // Moving data from a file into the part file is currently not + // supported. If a file has its priority set to 0 *after* it has already + // been created, it will not be moved into the partfile. + void file_priority(file_index_t index, download_priority_t priority) const; + download_priority_t file_priority(file_index_t index) const; + void prioritize_files(std::vector const& files) const; + std::vector get_file_priorities() const; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + void prioritize_files(std::vector const& files) const; + TORRENT_DEPRECATED + std::vector file_priorities() const; +#endif + + // by default, force-reannounce will still honor the min-interval + // published by the tracker. If this flag is set, it will be ignored + // and the tracker is announced immediately. + static constexpr reannounce_flags_t ignore_min_interval = 0_bit; + + // ``force_reannounce()`` will force this torrent to do another tracker + // request, to receive new peers. The ``seconds`` argument specifies how + // many seconds from now to issue the tracker announces. + // + // If the tracker's ``min_interval`` has not passed since the last + // announce, the forced announce will be scheduled to happen immediately + // as the ``min_interval`` expires. This is to honor trackers minimum + // re-announce interval settings. + // + // The ``tracker_index`` argument specifies which tracker to re-announce. + // If set to -1 (which is the default), all trackers are re-announce. + // + // The ``flags`` argument can be used to affect the re-announce. See + // ignore_min_interval. + // + // ``force_dht_announce`` will announce the torrent to the DHT + // immediately. + // + // ``force_lsd_announce`` will announce the torrent on LSD + // immediately. + void force_reannounce(int seconds = 0, int idx = -1, reannounce_flags_t = {}) const; + void force_dht_announce() const; + void force_lsd_announce() const; + +#if TORRENT_ABI_VERSION == 1 + // forces a reannounce in the specified amount of time. + // This overrides the default announce interval, and no + // announce will take place until the given time has + // timed out. + TORRENT_DEPRECATED + void force_reannounce(boost::posix_time::time_duration) const; +#endif + + // ``scrape_tracker()`` will send a scrape request to a tracker. By + // default (``idx`` = -1) it will scrape the last working tracker. If + // ``idx`` is >= 0, the tracker with the specified index will scraped. + // + // A scrape request queries the tracker for statistics such as total + // number of incomplete peers, complete peers, number of downloads etc. + // + // This request will specifically update the ``num_complete`` and + // ``num_incomplete`` fields in the torrent_status struct once it + // completes. When it completes, it will generate a scrape_reply_alert. + // If it fails, it will generate a scrape_failed_alert. + void scrape_tracker(int idx = -1) const; + + // ``set_upload_limit`` will limit the upload bandwidth used by this + // particular torrent to the limit you set. It is given as the number of + // bytes per second the torrent is allowed to upload. + // ``set_download_limit`` works the same way but for download bandwidth + // instead of upload bandwidth. Note that setting a higher limit on a + // torrent then the global limit + // (``settings_pack::upload_rate_limit``) will not override the global + // rate limit. The torrent can never upload more than the global rate + // limit. + // + // ``upload_limit`` and ``download_limit`` will return the current limit + // setting, for upload and download, respectively. + // + // Local peers are not rate limited by default. see peer-classes_. + void set_upload_limit(int limit) const; + int upload_limit() const; + void set_download_limit(int limit) const; + int download_limit() const; + + // ``connect_peer()`` is a way to manually connect to peers that one + // believe is a part of the torrent. If the peer does not respond, or is + // not a member of this torrent, it will simply be disconnected. No harm + // can be done by using this other than an unnecessary connection attempt + // is made. If the torrent is uninitialized or in queued or checking + // mode, this will throw system_error. The second (optional) + // argument will be bitwise ORed into the source mask of this peer. + // Typically this is one of the source flags in peer_info. i.e. + // ``tracker``, ``pex``, ``dht`` etc. + // + // For possible values of ``flags``, see pex_flags_t. + void connect_peer(tcp::endpoint const& adr, peer_source_flags_t source = {} + , pex_flags_t flags = pex_encryption | pex_utp | pex_holepunch) const; + + // This will disconnect all peers and clear the peer list for this + // torrent. New peers will have to be acquired before resuming, from + // trackers, DHT or local service discovery, for example. + void clear_peers(); + + // ``set_max_uploads()`` sets the maximum number of peers that's unchoked + // at the same time on this torrent. If you set this to -1, there will be + // no limit. This defaults to infinite. The primary setting controlling + // this is the global unchoke slots limit, set by unchoke_slots_limit in + // settings_pack. + // + // ``max_uploads()`` returns the current settings. + void set_max_uploads(int max_uploads) const; + int max_uploads() const; + + // ``set_max_connections()`` sets the maximum number of connection this + // torrent will open. If all connections are used up, incoming + // connections may be refused or poor connections may be closed. This + // must be at least 2. The default is unlimited number of connections. If + // -1 is given to the function, it means unlimited. There is also a + // global limit of the number of connections, set by + // ``connections_limit`` in settings_pack. + // + // ``max_connections()`` returns the current settings. + void set_max_connections(int max_connections) const; + int max_connections() const; + +#if TORRENT_ABI_VERSION == 1 + // sets a username and password that will be sent along in the HTTP-request + // of the tracker announce. Set this if the tracker requires authorization. + TORRENT_DEPRECATED + void set_tracker_login(std::string const& name + , std::string const& password) const; +#endif + + // Moves the file(s) that this torrent are currently seeding from or + // downloading to. If the given ``save_path`` is not located on the same + // drive as the original save path, the files will be copied to the new + // drive and removed from their original location. This will block all + // other disk IO, and other torrents download and upload rates may drop + // while copying the file. + // + // Since disk IO is performed in a separate thread, this operation is + // also asynchronous. Once the operation completes, the + // ``storage_moved_alert`` is generated, with the new path as the + // message. If the move fails for some reason, + // ``storage_moved_failed_alert`` is generated instead, containing the + // error message. + // + // The ``flags`` argument determines the behavior of the copying/moving + // of the files in the torrent. see move_flags_t. + // + // ``always_replace_files`` is the default and replaces any file that + // exist in both the source directory and the target directory. + // + // ``fail_if_exist`` first check to see that none of the copy operations + // would cause an overwrite. If it would, it will fail. Otherwise it will + // proceed as if it was in ``always_replace_files`` mode. Note that there + // is an inherent race condition here. If the files in the target + // directory appear after the check but before the copy or move + // completes, they will be overwritten. When failing because of files + // already existing in the target path, the ``error`` of + // ``move_storage_failed_alert`` is set to + // ``boost::system::errc::file_exists``. + // + // The intention is that a client may use this as a probe, and if it + // fails, ask the user which mode to use. The client may then re-issue + // the ``move_storage`` call with one of the other modes. + // + // ``dont_replace`` always keeps the existing file in the target + // directory, if there is one. The source files will still be removed in + // that case. Note that it won't automatically re-check files. If an + // incomplete torrent is moved into a directory with the complete files, + // pause, move, force-recheck and resume. Without the re-checking, the + // torrent will keep downloading and files in the new download directory + // will be overwritten. + // + // Files that have been renamed to have absolute paths are not moved by + // this function. Keep in mind that files that don't belong to the + // torrent but are stored in the torrent's directory may be moved as + // well. This goes for files that have been renamed to absolute paths + // that still end up inside the save path. + // + // When copying files, sparse regions are not likely to be preserved. + // This makes it proportionally more expensive to move a large torrent + // when only few pieces have been downloaded, since the files are then + // allocated with zeros in the destination directory. + void move_storage(std::string const& save_path + , move_flags_t flags = move_flags_t::always_replace_files + ) const; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + TORRENT_DEPRECATED + void move_storage(std::string const& save_path, int flags) const; +#endif + + // Renames the file with the given index asynchronously. The rename + // operation is complete when either a file_renamed_alert or + // file_rename_failed_alert is posted. + void rename_file(file_index_t index, std::string const& new_name) const; + +#if TORRENT_ABI_VERSION == 1 + // Enables or disabled super seeding/initial seeding for this torrent. + // The torrent needs to be a seed for this to take effect. + TORRENT_DEPRECATED + void super_seeding(bool on) const; +#endif // TORRENT_ABI_VERSION + + // returns the info-hash(es) of the torrent. If this handle is to a + // torrent that hasn't loaded yet (for instance by being added) by a + // URL, the returned value is undefined. + // The ``info_hash()`` returns the SHA-1 info-hash for v1 torrents and a + // truncated hash for v2 torrents. For the full v2 info-hash, use + // ``info_hashes()`` instead. + sha1_hash info_hash() const; + info_hash_t info_hashes() const; + + // comparison operators. The order of the torrents is unspecified + // but stable. + bool operator==(const torrent_handle& h) const + { return !m_torrent.owner_before(h.m_torrent) && !h.m_torrent.owner_before(m_torrent); } + bool operator!=(const torrent_handle& h) const + { return m_torrent.owner_before(h.m_torrent) || h.m_torrent.owner_before(m_torrent); } + bool operator<(const torrent_handle& h) const + { return m_torrent.owner_before(h.m_torrent); } + + // returns a unique identifier for this torrent. It's not a dense index. + // It's not preserved across sessions. + std::uint32_t id() const + { + uintptr_t ret = reinterpret_cast(m_torrent.lock().get()); + // a torrent object is about 1024 (2^10) bytes, so + // it's safe to shift 10 bits + return std::uint32_t(ret >> 10); + } + + // This function is intended only for use by plugins and the alert + // dispatch function. This type does not have a stable ABI and should + // be relied on as little as possible. Accessing the handle returned by + // this function is not thread safe outside of libtorrent's internal + // thread (which is used to invoke plugin callbacks). + // The ``torrent`` class is not only eligible for changing ABI across + // minor versions of libtorrent, its layout is also dependent on build + // configuration. This adds additional requirements on a client to be + // built with the exact same build configuration as libtorrent itself. + // i.e. the ``TORRENT_`` macros must match between libtorrent and the + // client builds. + std::shared_ptr native_handle() const; + + // returns the userdata pointer as set in add_torrent_params + client_data_t userdata() const; + + // Returns true if the torrent is in the session. It returns true before + // session::remove_torrent() is called, and false afterward. + // + // Note that this is a blocking function, unlike torrent_handle::is_valid() + // which returns immediately. + bool in_session() const; + + private: + + template + void async_call(Fun f, Args&&... a) const; + + template + void sync_call(Fun f, Args&&... a) const; + + template + Ret sync_call_ret(Ret def, Fun f, Args&&... a) const; + + explicit torrent_handle(std::weak_ptr const& t) + { if (!t.expired()) m_torrent = t; } + + std::weak_ptr m_torrent; + }; +} + +namespace std { + template <> + struct hash + { + std::size_t operator()(libtorrent::torrent_handle const& th) const + { + return libtorrent::hash_value(th); + } + }; +} + +#endif // TORRENT_TORRENT_HANDLE_HPP_INCLUDED diff --git a/include/libtorrent/torrent_info.hpp b/include/libtorrent/torrent_info.hpp new file mode 100644 index 0000000..ff6facc --- /dev/null +++ b/include/libtorrent/torrent_info.hpp @@ -0,0 +1,792 @@ +/* + +Copyright (c) 2003-2011, 2013-2022, Arvid Norberg +Copyright (c) 2004, Magnus Jonsson +Copyright (c) 2016-2018, 2020, Alden Torres +Copyright (c) 2016, Markus +Copyright (c) 2017, 2019, Andrei Kurushin +Copyright (c) 2017, Pavel Pimenov +Copyright (c) 2017-2019, Steven Siloti +Copyright (c) 2019, Amir Abrams +Copyright (c) 2020, Mike Tzou +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_INFO_HPP_INCLUDED +#define TORRENT_TORRENT_INFO_HPP_INCLUDED + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/copy_ptr.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/info_hash.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/index_range.hpp" +#include "libtorrent/aux_/merkle_tree.hpp" + +namespace libtorrent { + + struct invariant_access; + +namespace aux { + + // internal, exposed for the unit test + TORRENT_EXTRA_EXPORT void sanitize_append_path_element(std::string& path + , string_view element); + TORRENT_EXTRA_EXPORT bool verify_encoding(std::string& target); + + struct internal_drained_state + { + aux::vector urls; + std::vector web_seeds; + std::vector> nodes; + }; +} + + // the web_seed_entry holds information about a web seed (also known + // as URL seed or HTTP seed). It is essentially a URL with some state + // associated with it. For more information, see `BEP 17`_ and `BEP 19`_. + struct TORRENT_EXPORT web_seed_entry + { + // http seeds are different from url seeds in the + // protocol they use. http seeds follows the original + // http seed spec. by John Hoffman + enum type_t { url_seed, http_seed }; + + using headers_t = std::vector>; + + // hidden + web_seed_entry(std::string url_, type_t type_ + , std::string auth_ = std::string() + , headers_t extra_headers_ = headers_t()); + + // URL and type comparison + bool operator==(web_seed_entry const& e) const + { return type == e.type && url == e.url; } + + // URL and type less-than comparison + bool operator<(web_seed_entry const& e) const + { + if (url < e.url) return true; + if (url > e.url) return false; + return type < e.type; + } + + // The URL of the web seed + std::string url; + + // Optional authentication. If this is set, it's passed + // in as HTTP basic auth to the web seed. The format is: + // username:password. + std::string auth; + + // Any extra HTTP headers that need to be passed to the web seed + headers_t extra_headers; + + // The type of web seed (see type_t) + std::uint8_t type; + }; + + // hidden + class from_span_t {}; + + // used to disambiguate a bencoded buffer and a filename + extern TORRENT_EXPORT from_span_t from_span; + + // this object holds configuration options for limits to use when loading + // torrents. They are meant to prevent loading potentially malicious torrents + // that cause excessive memory allocations. + struct TORRENT_EXPORT load_torrent_limits + { + // the max size of a .torrent file to load into RAM + int max_buffer_size = 10000000; + + // the max number of pieces allowed in the torrent + int max_pieces = 0x200000; + + // the max recursion depth in the bdecoded structure + int max_decode_depth = 100; + + // the max number of bdecode tokens + int max_decode_tokens = 3000000; + }; + + using torrent_info_flags_t = flags::bitfield_flag; + +TORRENT_VERSION_NAMESPACE_3 + + // the torrent_info class holds the information found in a .torrent file. + class TORRENT_EXPORT torrent_info + { + public: + + // The constructor that takes an info-hash will initialize the info-hash + // to the given value, but leave all other fields empty. This is used + // internally when downloading torrents without the metadata. The + // metadata will be created by libtorrent as soon as it has been + // downloaded from the swarm. + // + // The constructor that takes a bdecode_node will create a torrent_info + // object from the information found in the given torrent_file. The + // bdecode_node represents a tree node in an bencoded file. To load an + // ordinary .torrent file into a bdecode_node, use bdecode(). + // + // The version that takes a buffer pointer and a size will decode it as a + // .torrent file and initialize the torrent_info object for you. + // + // The version that takes a filename will simply load the torrent file + // and decode it inside the constructor, for convenience. This might not + // be the most suitable for applications that want to be able to report + // detailed errors on what might go wrong. + // + // There is an upper limit on the size of the torrent file that will be + // loaded by the overload taking a filename. If it's important that even + // very large torrent files are loaded, use one of the other overloads. + // + // The overloads that takes an ``error_code const&`` never throws if an + // error occur, they will simply set the error code to describe what went + // wrong and not fully initialize the torrent_info object. The overloads + // that do not take the extra error_code parameter will always throw if + // an error occurs. These overloads are not available when building + // without exception support. + // + // The overload that takes a ``span`` also needs an extra parameter of + // type ``from_span_t`` to disambiguate the ``std::string`` overload for + // string literals. There is an object in the libtorrent namespace of this + // type called ``from_span``. +#ifndef BOOST_NO_EXCEPTIONS + explicit torrent_info(bdecode_node const& torrent_file); + torrent_info(char const* buffer, int size) + : torrent_info(span{buffer, size}, from_span) {} + explicit torrent_info(span buffer, from_span_t); + explicit torrent_info(std::string const& filename); + torrent_info(std::string const& filename, load_torrent_limits const& cfg); + torrent_info(span buffer, load_torrent_limits const& cfg, from_span_t); + torrent_info(bdecode_node const& torrent_file, load_torrent_limits const& cfg); +#endif // BOOST_NO_EXCEPTIONS + torrent_info(torrent_info const& t); + explicit torrent_info(info_hash_t const& info_hash); + torrent_info(bdecode_node const& torrent_file, error_code& ec); + torrent_info(char const* buffer, int size, error_code& ec) + : torrent_info(span{buffer, size}, ec, from_span) {} + torrent_info(span buffer, error_code& ec, from_span_t); + torrent_info(std::string const& filename, error_code& ec); + +#if TORRENT_ABI_VERSION == 1 +#ifndef BOOST_NO_EXCEPTIONS + TORRENT_DEPRECATED + torrent_info(char const* buffer, int size, int) + : torrent_info(span{buffer, size}, from_span) {} +#endif + TORRENT_DEPRECATED + torrent_info(bdecode_node const& torrent_file, error_code& ec, int) + : torrent_info(torrent_file, ec) {} + TORRENT_DEPRECATED + torrent_info(std::string const& filename, error_code& ec, int) + : torrent_info(filename, ec) {} + TORRENT_DEPRECATED + torrent_info(char const* buffer, int size, error_code& ec, int) + : torrent_info(span{buffer, size}, ec, from_span) {} +#endif // TORRENT_ABI_VERSION + + // frees all storage associated with this torrent_info object + ~torrent_info(); + + // hidden + torrent_info& operator=(torrent_info const&) = delete; + torrent_info& operator=(torrent_info&&); + + // The file_storage object contains the information on how to map the + // pieces to files. It is separated from the torrent_info object because + // when creating torrents a storage object needs to be created without + // having a torrent file. When renaming files in a storage, the storage + // needs to make its own copy of the file_storage in order to make its + // mapping differ from the one in the torrent file. + // + // ``orig_files()`` returns the original (unmodified) file storage for + // this torrent. This is used by the web server connection, which needs + // to request files with the original names. Filename may be changed using + // ``torrent_info::rename_file()``. + // + // For more information on the file_storage object, see the separate + // document on how to create torrents. + file_storage const& files() const { return m_files; } + file_storage const& orig_files() const; + + // Renames the file with the specified index to the new name. The new + // filename is reflected by the ``file_storage`` returned by ``files()`` + // but not by the one returned by ``orig_files()``. + // + // If you want to rename the base name of the torrent (for a multi file + // torrent), you can copy the ``file_storage`` (see files() and + // orig_files() ), change the name, and then use `remap_files()`_. + // + // The ``new_filename`` can both be a relative path, in which case the + // file name is relative to the ``save_path`` of the torrent. If the + // ``new_filename`` is an absolute path (i.e. ``is_complete(new_filename) + // == true``), then the file is detached from the ``save_path`` of the + // torrent. In this case the file is not moved when move_storage() is + // invoked. + void rename_file(file_index_t index, std::string const& new_filename); + + // .. warning:: + // Using `remap_files()` is discouraged as it's incompatible with v2 + // torrents. This is because the piece boundaries and piece hashes in + // v2 torrents are intimately tied to the file boundaries. Instead, + // just rename individual files, or implement a custom disk_interface + // to customize how to store files. + // + // Remaps the file storage to a new file layout. This can be used to, for + // instance, download all data in a torrent to a single file, or to a + // number of fixed size sector aligned files, regardless of the number + // and sizes of the files in the torrent. + // + // The new specified ``file_storage`` must have the exact same size as + // the current one. + void remap_files(file_storage const& f); + + // ``add_tracker()`` adds a tracker to the announce-list. The ``tier`` + // determines the order in which the trackers are to be tried. + // The ``trackers()`` function will return a sorted vector of + // announce_entry. Each announce entry contains a string, which is + // the tracker url, and a tier index. The tier index is the high-level + // priority. No matter which trackers that works or not, the ones with + // lower tier will always be tried before the one with higher tier + // number. For more information, see announce_entry. + // + // ``trackers()`` returns all entries from announce-list. + // + // ``clear_trackers()`` removes all trackers from announce-list. + void add_tracker(std::string const& url, int tier = 0); + void add_tracker(std::string const& url, int tier + , announce_entry::tracker_source source); + std::vector const& trackers() const { return m_urls; } + void clear_trackers(); + + // These two functions are related to `BEP 38`_ (mutable torrents). The + // vectors returned from these correspond to the "similar" and + // "collections" keys in the .torrent file. Both info-hashes and + // collections from within the info-dict and from outside of it are + // included. + std::vector similar_torrents() const; + std::vector collections() const; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 0.16. Use web_seeds() instead + TORRENT_DEPRECATED + std::vector url_seeds() const; + TORRENT_DEPRECATED + std::vector http_seeds() const; +#endif // TORRENT_ABI_VERSION + + // ``web_seeds()`` returns all url seeds and http seeds in the torrent. + // Each entry is a ``web_seed_entry`` and may refer to either a url seed + // or http seed. + // + // ``add_url_seed()`` and ``add_http_seed()`` adds one url to the list of + // url/http seeds. + // + // ``set_web_seeds()`` replaces all web seeds with the ones specified in + // the ``seeds`` vector. + // + // The ``extern_auth`` argument can be used for other authorization + // schemes than basic HTTP authorization. If set, it will override any + // username and password found in the URL itself. The string will be sent + // as the HTTP authorization header's value (without specifying "Basic"). + // + // The ``extra_headers`` argument defaults to an empty list, but can be + // used to insert custom HTTP headers in the requests to a specific web + // seed. + // + // See http-seeding_ for more information. + void add_url_seed(std::string const& url + , std::string const& ext_auth = std::string() + , web_seed_entry::headers_t const& ext_headers = web_seed_entry::headers_t()); + void add_http_seed(std::string const& url + , std::string const& extern_auth = std::string() + , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t()); + std::vector const& web_seeds() const { return m_web_seeds; } + void set_web_seeds(std::vector seeds); + + // internal + aux::internal_drained_state _internal_drain() { + return aux::internal_drained_state{std::move(m_urls), std::move(m_web_seeds), std::move(m_nodes)}; + } + + // ``total_size()`` returns the total number of bytes the torrent-file + // represents. Note that this is the number of pieces times the piece + // size (modulo the last piece possibly being smaller). With pad files, + // the total size will be larger than the sum of all (regular) file + // sizes. + std::int64_t total_size() const { return m_files.total_size(); } + + // ``piece_length()`` and ``num_pieces()`` returns the number of byte + // for each piece and the total number of pieces, respectively. The + // difference between ``piece_size()`` and ``piece_length()`` is that + // ``piece_size()`` takes the piece index as argument and gives you the + // exact size of that piece. It will always be the same as + // ``piece_length()`` except in the case of the last piece, which may be + // smaller. + int piece_length() const { return m_files.piece_length(); } + int num_pieces() const { return m_files.num_pieces(); } + + // returns the number of blocks there are in the typical piece. There + // may be fewer in the last piece) + int blocks_per_piece() const { return m_files.blocks_per_piece(); } + + // ``last_piece()`` returns the index to the last piece in the torrent and + // ``end_piece()`` returns the index to the one-past-end piece in the + // torrent + // ``piece_range()`` returns an implementation-defined type that can be + // used as the container in a range-for loop. Where the values are the + // indices of all pieces in the file_storage. + piece_index_t last_piece() const { return m_files.last_piece(); } + piece_index_t end_piece() const + { + TORRENT_ASSERT(m_files.num_pieces() > 0); + return m_files.end_piece(); + } + index_range piece_range() const + { return m_files.piece_range(); } + + // returns the info-hash of the torrent. For BitTorrent v2 support, use + // ``info_hashes()`` to get an object that may hold both a v1 and v2 + // info-hash + sha1_hash info_hash() const noexcept; + info_hash_t const& info_hashes() const { return m_info_hash; } + + // returns whether this torrent has v1 and/or v2 metadata, respectively. + // Hybrid torrents have both. These are shortcuts for + // info_hashes().has_v1() and info_hashes().has_v2() calls. + bool v1() const; + bool v2() const; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.0. Use the variants that take an index instead + // internal_file_entry is no longer exposed in the API + using file_iterator = file_storage::iterator; + using reverse_file_iterator = file_storage::reverse_iterator; + + // This class will need some explanation. First of all, to get a list of + // all files in the torrent, you can use ``begin_files()``, + // ``end_files()``, ``rbegin_files()`` and ``rend_files()``. These will + // give you standard vector iterators with the type + // ``internal_file_entry``, which is an internal type. + // + // You can resolve it into the public representation of a file + // (``file_entry``) using the ``file_storage::at`` function, which takes + // an index and an iterator. + TORRENT_DEPRECATED + file_iterator begin_files() const { return m_files.begin_deprecated(); } + TORRENT_DEPRECATED + file_iterator end_files() const { return m_files.end_deprecated(); } + reverse_file_iterator rbegin_files() const { return m_files.rbegin_deprecated(); } + TORRENT_DEPRECATED + reverse_file_iterator rend_files() const { return m_files.rend_deprecated(); } + + TORRENT_DEPRECATED + file_iterator file_at_offset(std::int64_t offset) const + { return m_files.file_at_offset_deprecated(offset); } + +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" + + TORRENT_DEPRECATED + file_entry file_at(int index) const { return m_files.at_deprecated(index); } + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif // TORRENT_ABI_VERSION + + // If you need index-access to files you can use the ``num_files()`` along + // with the ``file_path()``, ``file_size()``-family of functions to access + // files using indices. + int num_files() const { return m_files.num_files(); } + + // This function will map a piece index, a byte offset within that piece + // and a size (in bytes) into the corresponding files with offsets where + // that data for that piece is supposed to be stored. See file_slice. + std::vector map_block(piece_index_t const piece + , std::int64_t offset, int size) const + { + TORRENT_ASSERT(is_loaded()); + return m_files.map_block(piece, offset, size); + } + + // This function will map a range in a specific file into a range in the + // torrent. The ``file_offset`` parameter is the offset in the file, + // given in bytes, where 0 is the start of the file. See peer_request. + // + // The input range is assumed to be valid within the torrent. + // ``file_offset`` + ``size`` is not allowed to be greater than the file + // size. ``file_index`` must refer to a valid file, i.e. it cannot be >= + // ``num_files()``. + peer_request map_file(file_index_t const file, std::int64_t offset, int size) const + { + TORRENT_ASSERT(is_loaded()); + return m_files.map_file(file, offset, size); + } + +#if TORRENT_ABI_VERSION == 1 +// ------- start deprecation ------- + // deprecated in 1.2 + void load(char const*, int, error_code&) {} + void unload() {} + + TORRENT_DEPRECATED + explicit torrent_info(entry const& torrent_file); +// ------- end deprecation ------- +#endif + + // Returns the SSL root certificate for the torrent, if it is an SSL + // torrent. Otherwise returns an empty string. The certificate is + // the public certificate in x509 format. + string_view ssl_cert() const; + + // returns true if this torrent_info object has a torrent loaded. + // This is primarily used to determine if a magnet link has had its + // metadata resolved yet or not. + bool is_valid() const { return m_files.is_valid(); } + + // returns true if this torrent is private. i.e., the client should not + // advertise itself on the trackerless network (the Kademlia DHT) for this torrent. + bool priv() const { return bool(m_flags & private_torrent); } + + // returns true if this is an i2p torrent. This is determined by whether + // or not it has a tracker whose URL domain name ends with ".i2p". i2p + // torrents disable the DHT and local peer discovery as well as talking + // to peers over anything other than the i2p network. + bool is_i2p() const { return bool(m_flags & i2p); } + + // internal + bool v2_piece_hashes_verified() const { return bool(m_flags & v2_has_piece_hashes); } + void set_piece_layers(aux::vector, file_index_t> pl); + + // returns the piece size of file with ``index``. This will be the same as piece_length(), + // except for the last piece, which may be shorter. + int piece_size(piece_index_t index) const { return m_files.piece_size(index); } + + // ``hash_for_piece()`` takes a piece-index and returns the 20-bytes + // sha1-hash for that piece and ``info_hash()`` returns the 20-bytes + // sha1-hash for the info-section of the torrent file. + // ``hash_for_piece_ptr()`` returns a pointer to the 20 byte sha1 digest + // for the piece. Note that the string is not 0-terminated. + sha1_hash hash_for_piece(piece_index_t index) const; + char const* hash_for_piece_ptr(piece_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= piece_index_t(0)); + TORRENT_ASSERT_PRECOND(index < m_files.end_piece()); + TORRENT_ASSERT(is_loaded()); + int const idx = static_cast(index); + TORRENT_ASSERT(m_piece_hashes > 0); + TORRENT_ASSERT(m_piece_hashes < m_info_section_size); + TORRENT_ASSERT(idx < int((m_info_section_size - m_piece_hashes) / 20)); + return &m_info_section[std::ptrdiff_t(m_piece_hashes) + idx * 20]; + } + + bool is_loaded() const { return m_files.num_files() > 0; } + +#if TORRENT_ABI_VERSION <= 2 + // support for BEP 30 merkle torrents has been removed + + // ``merkle_tree()`` returns a reference to the merkle tree for this + // torrent, if any. + // ``set_merkle_tree()`` moves the passed in merkle tree into the + // torrent_info object. i.e. ``h`` will not be identical after the call. + // You need to set the merkle tree for a torrent that you've just created + // (as a merkle torrent). The merkle tree is retrieved from the + // ``create_torrent::merkle_tree()`` function, and need to be saved + // separately from the torrent file itself. Once it's added to + // libtorrent, the merkle tree will be persisted in the resume data. + TORRENT_DEPRECATED + std::vector const& merkle_tree() const { return m_merkle_tree; } + TORRENT_DEPRECATED + void set_merkle_tree(std::vector& h) + { TORRENT_ASSERT(h.size() == m_merkle_tree.size() ); m_merkle_tree.swap(h); } +#endif + + // ``name()`` returns the name of the torrent. + // name contains UTF-8 encoded string. + const std::string& name() const { return m_files.name(); } + + // ``creation_date()`` returns the creation date of the torrent as time_t + // (`posix time`_). If there's no time stamp in the torrent file, 0 is + // returned. + // .. _`posix time`: http://www.opengroup.org/onlinepubs/009695399/functions/time.html + std::time_t creation_date() const + { return m_creation_date; } + + // ``creator()`` returns the creator string in the torrent. If there is + // no creator string it will return an empty string. + const std::string& creator() const + { return m_created_by; } + + // ``comment()`` returns the comment associated with the torrent. If + // there's no comment, it will return an empty string. + // comment contains UTF-8 encoded string. + const std::string& comment() const + { return m_comment; } + + // If this torrent contains any DHT nodes, they are put in this vector in + // their original form (host name and port number). + std::vector> const& nodes() const + { return m_nodes; } + + // This is used when creating torrent. Use this to add a known DHT node. + // It may be used, by the client, to bootstrap into the DHT network. + void add_node(std::pair const& node) + { m_nodes.push_back(node); } + + // populates the torrent_info by providing just the info-dict buffer. + // This is used when loading a torrent from a magnet link for instance, + // where we only have the info-dict. The bdecode_node ``e`` points to a + // parsed info-dictionary. ``ec`` returns an error code if something + // fails (typically if the info dictionary is malformed). + // The `max_pieces` parameter allows limiting the amount of memory + // dedicated to loading the torrent, and fails for torrents that exceed + // the limit. To load large torrents, this limit may also need to be + // raised in settings_pack::max_piece_count and in calls to + // read_resume_data(). + bool parse_info_section(bdecode_node const& info, error_code& ec, int max_pieces); + +#if TORRENT_ABI_VERSION < 3 + TORRENT_DEPRECATED + bool parse_info_section(bdecode_node const& info, error_code& ec); +#endif + + // This function looks up keys from the info-dictionary of the loaded + // torrent file. It can be used to access extension values put in the + // .torrent file. If the specified key cannot be found, it returns nullptr. + bdecode_node info(char const* key) const; + + // returns a the raw info section of the torrent file. + // The underlying buffer is still owned by the torrent_info object + span info_section() const + { return span(m_info_section.get(), m_info_section_size); } + +#if TORRENT_ABI_VERSION <= 2 + // swap the content of this and ``ti``. + TORRENT_DEPRECATED + void swap(torrent_info& ti); + + // ``metadata()`` returns a the raw info section of the torrent file. The size + // of the metadata is returned by ``metadata_size()``. + // Even though the bytes returned by ``metadata()`` are not ``const``, + // they must not be modified. + TORRENT_DEPRECATED + int metadata_size() const { return m_info_section_size; } + TORRENT_DEPRECATED + boost::shared_array metadata() const; +#endif + + // return the bytes of the piece layer hashes for the specified file. If + // the file doesn't have a piece layer, an empty span is returned. + // The span size is divisible by 32, the size of a SHA-256 hash. + // If the size of the file is smaller than or equal to the piece size, + // the files "root hash" is the hash of the file and is not saved + // separately in the "piece layers" field, but this function still + // returns the root hash of the file in that case. + span piece_layer(file_index_t) const; + + // clears the piece layers from the torrent_info. This is done by the + // session when a torrent is added, to avoid storing it twice. The piece + // layer (or other hashes part of the merkle tree) are stored in the + // internal torrent object. + void free_piece_layers(); + + // internal + void internal_set_creator(string_view); + void internal_set_creation_date(std::time_t); + void internal_set_comment(string_view); + +#if TORRENT_ABI_VERSION <= 2 + // support for BEP 30 merkle torrents has been removed + + // internal + TORRENT_DEPRECATED + bool add_merkle_nodes(std::map const& + , piece_index_t) { return false; } + TORRENT_DEPRECATED + std::map build_merkle_list(piece_index_t) const + { + return std::map(); + } + + // returns whether or not this is a merkle torrent. + // see `BEP 30`__. + // + // __ https://www.bittorrent.org/beps/bep_0030.html + TORRENT_DEPRECATED + bool is_merkle_torrent() const { return !m_merkle_tree.empty(); } +#endif + + private: + + // populate the piece layers from the metadata + bool parse_piece_layers(bdecode_node const& e, error_code& ec); + + bool parse_torrent_file(bdecode_node const& torrent_file, error_code& ec, int piece_limit); + + void resolve_duplicate_filenames(); + + // the slow path, in case we detect/suspect a name collision + void resolve_duplicate_filenames_slow(); + +#if TORRENT_USE_INVARIANT_CHECKS + friend struct ::lt::invariant_access; + void check_invariant() const; +#endif + + void copy_on_write(); + + file_storage m_files; + + // if m_files is modified, it is first copied into + // m_orig_files so that the original name and + // filenames are preserved. + // the original filenames are required to build URLs for web seeds for + // instance + copy_ptr m_orig_files; + + // the URLs to the trackers + aux::vector m_urls; + std::vector m_web_seeds; + // dht nodes to add to the routing table/bootstrap from + std::vector> m_nodes; + + // the info-hashes (20 bytes each) in the "similar" key. These are offsets + // into the info dict buffer. + std::vector m_similar_torrents; + + // these are similar torrents from outside of the info-dict. We can't + // have non-owning pointers to those, as we only keep the info-dict + // around. + std::vector m_owned_similar_torrents; + + // these or strings of the "collections" key from the torrent file. The + // first value is the offset into the metadata where the string is, the + // second value is the length of the string. Strings are not 0-terminated. + std::vector> m_collections; + + // these are the collections from outside of the info-dict. These are + // owning strings, since we only keep the info-section around, these + // cannot be pointers into that buffer. + std::vector m_owned_collections; + +#if TORRENT_ABI_VERSION <= 2 + // if this is a merkle torrent, this is the merkle + // tree. It has space for merkle_num_nodes(merkle_num_leafs(num_pieces)) + // hashes + aux::vector m_merkle_tree; +#endif + + // v2 merkle tree for each file + // the actual hash buffers are always divisible by 32 (sha256_hash::size()) + aux::vector, file_index_t> m_piece_layers; + + // this is a copy of the info section from the torrent. + // it use maintained in this flat format in order to + // make it available through the metadata extension + // TODO: change the type to std::shared_ptr in C++17 + // it is used as if immutable, it cannot be const for technical reasons + // right now. + boost::shared_array m_info_section; + + // if a comment is found in the torrent file + // this will be set to that comment + std::string m_comment; + + // an optional string naming the software used + // to create the torrent file + std::string m_created_by; + + // the info section parsed. points into m_info_section + // parsed lazily + mutable bdecode_node m_info_dict; + + // if a creation date is found in the torrent file + // this will be set to that, otherwise it'll be + // 1970, Jan 1 + std::time_t m_creation_date = 0; + + // the hash(es) that identify this torrent + info_hash_t m_info_hash; + + // this is the offset into the m_info_section buffer to the first byte of + // the first SHA-1 hash + std::int32_t m_piece_hashes = 0; + + // the number of bytes in m_info_section + std::int32_t m_info_section_size = 0; + + // this is used when creating a torrent. If there's + // only one file there are cases where it's impossible + // to know if it should be written as a multi file torrent + // or not. e.g. test/test there's one file and one directory + // and they have the same name. + static constexpr torrent_info_flags_t multifile = 0_bit; + + // this is true if the torrent is private. i.e., is should not + // be announced on the dht + static constexpr torrent_info_flags_t private_torrent = 1_bit; + + // this is true if one of the trackers has an .i2p top + // domain in its hostname. This means the DHT and LSD + // features are disabled for this torrent (unless the + // settings allows mixing i2p peers with regular peers) + static constexpr torrent_info_flags_t i2p = 2_bit; + + // this flag is set if we found an ssl-cert field in the info + // dictionary + static constexpr torrent_info_flags_t ssl_torrent = 3_bit; + + // v2 piece hashes were loaded from the torrent file and verified + static constexpr torrent_info_flags_t v2_has_piece_hashes = 4_bit; + + torrent_info_flags_t m_flags{}; + }; + +TORRENT_VERSION_NAMESPACE_3_END + +} + +#endif // TORRENT_TORRENT_INFO_HPP_INCLUDED diff --git a/include/libtorrent/torrent_peer.hpp b/include/libtorrent/torrent_peer.hpp new file mode 100644 index 0000000..6e2c19a --- /dev/null +++ b/include/libtorrent/torrent_peer.hpp @@ -0,0 +1,312 @@ +/* + +Copyright (c) 2014-2017, 2019, 2021, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2018, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_PEER_HPP_INCLUDED +#define TORRENT_TORRENT_PEER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_info.hpp" // for peer_source_flags_t +#include "libtorrent/info_hash.hpp" +#include "libtorrent/aux_/string_ptr.hpp" +#include "libtorrent/string_view.hpp" + +namespace libtorrent { + + struct peer_connection_interface; + struct external_ip; + + // calculate the priority of a peer based on its address. One of the + // endpoint should be our own. The priority is symmetric, so it doesn't + // matter which is which + TORRENT_EXTRA_EXPORT std::uint32_t peer_priority( + tcp::endpoint e1, tcp::endpoint e2); + + struct TORRENT_EXTRA_EXPORT torrent_peer + { + torrent_peer(std::uint16_t port, bool connectable, peer_source_flags_t src); +#if TORRENT_USE_ASSERTS + torrent_peer(torrent_peer const&) = default; + torrent_peer& operator=(torrent_peer const&) & = default; + ~torrent_peer() { TORRENT_ASSERT(in_use); in_use = false; } +#endif + + std::int64_t total_download() const; + std::int64_t total_upload() const; + + std::uint32_t rank(external_ip const& external, int external_port) const; + + libtorrent::address address() const; + string_view dest() const; + + tcp::endpoint ip() const { return tcp::endpoint(address(), port); } + +#ifndef TORRENT_DISABLE_LOGGING + std::string to_string() const; +#endif + + protocol_version protocol() { return protocol_v2 ? protocol_version::V2 : protocol_version::V1; } + + // this is the accumulated amount of + // uploaded and downloaded data to this + // torrent_peer. It only accounts for what was + // shared during the last connection to + // this torrent_peer. i.e. These are only updated + // when the connection is closed. For the + // total amount of upload and download + // we'll have to add these figures with the + // statistics from the peer_connection. + // since these values don't need to be stored + // with byte-precision, they specify the number + // of kiB. i.e. shift left 10 bits to compare to + // byte counters. + std::uint32_t prev_amount_upload; + std::uint32_t prev_amount_download; + + // if the torrent_peer is connected now, this + // will refer to a valid peer_connection + peer_connection_interface* connection; + + // as computed by hashing our IP with the remote + // IP of this peer + // calculated lazily + mutable std::uint32_t peer_rank; + + // the time when this torrent_peer was optimistically unchoked + // the last time. in seconds since session was created + // 16 bits is enough to last for 18.2 hours + // when the session time reaches 18 hours, it jumps back by + // 9 hours, and all peers' times are updated to be + // relative to that new time offset + std::uint16_t last_optimistically_unchoked; + + // the time when the torrent_peer connected to us + // or disconnected if it isn't connected right now + // in number of seconds since session was created + std::uint16_t last_connected; + + // the port this torrent_peer is or was connected on + std::uint16_t port; + + // the number of times this torrent_peer has been + // part of a piece that failed the hash check + std::uint8_t hashfails; + + // the number of failed connection attempts + // this torrent_peer has + std::uint32_t failcount:5; // [0, 31] + + // incoming peers (that don't advertise their listen port) + // will not be considered connectable. Peers that + // we have a listen port for will be assumed to be. + std::uint32_t connectable:1; + + // true if this torrent_peer currently is unchoked + // because of an optimistic unchoke. + // when the optimistic unchoke is moved to + // another torrent_peer, this torrent_peer will be choked + // if this is true + std::uint32_t optimistically_unchoked:1; + + // this is true if the torrent_peer is a seed, and we know for sure + // because we have connected to it and it told us it was a seed + std::uint32_t seed:1; + + // we've been told that this peer is upload-only, but we don't know for + // sure because we haven't connected to it yet. If we are finished, we + // will de-prioritize peers that may be seeds + std::uint32_t maybe_upload_only:1; + + // the number of times we have allowed a fast + // reconnect for this torrent_peer. + std::uint32_t fast_reconnects:4; + + // for every valid piece we receive where this + // torrent_peer was one of the participants, we increase + // this value. For every invalid piece we receive + // where this torrent_peer was a participant, we decrease + // this value. If it sinks below a threshold, its + // considered a bad torrent_peer and will be banned. + signed trust_points:4; // [-7, 8] + + // a bitmap combining the peer_source flags + // from peer_info. + std::uint32_t source:6; + + peer_source_flags_t peer_source() const + { return peer_source_flags_t(source); } + +#if !defined TORRENT_DISABLE_ENCRYPTION + // Hints encryption support of torrent_peer. Only effective + // for and when the outgoing encryption policy + // allows both encrypted and non encrypted + // connections (pe_settings::out_enc_policy + // == enabled). The initial state of this flag + // determines the initial connection attempt + // type (true = encrypted, false = standard). + // This will be toggled everytime either an + // encrypted or non-encrypted handshake fails. + bool pe_support:1; +#endif + + // this is true if the v6 union member in addr is + // the one to use, false if it's the v4 one + bool is_v6_addr:1; +#if TORRENT_USE_I2P + // set if the i2p_destination is in use in the addr union + bool is_i2p_addr:1; +#endif + + // if this is true, the torrent_peer has previously + // participated in a piece that failed the piece + // hash check. This will put the torrent_peer on parole + // and only request entire pieces. If a piece pass + // that was partially requested from this torrent_peer it + // will leave parole mode and continue download + // pieces as normal peers. + bool on_parole:1; + + // is set to true if this torrent_peer has been banned + bool banned:1; + + // we think this torrent_peer supports uTP + bool supports_utp:1; + // we have been connected via uTP at least once + bool confirmed_supports_utp:1; + bool supports_holepunch:1; + // this is set to one for web seeds. Web seeds + // are not stored in the policy m_peers list, + // and are exempt from connect candidate bookkeeping + // so, any torrent_peer with the web_seed bit set, is + // never considered a connect candidate + bool web_seed:1; + // this peer supports protocol version 2 + bool protocol_v2:1; +#if TORRENT_USE_ASSERTS + bool in_use = true; +#endif + }; + + struct TORRENT_EXTRA_EXPORT ipv4_peer : torrent_peer + { + ipv4_peer(tcp::endpoint const& ep, bool connectable, peer_source_flags_t src); + ipv4_peer(ipv4_peer const& p); + ipv4_peer& operator=(ipv4_peer const& p) &; + + address_v4 addr; + }; + +#if TORRENT_USE_I2P + struct TORRENT_EXTRA_EXPORT i2p_peer : torrent_peer + { + i2p_peer(string_view dest, bool connectable, peer_source_flags_t src); + i2p_peer(i2p_peer const&) = delete; + i2p_peer& operator=(i2p_peer const&) = delete; + i2p_peer(i2p_peer&&) = default; + i2p_peer& operator=(i2p_peer&&) & = default; + + aux::string_ptr destination; + }; +#endif + + struct TORRENT_EXTRA_EXPORT ipv6_peer : torrent_peer + { + ipv6_peer(tcp::endpoint const& ep, bool connectable, peer_source_flags_t src); + ipv6_peer(ipv6_peer const& p); + + const address_v6::bytes_type addr; + }; + + // if we have i2p peer addresses, sort them *after* all IP addresses + struct peer_address_compare + { + bool operator()(torrent_peer const* lhs, address const& rhs) const + { +#if TORRENT_USE_I2P + if (lhs->is_i2p_addr) return false; +#endif + return lhs->address() < rhs; + } + + bool operator()(address const& lhs, torrent_peer const* rhs) const + { +#if TORRENT_USE_I2P + if (rhs->is_i2p_addr) return true; +#endif + return lhs < rhs->address(); + } + +#if TORRENT_USE_I2P + bool operator()(torrent_peer const* lhs, string_view rhs) const + { + if (!lhs->is_i2p_addr) return true; + return lhs->dest() < rhs; + } + + bool operator()(string_view lhs, torrent_peer const* rhs) const + { + if (!rhs->is_i2p_addr) return false; + return lhs < rhs->dest(); + } +#endif + + bool operator()(torrent_peer const* lhs, torrent_peer const* rhs) const + { +#if TORRENT_USE_I2P + if (lhs->is_i2p_addr != rhs->is_i2p_addr) + return lhs->is_i2p_addr < rhs->is_i2p_addr; + + if (lhs->is_i2p_addr) + return lhs->dest() < rhs->dest(); +#endif + return lhs->address() < rhs->address(); + } + }; + + inline bool torrent_peer_equal(torrent_peer const* lhs, torrent_peer const* rhs) + { +#if TORRENT_USE_I2P + if (lhs->is_i2p_addr != rhs->is_i2p_addr) + return false; + + if (lhs->is_i2p_addr) + return lhs->dest() == rhs->dest(); +#endif + return lhs->address() == rhs->address() + && lhs->port == rhs->port; + } +} + +#endif diff --git a/include/libtorrent/torrent_peer_allocator.hpp b/include/libtorrent/torrent_peer_allocator.hpp new file mode 100644 index 0000000..b48a2a2 --- /dev/null +++ b/include/libtorrent/torrent_peer_allocator.hpp @@ -0,0 +1,102 @@ +/* + +Copyright (c) 2010, 2013-2017, 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_ALLOCATOR_HPP_INCLUDED +#define TORRENT_PEER_ALLOCATOR_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/aux_/pool.hpp" + +namespace libtorrent { + + struct TORRENT_EXTRA_EXPORT torrent_peer_allocator_interface + { + enum peer_type_t + { + ipv4_peer_type, + ipv6_peer_type, + i2p_peer_type + }; + + virtual torrent_peer* allocate_peer_entry(int type) = 0; + virtual void free_peer_entry(torrent_peer* p) = 0; + protected: + ~torrent_peer_allocator_interface() {} + }; + + struct TORRENT_EXTRA_EXPORT torrent_peer_allocator final + : torrent_peer_allocator_interface + { +#if TORRENT_USE_ASSERTS + ~torrent_peer_allocator() { + m_in_use = false; + } +#endif + + torrent_peer* allocate_peer_entry(int type) override; + void free_peer_entry(torrent_peer* p) override; + + std::uint64_t total_bytes() const { return m_total_bytes; } + std::uint64_t total_allocations() const { return m_total_allocations; } + int live_bytes() const { return m_live_bytes; } + int live_allocations() const { return m_live_allocations; } + + private: + + // this is a shared pool where torrent_peer objects + // are allocated. It's a pool since we're likely + // to have tens of thousands of peers, and a pool + // saves significant overhead + + aux::pool m_ipv4_peer_pool{sizeof(libtorrent::ipv4_peer), 500}; + aux::pool m_ipv6_peer_pool{sizeof(libtorrent::ipv6_peer), 500}; +#if TORRENT_USE_I2P + aux::pool m_i2p_peer_pool{sizeof(libtorrent::i2p_peer), 500}; +#endif + + // the total number of bytes allocated (cumulative) + std::uint64_t m_total_bytes = 0; + // the total number of allocations (cumulative) + std::uint64_t m_total_allocations = 0; + // the number of currently live bytes + int m_live_bytes = 0; + // the number of currently live allocations + int m_live_allocations = 0; +#if TORRENT_USE_ASSERTS + bool m_in_use = true; +#endif + }; +} + +#endif + diff --git a/include/libtorrent/torrent_status.hpp b/include/libtorrent/torrent_status.hpp new file mode 100644 index 0000000..de95b24 --- /dev/null +++ b/include/libtorrent/torrent_status.hpp @@ -0,0 +1,610 @@ +/* + +Copyright (c) 2015-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +Copyright (c) 2017, AllSeeingEyeTolledEweSew +Copyright (c) 2018, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_STATUS_HPP_INCLUDED +#define TORRENT_TORRENT_STATUS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/time.hpp" // for time_duration +#include "libtorrent/storage_defs.hpp" // for storage_mode_t +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" + +#include +#include +#include + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_deprecation_warnings_push.hpp" +#endif + +TORRENT_VERSION_NAMESPACE_3 + + // holds a snapshot of the status of a torrent, as queried by + // torrent_handle::status(). + struct TORRENT_EXPORT torrent_status + { +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + // hidden + torrent_status() noexcept; + ~torrent_status(); + torrent_status(torrent_status const&); + torrent_status& operator=(torrent_status const&); + torrent_status(torrent_status&&) noexcept; + torrent_status& operator=(torrent_status&&); + + // compares if the torrent status objects come from the same torrent. i.e. + // only the torrent_handle field is compared. + bool operator==(torrent_status const& st) const + { return handle == st.handle; } + + // a handle to the torrent whose status the object represents. + torrent_handle handle; + + // the different overall states a torrent can be in + enum state_t + { +#if TORRENT_ABI_VERSION == 1 + // The torrent is in the queue for being checked. But there + // currently is another torrent that are being checked. + // This torrent will wait for its turn. + queued_for_checking TORRENT_DEPRECATED_ENUM, +#else + // internal + unused_enum_for_backwards_compatibility, +#endif + + // The torrent has not started its download yet, and is + // currently checking existing files. + checking_files, + + // The torrent is trying to download metadata from peers. + // This implies the ut_metadata extension is in use. + downloading_metadata, + + // The torrent is being downloaded. This is the state + // most torrents will be in most of the time. The progress + // meter will tell how much of the files that has been + // downloaded. + downloading, + + // In this state the torrent has finished downloading but + // still doesn't have the entire torrent. i.e. some pieces + // are filtered and won't get downloaded. + finished, + + // In this state the torrent has finished downloading and + // is a pure seeder. + seeding, + + // If the torrent was started in full allocation mode, this + // indicates that the (disk) storage for the torrent is + // allocated. +#if TORRENT_ABI_VERSION == 1 + allocating TORRENT_DEPRECATED_ENUM, +#else + unused_enum_for_backwards_compatibility_allocating, +#endif + + // The torrent is currently checking the fast resume data and + // comparing it to the files on disk. This is typically + // completed in a fraction of a second, but if you add a + // large number of torrents at once, they will queue up. + checking_resume_data + }; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED std::string error; +#endif + + // may be set to an error code describing why the torrent was paused, in + // case it was paused by an error. If the torrent is not paused or if it's + // paused but not because of an error, this error_code is not set. + // if the error is attributed specifically to a file, error_file is set to + // the index of that file in the .torrent file. + error_code errc; + + // if the torrent is stopped because of an disk I/O error, this field + // contains the index of the file in the torrent that encountered the + // error. If the error did not originate in a file in the torrent, there + // are a few special values this can be set to: error_file_none, + // error_file_ssl_ctx, error_file_exception, error_file_partfile or + // error_file_metadata; + file_index_t error_file = torrent_status::error_file_none; + + // special values for error_file to describe which file or component + // encountered the error (``errc``). + // the error did not occur on a file + static constexpr file_index_t error_file_none{-1}; + + // the error occurred setting up the SSL context + static constexpr file_index_t error_file_ssl_ctx{-3}; + + // the error occurred while loading the metadata for the torrent + static constexpr file_index_t error_file_metadata{-4}; + + // there was a serious error reported in this torrent. The error code + // or a torrent log alert may provide more information. + static constexpr file_index_t error_file_exception{-5}; + + // the error occurred with the partfile + static constexpr file_index_t error_file_partfile{-6}; + + // the path to the directory where this torrent's files are stored. + // It's typically the path as was given to async_add_torrent() or + // add_torrent() when this torrent was started. This field is only + // included if the torrent status is queried with + // ``torrent_handle::query_save_path``. + std::string save_path; + + // the name of the torrent. Typically this is derived from the + // .torrent file. In case the torrent was started without metadata, + // and hasn't completely received it yet, it returns the name given + // to it when added to the session. See ``session::add_torrent``. + // This field is only included if the torrent status is queried + // with ``torrent_handle::query_name``. + std::string name; + + // set to point to the ``torrent_info`` object for this torrent. It's + // only included if the torrent status is queried with + // ``torrent_handle::query_torrent_file``. + std::weak_ptr torrent_file; + + // the time until the torrent will announce itself to the tracker. + time_duration next_announce = seconds{0}; + +#if TORRENT_ABI_VERSION == 1 + // the time the tracker want us to wait until we announce ourself + // again the next time. + TORRENT_DEPRECATED time_duration announce_interval; +#endif + + // the URL of the last working tracker. If no tracker request has + // been successful yet, it's set to an empty string. + std::string current_tracker; + + // the number of bytes downloaded and uploaded to all peers, accumulated, + // *this session* only. The session is considered to restart when a + // torrent is paused and restarted again. When a torrent is paused, these + // counters are reset to 0. If you want complete, persistent, stats, see + // ``all_time_upload`` and ``all_time_download``. + std::int64_t total_download = 0; + std::int64_t total_upload = 0; + + // counts the amount of bytes send and received this session, but only + // the actual payload data (i.e the interesting data), these counters + // ignore any protocol overhead. The session is considered to restart + // when a torrent is paused and restarted again. When a torrent is + // paused, these counters are reset to 0. + std::int64_t total_payload_download = 0; + std::int64_t total_payload_upload = 0; + + // the number of bytes that has been downloaded and that has failed the + // piece hash test. In other words, this is just how much crap that has + // been downloaded since the torrent was last started. If a torrent is + // paused and then restarted again, this counter will be reset. + std::int64_t total_failed_bytes = 0; + + // the number of bytes that has been downloaded even though that data + // already was downloaded. The reason for this is that in some situations + // the same data can be downloaded by mistake. When libtorrent sends + // requests to a peer, and the peer doesn't send a response within a + // certain timeout, libtorrent will re-request that block. Another + // situation when libtorrent may re-request blocks is when the requests + // it sends out are not replied in FIFO-order (it will re-request blocks + // that are skipped by an out of order block). This is supposed to be as + // low as possible. This only counts bytes since the torrent was last + // started. If a torrent is paused and then restarted again, this counter + // will be reset. + std::int64_t total_redundant_bytes = 0; + + // a bitmask that represents which pieces we have (set to true) and the + // pieces we don't have. It's a pointer and may be set to 0 if the + // torrent isn't downloading or seeding. + typed_bitfield pieces; + + // a bitmask representing which pieces has had their hash checked. This + // only applies to torrents in *seed mode*. If the torrent is not in seed + // mode, this bitmask may be empty. + typed_bitfield verified_pieces; + + // the total number of bytes of the file(s) that we have. All this does + // not necessarily has to be downloaded during this session (that's + // ``total_payload_download``). + std::int64_t total_done = 0; + + // the total number of bytes to download for this torrent. This + // may be less than the size of the torrent in case there are + // pad files. This number only counts bytes that will actually + // be requested from peers. + std::int64_t total = 0; + + // the number of bytes we have downloaded, only counting the pieces that + // we actually want to download. i.e. excluding any pieces that we have + // but have priority 0 (i.e. not wanted). + // Once a torrent becomes seed, any piece- and file priorities are + // forgotten and all bytes are considered "wanted". + std::int64_t total_wanted_done = 0; + + // The total number of bytes we want to download. This may be smaller + // than the total torrent size in case any pieces are prioritized to 0, + // i.e. not wanted. + // Once a torrent becomes seed, any piece- and file priorities are + // forgotten and all bytes are considered "wanted". + std::int64_t total_wanted = 0; + + // are accumulated upload and download payload byte counters. They are + // saved in and restored from resume data to keep totals across sessions. + std::int64_t all_time_upload = 0; + std::int64_t all_time_download = 0; + + // the posix-time when this torrent was added. i.e. what ``time(nullptr)`` + // returned at the time. + std::time_t added_time = 0; + + // the posix-time when this torrent was finished. If the torrent is not + // yet finished, this is 0. + std::time_t completed_time = 0; + + // the time when we, or one of our peers, last saw a complete copy of + // this torrent. + std::time_t last_seen_complete = 0; + + // The allocation mode for the torrent. See storage_mode_t for the + // options. For more information, see storage-allocation_. + storage_mode_t storage_mode = storage_mode_sparse; + + // a value in the range [0, 1], that represents the progress of the + // torrent's current task. It may be checking files or downloading. + float progress = 0.f; + + // progress parts per million (progress * 1000000) when disabling + // floating point operations, this is the only option to query progress + // + // reflects the same value as ``progress``, but instead in a range [0, + // 1000000] (ppm = parts per million). When floating point operations are + // disabled, this is the only alternative to the floating point value in + // progress. + int progress_ppm = 0; + + // the position this torrent has in the download + // queue. If the torrent is a seed or finished, this is -1. + queue_position_t queue_position{}; + + // the total rates for all peers for this torrent. These will usually + // have better precision than summing the rates from all peers. The rates + // are given as the number of bytes per second. + int download_rate = 0; + int upload_rate = 0; + + // the total transfer rate of payload only, not counting protocol + // chatter. This might be slightly smaller than the other rates, but if + // projected over a long time (e.g. when calculating ETA:s) the + // difference may be noticeable. + int download_payload_rate = 0; + int upload_payload_rate = 0; + + // the number of peers that are seeding that this client is + // currently connected to. + int num_seeds = 0; + + // the number of peers this torrent currently is connected to. Peer + // connections that are in the half-open state (is attempting to connect) + // or are queued for later connection attempt do not count. Although they + // are visible in the peer list when you call get_peer_info(). + int num_peers = 0; + + // if the tracker sends scrape info in its announce reply, these fields + // will be set to the total number of peers that have the whole file and + // the total number of peers that are still downloading. set to -1 if the + // tracker did not send any scrape data in its announce reply. + int num_complete = -1; + int num_incomplete = -1; + + // the number of seeds in our peer list and the total number of peers + // (including seeds). We are not necessarily connected to all the peers + // in our peer list. This is the number of peers we know of in total, + // including banned peers and peers that we have failed to connect to. + int list_seeds = 0; + int list_peers = 0; + + // the number of peers in this torrent's peer list that is a candidate to + // be connected to. i.e. It has fewer connect attempts than the max fail + // count, it is not a seed if we are a seed, it is not banned etc. If + // this is 0, it means we don't know of any more peers that we can try. + int connect_candidates = 0; + + // the number of pieces that has been downloaded. It is equivalent to: + // ``std::accumulate(pieces->begin(), pieces->end())``. So you don't have + // to count yourself. This can be used to see if anything has updated + // since last time if you want to keep a graph of the pieces up to date. + // Note that these pieces have not necessarily been written to disk yet, + // and there is a risk the write to disk will fail. + int num_pieces = 0; + + // the number of distributed copies of the torrent. Note that one copy + // may be spread out among many peers. It tells how many copies there are + // currently of the rarest piece(s) among the peers this client is + // connected to. + int distributed_full_copies = 0; + + // tells the share of pieces that have more copies than the rarest + // piece(s). Divide this number by 1000 to get the fraction. + // + // For example, if ``distributed_full_copies`` is 2 and + // ``distributed_fraction`` is 500, it means that the rarest pieces have + // only 2 copies among the peers this torrent is connected to, and that + // 50% of all the pieces have more than two copies. + // + // If we are a seed, the piece picker is deallocated as an optimization, + // and piece availability is no longer tracked. In this case the + // distributed copies members are set to -1. + int distributed_fraction = 0; + + // the number of distributed copies of the file. note that one copy may + // be spread out among many peers. This is a floating point + // representation of the distributed copies. + // + // the integer part tells how many copies + // there are of the rarest piece(s) + // + // the fractional part tells the fraction of pieces that + // have more copies than the rarest piece(s). + float distributed_copies = 0.f; + + // the size of a block, in bytes. A block is a sub piece, it is the + // number of bytes that each piece request asks for and the number of + // bytes that each bit in the ``partial_piece_info``'s bitset represents, + // see get_download_queue(). This is typically 16 kB, but it may be + // smaller, if the pieces are smaller. + int block_size = 0; + + // the number of unchoked peers in this torrent. + int num_uploads = 0; + + // the number of peer connections this torrent has, including half-open + // connections that hasn't completed the bittorrent handshake yet. This + // is always >= ``num_peers``. + int num_connections = 0; + + // the set limit of upload slots (unchoked peers) for this torrent. + int uploads_limit = 0; + + // the set limit of number of connections for this torrent. + int connections_limit = 0; + + // the number of peers in this torrent that are waiting for more + // bandwidth quota from the torrent rate limiter. This can determine if + // the rate you get from this torrent is bound by the torrents limit or + // not. If there is no limit set on this torrent, the peers might still + // be waiting for bandwidth quota from the global limiter, but then they + // are counted in the ``session_status`` object. + int up_bandwidth_queue = 0; + int down_bandwidth_queue = 0; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + // use last_upload, last_download or + // seeding_duration, finished_duration and active_duration + // instead + + // the number of seconds since any peer last uploaded from this torrent + // and the last time a downloaded piece passed the hash check, + // respectively. Note, when starting up a torrent that needs its files + // checked, piece may pass and that will be considered downloading for the + // purpose of this counter. -1 means there either hasn't been any + // uploading/downloading, or it was too long ago for libtorrent to + // remember (currently forgetting happens after about 18 hours) + TORRENT_DEPRECATED int time_since_upload = 0; + TORRENT_DEPRECATED int time_since_download = 0; + + // These keep track of the number of seconds this torrent has been active + // (not paused) and the number of seconds it has been active while being + // finished and active while being a seed. ``seeding_time`` should be <= + // ``finished_time`` which should be <= ``active_time``. They are all + // saved in and restored from resume data, to keep totals across + // sessions. + TORRENT_DEPRECATED int active_time = 0; + TORRENT_DEPRECATED int finished_time = 0; + TORRENT_DEPRECATED int seeding_time = 0; +#endif + + // A rank of how important it is to seed the torrent, it is used to + // determine which torrents to seed and which to queue. It is based on + // the peer to seed ratio from the tracker scrape. For more information, + // see queuing_. Higher value means more important to seed + int seed_rank = 0; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + + // the number of seconds since this torrent acquired scrape data. + // If it has never done that, this value is -1. + TORRENT_DEPRECATED int last_scrape = 0; + + // the priority of this torrent + TORRENT_DEPRECATED int priority = 0; +#endif + + // the main state the torrent is in. See torrent_status::state_t. + state_t state = checking_resume_data; + + // true if this torrent has unsaved changes + // to its download state and statistics since the last resume data + // was saved. + bool need_save_resume = false; + +#if TORRENT_ABI_VERSION == 1 + // true if the session global IP filter applies + // to this torrent. This defaults to true. + TORRENT_DEPRECATED bool ip_filter_applies = false; + + // true if the torrent is blocked from downloading. This typically + // happens when a disk write operation fails. If the torrent is + // auto-managed, it will periodically be taken out of this state, in the + // hope that the disk condition (be it disk full or permission errors) + // has been resolved. If the torrent is not auto-managed, you have to + // explicitly take it out of the upload mode by calling set_upload_mode() + // on the torrent_handle. + TORRENT_DEPRECATED bool upload_mode = false; + + // true if the torrent is currently in share-mode, i.e. not downloading + // the torrent, but just helping the swarm out. + TORRENT_DEPRECATED bool share_mode = false; + + // true if the torrent is in super seeding mode + TORRENT_DEPRECATED bool super_seeding = false; + + // set to true if the torrent is paused and false otherwise. It's only + // true if the torrent itself is paused. If the torrent is not running + // because the session is paused, this is still false. To know if a + // torrent is active or not, you need to inspect both + // ``torrent_status::paused`` and ``session::is_paused()``. + TORRENT_DEPRECATED bool paused = false; + + // set to true if the torrent is auto managed, i.e. libtorrent is + // responsible for determining whether it should be started or queued. + // For more info see queuing_ + TORRENT_DEPRECATED bool auto_managed = false; + + // true when the torrent is in sequential download mode. In this mode + // pieces are downloaded in order rather than rarest first. + TORRENT_DEPRECATED bool sequential_download = false; +#endif + + // true if all pieces have been downloaded. + bool is_seeding = false; + + // true if all pieces that have a priority > 0 are downloaded. There is + // only a distinction between finished and seeding if some pieces or + // files have been set to priority 0, i.e. are not downloaded. + bool is_finished = false; + + // true if this torrent has metadata (either it was started from a + // .torrent file or the metadata has been downloaded). The only scenario + // where this can be false is when the torrent was started torrent-less + // (i.e. with just an info-hash and tracker ip, a magnet link for + // instance). + bool has_metadata = false; + + // true if there has ever been an incoming connection attempt to this + // torrent. + bool has_incoming = false; + +#if TORRENT_ABI_VERSION == 1 + // true if the torrent is in seed_mode. If the torrent was started in + // seed mode, it will leave seed mode once all pieces have been checked + // or as soon as one piece fails the hash check. + TORRENT_DEPRECATED bool seed_mode = false; +#endif + + // this is true if this torrent's storage is currently being moved from + // one location to another. This may potentially be a long operation + // if a large file ends up being copied from one drive to another. + bool moving_storage = false; + +#if TORRENT_ABI_VERSION == 1 + // true if this torrent is loaded into RAM. A torrent can be started + // and still not loaded into RAM, in case it has not had any peers interested in it + // yet. Torrents are loaded on demand. + TORRENT_DEPRECATED bool is_loaded = false; +#endif + + // these are set to true if this torrent is allowed to announce to the + // respective peer source. Whether they are true or false is determined by + // the queue logic/auto manager. Torrents that are not auto managed will + // always be allowed to announce to all peer sources. + bool announcing_to_trackers = false; + bool announcing_to_lsd = false; + bool announcing_to_dht = false; + +#if TORRENT_ABI_VERSION == 1 + // this reflects whether the ``stop_when_ready`` flag is currently enabled + // on this torrent. For more information, see + // torrent_handle::stop_when_ready(). + TORRENT_DEPRECATED bool stop_when_ready = false; +#endif + +#if TORRENT_ABI_VERSION < 3 + TORRENT_DEPRECATED sha1_hash info_hash; +#endif + + // the info-hash for this torrent + info_hash_t info_hashes; + + // the timestamps of the last time this torrent uploaded or downloaded + // payload to any peer. + time_point last_upload; + time_point last_download; + + // these are cumulative counters of for how long the torrent has been in + // different states. active means not paused and added to session. Whether + // it has found any peers or not is not relevant. + // finished means all selected files/pieces were downloaded and available + // to other peers (this is always a subset of active time). + // seeding means all files/pieces were downloaded and available to + // peers. Being available to peers does not imply there are other peers + // asking for the payload. + seconds active_duration; + seconds finished_duration; + seconds seeding_duration; + + // reflects several of the torrent's flags. For more + // information, see ``torrent_handle::flags()``. + torrent_flags_t flags{}; + }; + +TORRENT_VERSION_NAMESPACE_3_END +} // namespace libtorrent + +namespace std { + template <> + struct hash + { + std::size_t operator()(libtorrent::torrent_status const& ts) const + { + return libtorrent::hash_value(ts.handle); + } + }; +} + +#endif // TORRENT_TORRENT_STATUS_HPP_INCLUDED diff --git a/include/libtorrent/tracker_manager.hpp b/include/libtorrent/tracker_manager.hpp new file mode 100644 index 0000000..4f9857a --- /dev/null +++ b/include/libtorrent/tracker_manager.hpp @@ -0,0 +1,413 @@ +/* + +Copyright (c) 2015, Mikhail Titov +Copyright (c) 2004-2021, Arvid Norberg +Copyright (c) 2016, 2020, Alden Torres +Copyright (c) 2016-2017, Steven Siloti +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TRACKER_MANAGER_HPP_INCLUDED +#define TORRENT_TRACKER_MANAGER_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/flags.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/peer.hpp" // peer_entry +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/union_endpoint.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/listen_socket_handle.hpp" +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/ssl.hpp" +#include "libtorrent/string_view.hpp" + +namespace libtorrent { + + class tracker_manager; + struct timeout_handler; + class udp_tracker_connection; + class http_tracker_connection; + struct counters; +#if TORRENT_USE_I2P + class i2p_connection; +#endif +namespace aux { + struct session_logger; + struct session_settings; + struct resolver_interface; +} + +using tracker_request_flags_t = flags::bitfield_flag; + +// Kinds of tracker announces. This is typically indicated as the ``&event=`` +// HTTP query string parameter to HTTP trackers. +enum class event_t : std::uint8_t +{ + none, + completed, + started, + stopped, + paused +}; + + struct TORRENT_EXTRA_EXPORT tracker_request + { + tracker_request() = default; + + static constexpr tracker_request_flags_t scrape_request = 0_bit; + + // affects interpretation of peers string in HTTP response + // see parse_tracker_response() + static constexpr tracker_request_flags_t i2p = 1_bit; + + std::string url; + std::string trackerid; +#if TORRENT_ABI_VERSION == 1 + std::string auth; +#endif + + std::shared_ptr filter; + + std::int64_t downloaded = -1; + std::int64_t uploaded = -1; + std::int64_t left = -1; + std::int64_t corrupt = 0; + std::int64_t redundant = 0; + std::uint16_t listen_port = 0; + event_t event = event_t::none; + tracker_request_flags_t kind = {}; + + std::uint32_t key = 0; + int num_want = 0; + std::vector ipv6; + std::vector ipv4; + sha1_hash info_hash; + peer_id pid; + + aux::listen_socket_handle outgoing_socket; + + // set to true if the .torrent file this tracker announce is for is marked + // as private (i.e. has the "priv": 1 key) + bool private_torrent = false; + + // this is set to true if this request was triggered by a "manual" call to + // scrape_tracker() or force_reannounce() + bool triggered_manually = false; + +#if TORRENT_USE_SSL + ssl::context* ssl_ctx = nullptr; +#endif +#if TORRENT_USE_I2P + i2p_connection* i2pconn = nullptr; +#endif + }; + + struct tracker_response + { + tracker_response() + : interval(1800) + , min_interval(1) + , complete(-1) + , incomplete(-1) + , downloaders(-1) + , downloaded(-1) + {} + + // peers from the tracker, in various forms + std::vector peers; + std::vector peers4; + std::vector peers6; +#if TORRENT_USE_I2P + std::vector i2p_peers; +#endif + // our external IP address (if the tracker responded with ti, otherwise + // INADDR_ANY) + address external_ip; + + // the tracker id, if it was included in the response, otherwise + // an empty string + std::string trackerid; + + // if the tracker returned an error, this is set to that error + std::string failure_reason; + + // contains a warning message from the tracker, if included in + // the response + std::string warning_message; + + // re-announce interval, in seconds + seconds32 interval; + + // the lowest force-announce interval + seconds32 min_interval; + + // the number of seeds in the swarm + int complete; + + // the number of downloaders in the swarm + int incomplete; + + // if supported by the tracker, the number of actively downloading peers. + // i.e. partial seeds. If not supported, -1 + int downloaders; + + // the number of times the torrent has been downloaded + int downloaded; + }; + + struct TORRENT_EXTRA_EXPORT request_callback + { + friend class tracker_manager; + request_callback() {} + virtual ~request_callback() {} + virtual void tracker_warning(tracker_request const& req + , std::string const& msg) = 0; + virtual void tracker_scrape_response(tracker_request const& /*req*/ + , int /*complete*/, int /*incomplete*/, int /*downloads*/ + , int /*downloaders*/) {} + virtual void tracker_response( + tracker_request const& req + , address const& tracker_ip + , std::list
    const& ip_list + , struct tracker_response const& response) = 0; + virtual void tracker_request_error( + tracker_request const& req + , error_code const& ec + , operation_t op + , const std::string& msg + , seconds32 retry_interval) = 0; + +#ifndef TORRENT_DISABLE_LOGGING + virtual bool should_log() const = 0; + virtual void debug_log(const char* fmt, ...) const noexcept TORRENT_FORMAT(2,3) = 0; +#endif + }; + + struct TORRENT_EXTRA_EXPORT timeout_handler + : std::enable_shared_from_this + { + explicit timeout_handler(io_context&); + + timeout_handler(timeout_handler const&) = delete; + timeout_handler& operator=(timeout_handler const&) = delete; + + void set_timeout(int completion_timeout, int read_timeout); + void restart_read_timeout(); + void cancel(); + bool cancelled() const { return m_abort; } + + virtual void on_timeout(error_code const& ec) = 0; + virtual ~timeout_handler(); + + auto get_executor() { return m_timeout.get_executor(); } + + private: + + void timeout_callback(error_code const&); + + int m_completion_timeout = 0; + + // used for timeouts + // this is set when the request has been sent + time_point m_start_time; + + // this is set every time something is received + time_point m_read_time; + + // the asio async operation + deadline_timer m_timeout; + + int m_read_timeout = 0; + + bool m_abort = false; +#if TORRENT_USE_ASSERTS + int m_outstanding_timer_wait = 0; +#endif + }; + + struct TORRENT_EXTRA_EXPORT tracker_connection + : timeout_handler + { + tracker_connection(tracker_manager& man + , tracker_request req + , io_context& ios + , std::weak_ptr r); + + std::shared_ptr requester() const; + ~tracker_connection() override {} + + tracker_request const& tracker_req() const { return m_req; } + + void fail(error_code const& ec, operation_t op, char const* msg = "" + , seconds32 interval = seconds32(0), seconds32 min_interval = seconds32(0)); + virtual void start() = 0; + virtual void close() = 0; + aux::listen_socket_handle const& bind_socket() const { return m_req.outgoing_socket; } + void sent_bytes(int bytes); + void received_bytes(int bytes); + + std::shared_ptr shared_from_this() + { + return std::static_pointer_cast( + timeout_handler::shared_from_this()); + } + + private: + + const tracker_request m_req; + + protected: + + void fail_impl(error_code const& ec, operation_t op, std::string msg = std::string() + , seconds32 interval = seconds32(0), seconds32 min_interval = seconds32(0)); + + std::weak_ptr m_requester; + + tracker_manager& m_man; + }; + + class TORRENT_EXTRA_EXPORT tracker_manager final + : single_threaded + { + public: + + using send_fun_t = std::function + , error_code&, udp_send_flags_t)>; + using send_fun_hostname_t = std::function + , error_code&, udp_send_flags_t)>; + + tracker_manager(send_fun_t send_fun + , send_fun_hostname_t send_fun_hostname + , counters& stats_counters + , aux::resolver_interface& resolver + , aux::session_settings const& sett +#if !defined TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + , aux::session_logger& ses +#endif + ); + + ~tracker_manager(); + + tracker_manager(tracker_manager const&) = delete; + tracker_manager& operator=(tracker_manager const&) = delete; + + void queue_request( + io_context& ios + , tracker_request&& r + , aux::session_settings const& sett + , std::weak_ptr c + = std::weak_ptr()); + void queue_request( + io_context& ios + , tracker_request const& r + , aux::session_settings const& sett + , std::weak_ptr c + = std::weak_ptr()) = delete; + void abort_all_requests(bool all = false); + void stop(); + + void remove_request(http_tracker_connection const* c); + void remove_request(udp_tracker_connection const* c); + bool empty() const; + int num_requests() const; + + void sent_bytes(int bytes); + void received_bytes(int bytes); + + void incoming_error(error_code const& ec, udp::endpoint const& ep); + bool incoming_packet(udp::endpoint const& ep, span buf); + + // this is only used for SOCKS packets, since + // they may be addressed to hostname + bool incoming_packet(string_view hostname, span buf); + + void update_transaction_id( + std::shared_ptr c + , std::uint32_t tid); + + aux::session_settings const& settings() const { return m_settings; } + aux::resolver_interface& host_resolver() { return m_host_resolver; } + + void send_hostname(aux::listen_socket_handle const& sock + , char const* hostname, int port, span p + , error_code& ec, udp_send_flags_t flags = {}); + + void send(aux::listen_socket_handle const& sock + , udp::endpoint const& ep, span p + , error_code& ec, udp_send_flags_t flags = {}); + + private: + + // maps transactionid to the udp_tracker_connection + // These must use shared_ptr to avoid a dangling reference + // if a connection is erased while a timeout event is in the queue + std::unordered_map> m_udp_conns; + + std::vector> m_http_conns; + std::deque> m_queued; + + send_fun_t m_send_fun; + send_fun_hostname_t m_send_fun_hostname; + aux::resolver_interface& m_host_resolver; + aux::session_settings const& m_settings; + counters& m_stats_counters; + bool m_abort = false; +#if !defined TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + aux::session_logger& m_ses; +#endif + }; +} + +#endif // TORRENT_TRACKER_MANAGER_HPP_INCLUDED diff --git a/include/libtorrent/truncate.hpp b/include/libtorrent/truncate.hpp new file mode 100644 index 0000000..0786c81 --- /dev/null +++ b/include/libtorrent/truncate.hpp @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2022, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TRUNCATE_HPP_INCLUDED +#define TORRENT_TRUNCATE_HPP_INCLUDED + +#include "libtorrent/fwd.hpp" +#include "libtorrent/error_code.hpp" +#include + +namespace libtorrent { + +// Truncates files larger than specified in the file_storage, saved under +// the specified save_path. +TORRENT_EXPORT void truncate_files(file_storage const& fs, std::string const& save_path, storage_error& ec); + +} + +#endif diff --git a/include/libtorrent/udp_socket.hpp b/include/libtorrent/udp_socket.hpp new file mode 100644 index 0000000..d723565 --- /dev/null +++ b/include/libtorrent/udp_socket.hpp @@ -0,0 +1,174 @@ +/* + +Copyright (c) 2007-2021, Arvid Norberg +Copyright (c) 2016, 2020, Alden Torres +Copyright (c) 2016, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UDP_SOCKET_HPP_INCLUDED +#define TORRENT_UDP_SOCKET_HPP_INCLUDED + +#include "libtorrent/socket.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/proxy_settings.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/aux_/listen_socket_handle.hpp" +#include "libtorrent/aux_/resolver_interface.hpp" + +#include +#include + +namespace libtorrent { + +namespace aux { struct alert_manager; } + struct socks5; + + using udp_send_flags_t = flags::bitfield_flag; + + class TORRENT_EXTRA_EXPORT udp_socket : single_threaded + { + public: + udp_socket(io_context& ios, aux::listen_socket_handle ls); + + // non-copyable + udp_socket(udp_socket const&) = delete; + udp_socket& operator=(udp_socket const&) = delete; + + static constexpr udp_send_flags_t peer_connection = 0_bit; + static constexpr udp_send_flags_t tracker_connection = 1_bit; + static constexpr udp_send_flags_t dont_queue = 2_bit; + static constexpr udp_send_flags_t dont_fragment = 3_bit; + + bool is_open() const { return m_abort == false; } + udp::socket::executor_type get_executor() { return m_socket.get_executor(); } + + template + void async_read(Handler&& h) + { + m_socket.async_wait(udp::socket::wait_read, std::forward(h)); + } + + template + void async_write(Handler&& h) + { + m_socket.async_wait(udp::socket::wait_write, std::forward(h)); + } + + struct packet + { + span data; + udp::endpoint from; + string_view hostname; + error_code error; + }; + + int read(span pkts, error_code& ec); + + // this is only valid when using a socks5 proxy + void send_hostname(char const* hostname, int port, span p + , error_code& ec, udp_send_flags_t flags = {}); + + void send(udp::endpoint const& ep, span p + , error_code& ec, udp_send_flags_t flags = {}); + void open(udp const& protocol, error_code& ec); + void bind(udp::endpoint const& ep, error_code& ec); + void close(); + int local_port() const { return m_bind_port; } + + void set_proxy_settings(aux::proxy_settings const& ps, aux::alert_manager& alerts + , aux::resolver_interface& resolver, bool send_local_ep); + aux::proxy_settings const& get_proxy_settings() { return m_proxy_settings; } + + bool is_closed() const { return m_abort; } + udp::endpoint local_endpoint(error_code& ec) const + { return m_socket.local_endpoint(ec); } + // best effort, if you want to know the error, use + // ``local_endpoint(error_code& ec)`` + udp::endpoint local_endpoint() const + { + error_code ec; + return local_endpoint(ec); + } + + using receive_buffer_size = udp::socket::receive_buffer_size; + using send_buffer_size = udp::socket::send_buffer_size; + + template + void get_option(SocketOption const& opt, error_code& ec) + { + m_socket.get_option(opt, ec); + } + + template + void set_option(SocketOption const& opt, error_code& ec) + { + m_socket.set_option(opt, ec); + } + +#ifdef TCP_NOTSENT_LOWAT + void set_option(tcp_notsent_lowat const&, error_code&) {} +#endif + + template + void get_option(SocketOption& opt, error_code& ec) + { + m_socket.get_option(opt, ec); + } + + bool active_socks5() const; + + private: + + void wrap(udp::endpoint const& ep, span p, error_code& ec, udp_send_flags_t flags); + void wrap(char const* hostname, int port, span p, error_code& ec, udp_send_flags_t flags); + bool unwrap(udp_socket::packet& pack); + + udp::socket m_socket; + + io_context& m_ioc; + + using receive_buffer = std::array; + std::unique_ptr m_buf; + aux::listen_socket_handle m_listen_socket; + + std::uint16_t m_bind_port; + + aux::proxy_settings m_proxy_settings; + + std::shared_ptr m_socks5_connection; + + bool m_abort:1; + }; +} + +#endif diff --git a/include/libtorrent/udp_tracker_connection.hpp b/include/libtorrent/udp_tracker_connection.hpp new file mode 100644 index 0000000..11b6158 --- /dev/null +++ b/include/libtorrent/udp_tracker_connection.hpp @@ -0,0 +1,134 @@ +/* + +Copyright (c) 2004-2008, 2010, 2012, 2014-2017, 2019-2020, Arvid Norberg +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UDP_TRACKER_CONNECTION_HPP_INCLUDED +#define TORRENT_UDP_TRACKER_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/string_view.hpp" + +namespace libtorrent { + + class TORRENT_EXTRA_EXPORT udp_tracker_connection: public tracker_connection + { + friend class tracker_manager; + public: + + udp_tracker_connection( + io_context& ios + , tracker_manager& man + , tracker_request const& req + , std::weak_ptr c); + + void start() override; + void close() override; + + std::uint32_t transaction_id() const { return m_transaction_id; } + + private: + + enum class action_t : std::uint8_t + { + connect, + announce, + scrape, + error + }; + + std::shared_ptr shared_from_this() + { + return std::static_pointer_cast( + tracker_connection::shared_from_this()); + } + + void update_transaction_id(); + + void name_lookup(error_code const& error + , std::vector
    const& addresses, int port); + void start_announce(); + + bool on_receive(udp::endpoint const& ep, span buf); + bool on_receive_hostname(string_view hostname, span buf); + bool on_connect_response(span buf); + bool on_announce_response(span buf); + bool on_scrape_response(span buf); + + // wraps tracker_connection::fail + void fail(error_code const& ec + , operation_t op + , char const* msg = "" + , seconds32 interval = seconds32(0) + , seconds32 min_interval = seconds32(30)); + + void send_udp_connect(); + void send_udp_announce(); + void send_udp_scrape(); + + void on_timeout(error_code const& ec) override; + + std::string m_hostname; + std::vector m_endpoints; + + struct connection_cache_entry + { + std::int64_t connection_id; + time_point expires; + }; + + static std::map m_connection_cache; + static std::mutex m_cache_mutex; + + udp::endpoint m_target; + + std::uint32_t m_transaction_id; + int m_attempts; + + action_t m_state; + + bool m_abort; + }; + +} + +#endif // TORRENT_UDP_TRACKER_CONNECTION_HPP_INCLUDED diff --git a/include/libtorrent/union_endpoint.hpp b/include/libtorrent/union_endpoint.hpp new file mode 100644 index 0000000..2aa2ec6 --- /dev/null +++ b/include/libtorrent/union_endpoint.hpp @@ -0,0 +1,115 @@ +/* + +Copyright (c) 2010, 2013, 2015-2019, Arvid Norberg +Copyright (c) 2015, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UNION_ENDPOINT_HPP_INCLUDED +#define TORRENT_UNION_ENDPOINT_HPP_INCLUDED + +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" + +namespace libtorrent { + + struct union_address + { + union_address() { *this = address(); } + explicit union_address(address const& a) { *this = a; } + union_address& operator=(address const& a) & + { + v4 = a.is_v4(); + if (v4) + addr.v4 = a.to_v4().to_bytes(); + else + addr.v6 = a.to_v6().to_bytes(); + return *this; + } + + bool operator==(union_address const& rh) const + { + if (v4 != rh.v4) return false; + if (v4) + return addr.v4 == rh.addr.v4; + else + return addr.v6 == rh.addr.v6; + } + + bool operator!=(union_address const& rh) const + { + return !(*this == rh); + } + + operator address() const + { + if (v4) return address(address_v4(addr.v4)); + else return address(address_v6(addr.v6)); + } + + union addr_t + { + address_v4::bytes_type v4; + address_v6::bytes_type v6; + }; + addr_t addr; + bool v4:1; + }; + + struct union_endpoint + { + explicit union_endpoint(tcp::endpoint const& ep) { *this = ep; } + explicit union_endpoint(udp::endpoint const& ep) { *this = ep; } + union_endpoint() { *this = tcp::endpoint(); } + + union_endpoint& operator=(udp::endpoint const& ep) & + { + addr = ep.address(); + port = ep.port(); + return *this; + } + + operator udp::endpoint() const { return udp::endpoint(addr, port); } + + union_endpoint& operator=(tcp::endpoint const& ep) & + { + addr = ep.address(); + port = ep.port(); + return *this; + } + + libtorrent::address address() const { return addr; } + operator tcp::endpoint() const { return tcp::endpoint(addr, port); } + + union_address addr; + std::uint16_t port; + }; +} + +#endif diff --git a/include/libtorrent/units.hpp b/include/libtorrent/units.hpp new file mode 100644 index 0000000..131775a --- /dev/null +++ b/include/libtorrent/units.hpp @@ -0,0 +1,186 @@ +/* + +Copyright (c) 2016-2017, Alden Torres +Copyright (c) 2016-2021, Arvid Norberg +Copyright (c) 2019, Steven Siloti +Copyright (c) 2020, Silver Zachara +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UNITS_HPP +#define TORRENT_UNITS_HPP + +#include +#include +#include +#include +#include + +#include "libtorrent/config.hpp" + +namespace libtorrent { +namespace aux { + template + struct difference_tag; + +#if TORRENT_USE_IOSTREAM + template + struct type_to_print_as + { + using type = typename std::conditional::type; + }; +#endif + + + template::value>::type> + struct strong_typedef + { + using underlying_type = UnderlyingType; + using diff_type = strong_typedef>; + + constexpr strong_typedef(strong_typedef const& rhs) noexcept = default; + constexpr strong_typedef(strong_typedef&& rhs) noexcept = default; + strong_typedef() noexcept = default; +#if TORRENT_ABI_VERSION == 1 + constexpr strong_typedef(UnderlyingType val) : m_val(val) {} + constexpr operator UnderlyingType() const { return m_val; } +#else + constexpr explicit strong_typedef(UnderlyingType val) : m_val(val) {} + constexpr explicit operator UnderlyingType() const { return m_val; } + constexpr bool operator==(strong_typedef const& rhs) const { return m_val == rhs.m_val; } + constexpr bool operator!=(strong_typedef const& rhs) const { return m_val != rhs.m_val; } + constexpr bool operator<(strong_typedef const& rhs) const { return m_val < rhs.m_val; } + constexpr bool operator>(strong_typedef const& rhs) const { return m_val > rhs.m_val; } + constexpr bool operator>=(strong_typedef const& rhs) const { return m_val >= rhs.m_val; } + constexpr bool operator<=(strong_typedef const& rhs) const { return m_val <= rhs.m_val; } +#endif + strong_typedef& operator++() { ++m_val; return *this; } + strong_typedef& operator--() { --m_val; return *this; } + + strong_typedef operator++(int) & { return strong_typedef{m_val++}; } + strong_typedef operator--(int) & { return strong_typedef{m_val--}; } + + friend diff_type operator-(strong_typedef lhs, strong_typedef rhs) + { return diff_type{lhs.m_val - rhs.m_val}; } + friend strong_typedef operator+(strong_typedef lhs, diff_type rhs) + { return strong_typedef{lhs.m_val + static_cast(rhs)}; } + friend strong_typedef operator+(diff_type lhs, strong_typedef rhs) + { return strong_typedef{static_cast(lhs) + rhs.m_val}; } + friend strong_typedef operator-(strong_typedef lhs, diff_type rhs) + { return strong_typedef{lhs.m_val - static_cast(rhs)}; } + + strong_typedef& operator+=(diff_type rhs) & + { m_val += static_cast(rhs); return *this; } + strong_typedef& operator-=(diff_type rhs) & + { m_val -= static_cast(rhs); return *this; } + + strong_typedef& operator=(strong_typedef const& rhs) & noexcept = default; + strong_typedef& operator=(strong_typedef&& rhs) & noexcept = default; + +#if TORRENT_USE_IOSTREAM + friend std::ostream& operator<<(std::ostream& os, strong_typedef val) + { return os << static_cast::type>(static_cast(val)); } +#endif + + private: + UnderlyingType m_val; + }; + + // meta function to return the underlying type of a strong_typedef or enumeration + // , or the type itself if it isn't a strong_typedef + template + struct underlying_index_t { using type = T; }; + + template + struct underlying_index_t::value>::type> + { using type = typename std::underlying_type::type; }; + + template + struct underlying_index_t> { using type = U; }; + + struct piece_index_tag; + struct file_index_tag; + + template + std::string to_string(strong_typedef const t) + { return std::to_string(static_cast(t)); } + + template + strong_typedef next(strong_typedef v) + { return ++v;} + + template + strong_typedef prev(strong_typedef v) + { return --v;} + +} // namespace libtorrent::aux + + // this type represents a piece index in a torrent. + using piece_index_t = aux::strong_typedef; + + // this type represents an index to a file in a torrent. Any specific torrent + // file has a well defined and immutable file list, and a file index into it + // always refers to the same file. + using file_index_t = aux::strong_typedef; + +} // namespace libtorrent + +namespace std { + + template + class numeric_limits> : public std::numeric_limits + { + using type = libtorrent::aux::strong_typedef; + public: + + static constexpr type (min)() + { return type((std::numeric_limits::min)()); } + + static constexpr type (max)() + { return type((std::numeric_limits::max)()); } + }; + + template + struct hash> : std::hash + { + using base = std::hash; + using argument_type = libtorrent::aux::strong_typedef; +#if __cplusplus < 201402 + // this was deprecated in C++17 + using result_type = typename base::result_type; +#else + using result_type = std::size_t; +#endif + result_type operator()(argument_type const& s) const + { return this->base::operator()(static_cast(s)); } + }; +} + +#endif diff --git a/include/libtorrent/upnp.hpp b/include/libtorrent/upnp.hpp new file mode 100644 index 0000000..42f33c0 --- /dev/null +++ b/include/libtorrent/upnp.hpp @@ -0,0 +1,406 @@ +/* + +Copyright (c) 2007-2010, 2013-2020, 2022, Arvid Norberg +Copyright (c) 2016-2017, 2020, Alden Torres +Copyright (c) 2016, Andrei Kurushin +Copyright (c) 2016, Pavel Pimenov +Copyright (c) 2020, Paul-Louis Ageneau +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UPNP_HPP +#define TORRENT_UPNP_HPP + +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/aux_/resolver.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/aux_/portmap.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/listen_socket_handle.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/ssl.hpp" + +#include +#include +#include + +namespace libtorrent { + struct http_connection; + class http_parser; + +namespace aux { + + struct socket_package + { + socket_package(io_context& ios) : socket(ios) {} + udp::socket socket; + std::array buffer; + udp::endpoint remote; + }; +} + +namespace upnp_errors { + // error codes for the upnp_error_category. They hold error codes + // returned by UPnP routers when mapping ports + enum error_code_enum + { + // No error + no_error = 0, + // One of the arguments in the request is invalid + invalid_argument = 402, + // The request failed + action_failed = 501, + // The specified value does not exist in the array + value_not_in_array = 714, + // The source IP address cannot be wild-carded, but + // must be fully specified + source_ip_cannot_be_wildcarded = 715, + // The external port cannot be a wildcard, but must + // be specified + external_port_cannot_be_wildcarded = 716, + // The port mapping entry specified conflicts with a + // mapping assigned previously to another client + port_mapping_conflict = 718, + // Internal and external port value must be the same + internal_port_must_match_external = 724, + // The NAT implementation only supports permanent + // lease times on port mappings + only_permanent_leases_supported = 725, + // RemoteHost must be a wildcard and cannot be a + // specific IP address or DNS name + remote_host_must_be_wildcard = 726, + // ExternalPort must be a wildcard and cannot be a + // specific port + external_port_must_be_wildcard = 727 + }; + + // hidden + TORRENT_EXPORT boost::system::error_code make_error_code(error_code_enum e); +} // namespace upnp_errors + + // the boost.system error category for UPnP errors + TORRENT_EXPORT boost::system::error_category& upnp_category(); + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + inline boost::system::error_category& get_upnp_category() + { return upnp_category(); } +#endif + +struct parse_state +{ + bool in_service = false; + std::vector tag_stack; + std::string control_url; + std::string service_type; + std::string model; + std::string url_base; + bool top_tags(string_view str1, string_view str2) + { + auto i = tag_stack.rbegin(); + if (i == tag_stack.rend()) return false; + if (!string_equal_no_case(*i, str2)) return false; + ++i; + if (i == tag_stack.rend()) return false; + if (!string_equal_no_case(*i, str1)) return false; + return true; + } +}; + +struct error_code_parse_state +{ + bool in_error_code = false; + bool exit = false; + int error_code = -1; +}; + +struct ip_address_parse_state: error_code_parse_state +{ + bool in_ip_address = false; + std::string ip_address; +}; + +TORRENT_EXTRA_EXPORT void find_control_url(int type, string_view, parse_state& state); + +TORRENT_EXTRA_EXPORT void find_error_code(int type, string_view string + , error_code_parse_state& state); + +TORRENT_EXTRA_EXPORT void find_ip_address(int type, string_view string + , ip_address_parse_state& state); + +// TODO: support using the windows API for UPnP operations as well +struct TORRENT_EXTRA_EXPORT upnp final + : std::enable_shared_from_this + , single_threaded +{ + upnp(io_context& ios + , aux::session_settings const& settings + , aux::portmap_callback& cb + , address_v4 listen_address + , address_v4 netmask + , std::string listen_device + , aux::listen_socket_handle ls); + ~upnp(); + + void start(); + + // Attempts to add a port mapping for the specified protocol. Valid protocols are + // ``upnp::tcp`` and ``upnp::udp`` for the UPnP class and ``natpmp::tcp`` and + // ``natpmp::udp`` for the NAT-PMP class. + // + // ``external_port`` is the port on the external address that will be mapped. This + // is a hint, you are not guaranteed that this port will be available, and it may + // end up being something else. In the portmap_alert_ notification, the actual + // external port is reported. + // + // ``local_port`` is the port in the local machine that the mapping should forward + // to. + // + // The return value is an index that identifies this port mapping. This is used + // to refer to mappings that fails or succeeds in the portmap_error_alert_ and + // portmap_alert_ respectively. If The mapping fails immediately, the return value + // is -1, which means failure. There will not be any error alert notification for + // mappings that fail with a -1 return value. + port_mapping_t add_mapping(portmap_protocol p, int external_port, tcp::endpoint local_ep + , std::string const& device); + + // This function removes a port mapping. ``mapping_index`` is the index that refers + // to the mapping you want to remove, which was returned from add_mapping(). + void delete_mapping(port_mapping_t mapping_index); + + bool get_mapping(port_mapping_t mapping_index, tcp::endpoint& local_ep, int& external_port + , portmap_protocol& protocol) const; + + void close(); + + // This is only available for UPnP routers. If the model is advertised by + // the router, it can be queried through this function. + std::string router_model() + { + TORRENT_ASSERT(is_single_thread()); + return m_model; + } + +private: + + std::shared_ptr self() { return shared_from_this(); } + + void open_multicast_socket(aux::socket_package& s, error_code& ec); + void open_unicast_socket(aux::socket_package& s, error_code& ec); + + void map_timer(error_code const& ec); + void try_map_upnp(); + void discover_device_impl(); + + void resend_request(error_code const& e); + void on_reply(aux::socket_package& s, error_code const& ec, std::size_t len); + + struct rootdevice; + void next(rootdevice& d, port_mapping_t i); + void update_map(rootdevice& d, port_mapping_t i); + + int lease_duration(rootdevice const& d) const; + + void connect(rootdevice& d); + + void on_upnp_xml(error_code const& e + , libtorrent::http_parser const& p, rootdevice& d + , http_connection& c); + void on_upnp_get_ip_address_response(error_code const& e + , libtorrent::http_parser const& p, rootdevice& d + , http_connection& c); + void on_upnp_map_response(error_code const& e + , libtorrent::http_parser const& p, rootdevice& d + , port_mapping_t mapping, http_connection& c); + void on_upnp_unmap_response(error_code const& e + , libtorrent::http_parser const& p, rootdevice& d + , port_mapping_t mapping, http_connection& c); + void on_expire(error_code const& e); + + void disable(error_code const& ec); + void return_error(port_mapping_t mapping, int code); +#ifndef TORRENT_DISABLE_LOGGING + bool should_log() const; + void log(char const* fmt, ...) const TORRENT_FORMAT(2,3); +#endif + + void get_ip_address(rootdevice& d); + void delete_port_mapping(rootdevice& d, port_mapping_t i); + void create_port_mapping(http_connection& c, rootdevice& d, port_mapping_t i); + void post(upnp::rootdevice const& d, char const* soap + , char const* soap_action); + + int num_mappings() const { return int(m_mappings.size()); } + + struct global_mapping_t + { + portmap_protocol protocol = portmap_protocol::none; + int external_port = 0; + tcp::endpoint local_ep; + // may be set to a device name, if this mapping is for a network bound + // to a specific network device + std::string device; + }; + + struct mapping_t : aux::base_mapping + { + // the local port for this mapping. If this is set + // to 0, the mapping is not in use + tcp::endpoint local_ep; + + // may be set to a network device name to bind to + std::string device; + + // the number of times this mapping has failed + int failcount = 0; + }; + + struct rootdevice + { + rootdevice(); + ~rootdevice(); + rootdevice(rootdevice const&); + rootdevice& operator=(rootdevice const&) &; + rootdevice(rootdevice&&) noexcept; + rootdevice& operator=(rootdevice&&) &; + + // the interface url, through which the list of + // supported interfaces are fetched + std::string url; + + // the url to the WANIP or WANPPP interface + std::string control_url; + // either the WANIP namespace or the WANPPP namespace + std::string service_namespace; + + aux::noexcept_movable> mapping; + + // this is the hostname, port and path + // component of the url or the control_url + // if it has been found + std::string hostname; + int port = 0; + std::string path; + aux::noexcept_movable
    external_ip; + + // set to false if the router doesn't support lease durations + bool use_lease_duration = true; + + // true if the device supports specifying a + // specific external port, false if it doesn't + bool supports_specific_external = true; + + bool disabled = false; + + mutable std::shared_ptr upnp_connection; + +#if TORRENT_USE_ASSERTS + int magic = 1337; +#endif + + bool operator<(rootdevice const& rhs) const + { return url < rhs.url; } + }; + + struct upnp_state_t + { + aux::vector mappings; + std::set devices; + }; + + aux::vector m_mappings; + + aux::session_settings const& m_settings; + + // the set of devices we've found + std::set m_devices; + + aux::portmap_callback& m_callback; + + // current retry count + int m_retry_count = 0; + + io_context& m_io_service; + + aux::resolver m_resolver; + + // the udp socket used to send and receive + // multicast messages on the network + aux::socket_package m_multicast; + aux::socket_package m_unicast; + + // used to resend udp packets in case + // they time out + deadline_timer m_broadcast_timer; + + // timer used to refresh mappings + deadline_timer m_refresh_timer; + + // this timer fires one second after the last UPnP response. This is the + // point where we assume we have received most or all SSDP responses. If we + // are ignoring non-routers and at this point we still haven't received a + // response from a router UPnP device, we override the ignoring behavior and + // map them anyway. + deadline_timer m_map_timer; + + bool m_disabled = false; + bool m_closing = false; + + std::string m_model; + + // the network this UPnP mapper is associated with. Don't talk to any other + // network + address_v4 m_listen_address; + address_v4 m_netmask; + std::string m_device; + +#if TORRENT_USE_SSL + ssl::context m_ssl_ctx; +#endif + + aux::listen_socket_handle m_listen_handle; +}; + +} // namespace libtorrent + +namespace boost { +namespace system { + + template<> struct is_error_code_enum + { static const bool value = true; }; + +} +} + +#endif diff --git a/include/libtorrent/utf8.hpp b/include/libtorrent/utf8.hpp new file mode 100644 index 0000000..9c0596e --- /dev/null +++ b/include/libtorrent/utf8.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2005, 2008-2009, 2013, 2016-2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UTF8_HPP_INCLUDED +#define TORRENT_UTF8_HPP_INCLUDED + +#include "libtorrent/aux_/export.hpp" + +#include +#include + +#include "libtorrent/string_view.hpp" + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT std::pair + parse_utf8_codepoint(string_view str); + + TORRENT_EXTRA_EXPORT void append_utf8_codepoint(std::string&, std::int32_t); + +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/vector_utils.hpp b/include/libtorrent/vector_utils.hpp new file mode 100644 index 0000000..01fc3bf --- /dev/null +++ b/include/libtorrent/vector_utils.hpp @@ -0,0 +1,59 @@ +/* + +Copyright (c) 2010, 2013-2014, 2016, 2018-2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_VECTOR_UTILS_HPP_INCLUDE +#define TORRENT_VECTOR_UTILS_HPP_INCLUDE + +#include +#include + +namespace libtorrent { + + template + auto sorted_find(Container& container, T const& v) + -> decltype(container.begin()) + { + auto i = std::lower_bound(container.begin(), container.end(), v); + if (i == container.end()) return container.end(); + if (*i != v) return container.end(); + return i; + } + + template + void sorted_insert(std::vector& container, U v) + { + auto i = std::lower_bound(container.begin(), container.end(), v); + container.insert(i, v); + } +} + +#endif diff --git a/include/libtorrent/version.hpp b/include/libtorrent/version.hpp new file mode 100644 index 0000000..15ee5bd --- /dev/null +++ b/include/libtorrent/version.hpp @@ -0,0 +1,72 @@ +/* + +Copyright (c) 2004, 2006, 2010, 2015, 2017-2020, 2022, Arvid Norberg +Copyright (c) 2016, Jan Berkel +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_VERSION_HPP_INCLUDED +#define TORRENT_VERSION_HPP_INCLUDED + +#include "libtorrent/aux_/export.hpp" +#include + +#define LIBTORRENT_VERSION_MAJOR 2 +#define LIBTORRENT_VERSION_MINOR 0 +#define LIBTORRENT_VERSION_TINY 11 + +// the format of this version is: MMmmtt +// M = Major version, m = minor version, t = tiny version +#define LIBTORRENT_VERSION_NUM ((LIBTORRENT_VERSION_MAJOR * 10000) + (LIBTORRENT_VERSION_MINOR * 100) + LIBTORRENT_VERSION_TINY) + +#define LIBTORRENT_VERSION "2.0.11.0" +#define LIBTORRENT_REVISION "6e1587799" + +namespace libtorrent { + + // the major, minor and tiny versions of libtorrent + constexpr int version_major = 2; + constexpr int version_minor = 0; + constexpr int version_tiny = 11; + + // the libtorrent version in string form + constexpr char const* version_str = "2.0.11.0"; + + // the git commit of this libtorrent version + constexpr std::uint64_t version_revision = 0x6e1587799; + + // returns the libtorrent version as string form in this format: + // "..." + TORRENT_EXPORT char const* version(); + +} + +namespace lt = libtorrent; + +#endif diff --git a/include/libtorrent/web_connection_base.hpp b/include/libtorrent/web_connection_base.hpp new file mode 100644 index 0000000..99eb566 --- /dev/null +++ b/include/libtorrent/web_connection_base.hpp @@ -0,0 +1,137 @@ +/* + +Copyright (c) 2010, 2012, 2014-2020, Arvid Norberg +Copyright (c) 2016, 2019, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef WEB_CONNECTION_BASE_HPP_INCLUDED +#define WEB_CONNECTION_BASE_HPP_INCLUDED + +#include +#include +#include + +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/http_parser.hpp" + +namespace libtorrent { + + class TORRENT_EXTRA_EXPORT web_connection_base + : public peer_connection + { + friend struct invariant_access; + public: + + // this is the constructor where the we are the active part. + // The peer_connection should handshake and verify that the + // other end has the correct id + web_connection_base(peer_connection_args& pack + , web_seed_t const& web); + + int timeout() const override; + void start() override; + + ~web_connection_base() override; + + // called from the main loop when this connection has any + // work to do. + void on_sent(error_code const& error + , std::size_t bytes_transferred) override; + + virtual std::string const& url() const = 0; + + bool in_handshake() const override; + + peer_id our_pid() const override { return peer_id(); } + + // the following functions appends messages + // to the send buffer + void write_choke() override {} + void write_unchoke() override {} + void write_interested() override {} + void write_not_interested() override {} + void write_request(peer_request const&) override = 0; + void write_cancel(peer_request const&) override {} + void write_have(piece_index_t) override {} + void write_dont_have(piece_index_t) override {} + void write_piece(peer_request const&, disk_buffer_holder) override + { TORRENT_ASSERT_FAIL(); } + void write_keepalive() override {} + void on_connected() override; + void write_reject_request(peer_request const&) override {} + void write_allow_fast(piece_index_t) override {} + void write_suggest(piece_index_t) override {} + void write_bitfield() override {} + void write_upload_only(bool) override {} + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + void get_specific_peer_info(peer_info& p) const override; + + protected: + + virtual void add_headers(std::string& request + , aux::session_settings const& sett, bool using_proxy) const; + + // the first request will contain a little bit more data + // than subsequent ones, things that aren't critical are left + // out to save bandwidth. + bool m_first_request; + + // true if we're using ssl + bool m_ssl; + + // this has one entry per bittorrent request + std::deque m_requests; + + std::string m_server_string; + std::string m_basic_auth; + std::string m_host; + std::string m_path; + + std::string m_external_auth; + web_seed_entry::headers_t m_extra_headers; + + http_parser m_parser; + + int m_port; + + // the number of bytes into the receive buffer where + // current read cursor is. + int m_body_start; + }; +} + +#endif // TORRENT_WEB_CONNECTION_BASE_HPP_INCLUDED diff --git a/include/libtorrent/web_peer_connection.hpp b/include/libtorrent/web_peer_connection.hpp new file mode 100644 index 0000000..f319a52 --- /dev/null +++ b/include/libtorrent/web_peer_connection.hpp @@ -0,0 +1,147 @@ +/* + +Copyright (c) 2006-2007, 2009-2020, Arvid Norberg +Copyright (c) 2016-2017, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_WEB_PEER_CONNECTION_HPP_INCLUDED +#define TORRENT_WEB_PEER_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/web_connection_base.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/operations.hpp" // for operation_t enum +#include "libtorrent/aux_/vector.hpp" + +namespace libtorrent { + + class TORRENT_EXTRA_EXPORT web_peer_connection + : public web_connection_base + { + friend struct invariant_access; + public: + + // this is the constructor where the we are the active part. + // The peer_connection should handshake and verify that the + // other end has the correct id + web_peer_connection(peer_connection_args& pack + , web_seed_t& web); + + void on_connected() override; + + connection_type type() const override + { return connection_type::url_seed; } + + // called from the main loop when this connection has any + // work to do. + void on_receive(error_code const& error + , std::size_t bytes_transferred) override; + + std::string const& url() const override { return m_url; } + + void get_specific_peer_info(peer_info& p) const override; + void disconnect(error_code const& ec + , operation_t op, disconnect_severity_t error = peer_connection_interface::normal) override; + + void write_request(peer_request const& r) override; + + bool received_invalid_data(piece_index_t index, bool single_peer) override; + + private: + + void on_receive_padfile(); + void incoming_payload(char const* buf, int len); + void incoming_zeroes(int len); + void handle_redirect(int bytes_left); + void handle_error(int bytes_left); + void maybe_harvest_piece(); + void disable(error_code const& ec); + + // returns the block currently being + // downloaded. And the progress of that + // block. If the peer isn't downloading + // a piece for the moment, the boost::optional + // will be invalid. + piece_block_progress downloading_piece_progress() const override; + + void handle_padfile(); + + // this has one entry per http-request + // (might be more than the bt requests) + struct file_request_t + { + file_index_t file_index; + int length; + std::int64_t start; + }; + std::deque m_file_requests; + + std::string m_url; + + web_seed_t* m_web; + + // this is used for intermediate storage of pieces to be delivered to the + // bittorrent engine + // TODO: 3 if we make this be a disk_buffer_holder instead + // we would save a copy + // use allocate_disk_receive_buffer and release_disk_receive_buffer + aux::vector m_piece; + + // the number of bytes we've forwarded to the incoming_payload() function + // in the current HTTP response. used to know where in the buffer the + // next response starts + int m_received_body; + + // this is the offset inside the current receive + // buffer where the next chunk header will be. + // this is updated for each chunk header that's + // parsed. It does not necessarily point to a valid + // offset in the receive buffer, if we haven't received + // it yet. This offset never includes the HTTP header + int m_chunk_pos; + + // this is the number of bytes we've already received + // from the next chunk header we're waiting for + int m_partial_chunk_header; + + // the number of responses we've received so far on + // this connection + int m_num_responses; + }; +} + +#endif // TORRENT_WEB_PEER_CONNECTION_HPP_INCLUDED diff --git a/include/libtorrent/write_resume_data.hpp b/include/libtorrent/write_resume_data.hpp new file mode 100644 index 0000000..e1ff682 --- /dev/null +++ b/include/libtorrent/write_resume_data.hpp @@ -0,0 +1,84 @@ +/* + +Copyright (c) 2017-2018, 2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_WRITE_RESUME_DATA_HPP_INCLUDE +#define TORRENT_WRITE_RESUME_DATA_HPP_INCLUDE + +#include + +#include "libtorrent/fwd.hpp" +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/flags.hpp" + +namespace libtorrent { + + // this function turns the resume data in an ``add_torrent_params`` object + // into a bencoded structure + TORRENT_EXPORT entry write_resume_data(add_torrent_params const& atp); + TORRENT_EXPORT std::vector write_resume_data_buf(add_torrent_params const& atp); + + // hidden + using write_torrent_flags_t = flags::bitfield_flag; + + namespace write_flags + { + // this makes write_torrent_file() not fail when attempting to write a + // v2 torrent file that does not have all the piece layers + constexpr write_torrent_flags_t allow_missing_piece_layer = 0_bit; + + // don't include http seeds in the torrent file, even if some are + // present in the add_torrent_params object + constexpr write_torrent_flags_t no_http_seeds = 1_bit; + + // When set, DHT nodes from the add_torrent_params objects are included + // in the resulting .torrent file + constexpr write_torrent_flags_t include_dht_nodes = 2_bit; + } + + // writes only the fields to create a .torrent file. This function may fail + // with a ``std::system_error`` exception if: + // + // * The add_torrent_params object passed to this function does not contain the + // info dictionary (the ``ti`` field) + // * The piece layers are not complete for all files that need them + // + // The ``write_torrent_file_buf()`` overload returns the torrent file in + // bencoded buffer form. This overload may be faster at the expense of lost + // flexibility to add custom fields. + TORRENT_EXPORT entry write_torrent_file(add_torrent_params const& atp); + TORRENT_EXPORT entry write_torrent_file(add_torrent_params const& atp, write_torrent_flags_t flags); + TORRENT_EXPORT std::vector write_torrent_file_buf(add_torrent_params const& atp + , write_torrent_flags_t flags); +} + +#endif diff --git a/include/libtorrent/xml_parse.hpp b/include/libtorrent/xml_parse.hpp new file mode 100644 index 0000000..014dc01 --- /dev/null +++ b/include/libtorrent/xml_parse.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2007, 2011-2017, 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_XML_PARSE_HPP +#define TORRENT_XML_PARSE_HPP + +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/string_view.hpp" + +namespace libtorrent { + + enum + { + xml_start_tag, + xml_end_tag, + xml_empty_tag, + xml_declaration_tag, + xml_string, + xml_attribute, + xml_comment, + xml_parse_error, + // used for tags that don't follow the convention of + // key-value pairs inside the tag brackets. Like !DOCTYPE + xml_tag_content + }; + + // callback(int type, char const* name, int name_len + // , char const* val, int val_len) + // name is element or attribute name + // val is attribute value + // neither string is 0-terminated, but their lengths are specified via + // name_len and val_len respectively + TORRENT_EXTRA_EXPORT void xml_parse(string_view input + , std::function callback); +} + + +#endif diff --git a/project-config.jam b/project-config.jam new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0f4af51 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,86 @@ +[project] +requires-python = ">=3.9" + +[tool.cibuildwheel] +skip = "{pp*,}" + +[tool.cibuildwheel.macos] +before-all = [ + "./tools/cibuildwheel/setup_boost.sh $BOOST_VERSION $BOOST_ROOT", + "brew install openssl" +] +test-command = [ + "cd {project}/bindings/python", + "python test.py" +] + +[tool.cibuildwheel.macos.environment] +BOOST_BUILD_PATH = "/tmp/boost/tools/build" +BOOST_ROOT = "/tmp/boost" +BOOST_VERSION = "1.83.0" +PATH = "/tmp/boost:$PATH" + +[[tool.cibuildwheel.overrides]] +before-all = [ + "./tools/cibuildwheel/setup_boost.sh $BOOST_VERSION $BOOST_ROOT", + "./tools/cibuildwheel/setup_ccache_on_manylinux.sh", + "./tools/cibuildwheel/setup_openssl.sh", + "yum install -y glibc-static" # needed for libutil.a and libdl.a +] +before-test = "ccache -s" +select = "*-manylinux_*" +test-command = [ + "cd {project}/bindings/python", + "python test.py" +] + +[tool.cibuildwheel.overrides.environment] # sub-table of previous block! +BOOST_BUILD_PATH = "/tmp/boost/tools/build" +BOOST_ROOT = "/tmp/boost" +BOOST_VERSION = "1.83.0" +PATH = "/usr/local/ccache/bin:/tmp/boost:$PATH" + +[[tool.cibuildwheel.overrides]] +before-all = [ + "./tools/cibuildwheel/setup_boost.sh $BOOST_VERSION $BOOST_ROOT", + "./tools/cibuildwheel/setup_openssl.sh", + "apk add ccache openssl-dev openssl-libs-static" +] +before-test = "ccache -s" +select = "*-musllinux_*" +test-command = [ + "cd {project}/bindings/python", + "python test.py" +] + +[tool.cibuildwheel.overrides.environment] # sub-table of previous block! +BOOST_BUILD_PATH = "/tmp/boost/tools/build" +BOOST_ROOT = "/tmp/boost" +BOOST_VERSION = "1.83.0" +PATH = "/usr/lib/ccache/bin:/tmp/boost:$PATH" + +[[tool.cibuildwheel.overrides]] +before-all = [ + "bash -c './tools/cibuildwheel/setup_boost.sh $BOOST_VERSION $BOOST_ROOT'" +] +select = "*-win32" + +[[tool.cibuildwheel.overrides]] +before-all = [ + "bash -c './tools/cibuildwheel/setup_boost.sh $BOOST_VERSION $BOOST_ROOT'" +] +select = "*-win_amd64" + +[tool.cibuildwheel.windows] +test-command = '''bash -c "cd '{project}/bindings/python' && python test.py"''' + +[tool.cibuildwheel.windows.environment] +BOOST_BUILD_PATH = 'c:/boost/tools/build' +BOOST_ROOT = 'c:/boost' +BOOST_VERSION = "1.83.0" +PATH = 'c:/boost;$PATH' + +[tool.isort] +profile = "google" +single_line_exclusions = [] +src_paths = ["."] diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..f3187b5 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,15 @@ +[flake8] +# https://black.readthedocs.io/en/stable/compatible_configs.html#flake8 +max-line-length = 88 +extend-ignore = E203, W503 + +[mypy] +warn_return_any = True +warn_redundant_casts = True +warn_unused_ignores = True +warn_unreachable = True +warn_unused_configs = True +#disallow_any_unimported = True +disallow_untyped_defs = True +disallow_incomplete_defs = True +mypy_path = bindings/python/install_data diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..190366e --- /dev/null +++ b/setup.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 +import os +import runpy + +os.chdir("bindings/python") +runpy.run_path("setup.py") diff --git a/simulation/Jamfile b/simulation/Jamfile new file mode 100644 index 0000000..479664c --- /dev/null +++ b/simulation/Jamfile @@ -0,0 +1,68 @@ +import testing ; +import feature : feature ; + +use-project /torrent : .. ; +use-project /libtorrent_test : ../test ; + +use-project /libsimulator : libsimulator ; + +project + : requirements + on + on + /torrent//torrent + /libtorrent_test//libtorrent_test + setup_swarm.cpp + setup_dht.cpp + create_torrent.cpp + utils.cpp + disk_io.cpp + transfer_sim.cpp + msvc:/wd4275 + msvc:/wd4005 + msvc:/wd4268 + : default-build + multi + full + on + on + 14 + built-in + ; + +run test_pause.cpp ; +run test_socks5.cpp ; +run test_checking.cpp ; +run test_optimistic_unchoke.cpp ; +run test_transfer.cpp ; +run test_transfer_no_files.cpp ; +run test_transfer_full_invalid_files.cpp ; +run test_transfer_partial_valid_files.cpp ; +run test_http_connection.cpp ; +run test_web_seed.cpp ; +run test_auto_manage.cpp ; +run test_torrent_status.cpp ; +run test_swarm.cpp ; +run test_session.cpp ; +run test_super_seeding.cpp ; +run test_utp.cpp ; +run test_dht.cpp ; +run test_dht_bootstrap.cpp ; +run test_dht_storage.cpp ; +run test_pe_crypto.cpp ; +run test_metadata_extension.cpp ; +run test_tracker.cpp ; +run test_thread_pool.cpp ; +run test_ip_filter.cpp ; +run test_dht_rate_limit.cpp ; +run test_fast_extensions.cpp ; +run test_v2.cpp ; +# TODO figure out what to do with this +# since v2 support was added re-mapped files are required to be piece aligned +# but test_file_pool requires mapping more files than there are pieces +#run test_file_pool.cpp ; +run test_save_resume.cpp ; +run test_error_handling.cpp ; +run test_timeout.cpp ; +run test_peer_connection.cpp ; + diff --git a/simulation/create_torrent.cpp b/simulation/create_torrent.cpp new file mode 100644 index 0000000..c5ebf67 --- /dev/null +++ b/simulation/create_torrent.cpp @@ -0,0 +1,73 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" // for ::create_torrent +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/create_torrent.hpp" +#include + + +std::string save_path(int idx) +{ + int const swarm_id = unit_test::test_counter(); + char path[200]; + std::snprintf(path, sizeof(path), "swarm-%04d-peer-%02d" + , swarm_id, idx); + return path; +} + +lt::add_torrent_params create_torrent(int const idx, bool const seed + , int const num_pieces, lt::create_flags_t const flags) +{ + // TODO: if we want non-seeding torrents, that could be a bit cheaper to + // create + lt::add_torrent_params params; + int swarm_id = unit_test::test_counter(); + char name[200]; + std::snprintf(name, sizeof(name), "temp-%02d", swarm_id); + std::string path = save_path(idx); + lt::error_code ec; + lt::create_directory(path, ec); + if (ec) std::printf("failed to create directory: \"%s\": %s\n" + , path.c_str(), ec.message().c_str()); + std::ofstream file(lt::combine_path(path, name).c_str()); + params.ti = ::create_torrent(&file, name, 0x4000, num_pieces + idx, false, flags); + file.close(); + + // by setting the save path to a dummy path, it won't be seeding + params.save_path = seed ? path : "dummy"; + return params; +} + + diff --git a/simulation/create_torrent.hpp b/simulation/create_torrent.hpp new file mode 100644 index 0000000..e7f70eb --- /dev/null +++ b/simulation/create_torrent.hpp @@ -0,0 +1,45 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SIM_CREATE_TORRENT_HPP_INCLUDED +#define TORRENT_SIM_CREATE_TORRENT_HPP_INCLUDED + +#include +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/create_torrent.hpp" + +std::string save_path(int idx); +lt::add_torrent_params create_torrent(int idx, bool seed = true + , int num_pieces = 9, lt::create_flags_t flags = {}); + +#endif + diff --git a/simulation/disk_io.cpp b/simulation/disk_io.cpp new file mode 100644 index 0000000..86bb630 --- /dev/null +++ b/simulation/disk_io.cpp @@ -0,0 +1,728 @@ +/* + +Copyright (c) 2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "disk_io.hpp" +#include "utils.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/io_context.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/disk_observer.hpp" +#include "libtorrent/aux_/apply_pad_files.hpp" +#include "libtorrent/random.hpp" + +#include // for exchange() + +namespace { + + // this is posted to the network thread + void watermark_callback(std::vector> const& cbs) + { + for (auto const& i : cbs) + { + std::shared_ptr o = i.lock(); + if (o) o->on_disk(); + } + } + +} // anonymous namespace + +std::array generate_block_fill(lt::piece_index_t const p, int const block) +{ + int const v = (static_cast(p) << 8) | (block & 0xff); + std::array ret; + for (int i = 0; i < 0x4000; i += 4) + { + std::memcpy(ret.data() + i, reinterpret_cast(&v), 4); + } + return ret; +} + +lt::sha1_hash generate_hash1(lt::piece_index_t const p, int const piece_size, int const pad_bytes) +{ + lt::hasher ret; + int const payload_size = piece_size - pad_bytes; + int offset = 0; + for (int block = 0; offset < payload_size; ++block) + { + auto const fill = generate_block_fill(p, block); + for (int i = 0; i < lt::default_block_size;) + { + int const bytes = std::min(int(fill.size()), payload_size - offset); + if (bytes <= 0) break; + ret.update(fill.data(), bytes); + offset += bytes; + i += bytes; + } + } + TORRENT_ASSERT(piece_size - offset == pad_bytes); + std::array const pad{{0, 0, 0, 0, 0, 0, 0, 0}}; + while (offset < piece_size) + { + int const bytes = std::min(int(pad.size()), piece_size - offset); + ret.update(pad.data(), bytes); + offset += bytes; + } + return ret.final(); +} + +lt::sha1_hash generate_hash2(lt::piece_index_t p, lt::file_storage const& fs + , lt::span const hashes, int const pad_bytes) +{ + int const piece_size = fs.piece_size(p); + int const payload_size = piece_size - pad_bytes; + int const piece_size2 = fs.piece_size2(p); + int const blocks_in_piece = (piece_size + lt::default_block_size - 1) / lt::default_block_size; + int const blocks_in_piece2 = fs.blocks_in_piece2(p); + TORRENT_ASSERT(int(hashes.size()) >= blocks_in_piece2); + int const blocks_to_read = std::max(blocks_in_piece, blocks_in_piece2); + + TORRENT_ASSERT(piece_size - pad_bytes == piece_size2); + + lt::hasher ret; + int offset = 0; + for (int block = 0; block < blocks_to_read; ++block) + { + lt::hasher256 v2_hash; + auto const fill = generate_block_fill(p, block); + + bool const v2 = piece_size2 - offset > 0; + + int const block_size = std::min(lt::default_block_size, payload_size - offset); + for (int i = 0; i < block_size;) + { + int const bytes = std::min(int(fill.size()), payload_size - offset); + TORRENT_ASSERT(bytes > 0); + ret.update(fill.data(), bytes); + + if (piece_size2 - offset > 0) + v2_hash.update(fill.data(), std::min(int(fill.size()), piece_size2 - offset)); + + offset += bytes; + i += bytes; + } + if (offset < piece_size) + { + std::vector padding(piece_size - offset, 0); + ret.update(padding); + } + if (v2) + hashes[block] = v2_hash.final(); + } + return ret.final(); +} + +lt::sha256_hash generate_block_hash(lt::piece_index_t p, int const offset) +{ + // TODO: this function is not correct for files whose size are not divisible + // by the block size (for the last block) + lt::hasher256 ret; + int const block = offset / lt::default_block_size; + auto const fill = generate_block_fill(p, block); + for (int i = 0; i < lt::default_block_size; i += fill.size()) + ret.update(fill); + return ret.final(); +} + +void generate_block(char* b, lt::peer_request r, int const pad_bytes, int const piece_size) +{ + // for now we don't support unaligned start address + char* end = b + r.length - pad_bytes; + while (b < end) + { + auto const fill = generate_block_fill(r.piece, (r.start / lt::default_block_size)); + + int const block_offset = r.start % int(fill.size()); + int const bytes = std::min(std::min(int(fill.size() - block_offset), int(end - b)), piece_size - r.start); + std::memcpy(b, fill.data() + block_offset, bytes); + b += bytes; + r.start += bytes; + if (r.start >= piece_size) + { + r.start = 0; + ++r.piece; + } + } + + if (pad_bytes > 0) + std::memset(b, 0, pad_bytes); +} + +std::unordered_map compute_pad_bytes(lt::file_storage const& fs) +{ + std::unordered_map ret; + lt::aux::apply_pad_files(fs, [&](lt::piece_index_t p, int bytes) + { + ret.emplace(p, bytes); + }); + return ret; +} + +int pads_in_piece(std::unordered_map const& pb, lt::piece_index_t const p) +{ + auto it = pb.find(p); + return (it == pb.end()) ? 0 : it->second; +} + +int pads_in_req(std::unordered_map const& pb + , lt::peer_request const& r, int const piece_size) +{ + auto it = pb.find(r.piece); + if (it == pb.end()) return 0; + + int const pad_start = piece_size - it->second; + int const req_end = r.start + r.length; + return std::max(0, std::min(req_end - pad_start, r.length)); +} + +std::shared_ptr create_test_torrent(int const piece_size + , int const num_pieces, lt::create_flags_t const flags, int const num_files) +{ + lt::file_storage ifs; + int total_size = num_files * piece_size * num_pieces + 1234; + if (num_files == 1) + { + ifs.add_file("file-1", total_size); + } + else + { + int const file_size = total_size / num_files + 10; + for (int i = 0; i < num_files; ++i) + { + int const this_size = std::min(file_size, total_size); + ifs.add_file("test-torrent/file-" + std::to_string(i + 1), this_size); + total_size -= this_size; + } + } + lt::create_torrent t(ifs, piece_size, flags); + + lt::file_storage const& fs = t.files(); + + auto const pad_bytes = compute_pad_bytes(fs); + + if (flags & lt::create_torrent::v1_only) + { + for (auto const i : fs.piece_range()) + t.set_hash(i, generate_hash1(i, fs.piece_length(), pads_in_piece(pad_bytes, i))); + } + else + { + int const blocks_per_piece = piece_size / lt::default_block_size; + TORRENT_ASSERT(blocks_per_piece * lt::default_block_size == piece_size); + // blocks per piece must be a power of two + TORRENT_ASSERT((blocks_per_piece & (blocks_per_piece - 1)) == 0); + + lt::aux::vector blocks(blocks_per_piece); + std::vector scratch_space; + + for (auto const f : fs.file_range()) + { + if (fs.pad_file_at(f)) continue; + auto const file_piece = fs.piece_index_at_file(f); + for (auto const p : fs.file_piece_range(f)) + { + auto const piece = file_piece + p; + auto const blocks_in_piece = fs.blocks_in_piece2(piece); + TORRENT_ASSERT(blocks_in_piece > 0); + TORRENT_ASSERT(blocks_in_piece <= int(blocks.size())); + auto const hash = generate_hash2(piece, fs, blocks, pads_in_piece(pad_bytes, piece)); + auto const piece_layer_hash = lt::merkle_root_scratch( + lt::span(blocks).first(blocks_in_piece) + , blocks_per_piece + , {} + , scratch_space); + t.set_hash2(f, p, piece_layer_hash); + + if (!(flags & lt::create_torrent::v2_only)) + t.set_hash(file_piece + p, hash); + } + } + } + + lt::entry tor = t.generate(); + + std::vector tmp; + bencode(std::back_inserter(tmp), tor); + lt::error_code ec; + return std::make_shared(tmp, ec, lt::from_span); +} + +lt::add_torrent_params create_test_torrent( + int const num_pieces, lt::create_flags_t const flags, int const blocks_per_piece + , int const num_files) +{ + lt::add_torrent_params params; + params.ti = ::create_test_torrent(lt::default_block_size * blocks_per_piece, num_pieces, flags, num_files); + // this is unused by the test disk I/O + params.save_path = "."; + return params; +} + +// This is a disk subsystem used for tests (simulations specifically), it: +// +// * supports only a single torrent at a time (to keep it simple) +// * does not support arbitrary data, it generates the data read from it +// according to a specific function. This keeps the memory footprint down even +// for large tests +// * it can simulate delays in reading and writing +// * it can simulate disk full + +struct test_disk_io final : lt::disk_interface + , lt::buffer_allocator_interface +{ + explicit test_disk_io(lt::io_context& ioc, test_disk state) + : m_timer(ioc) + , m_state(state) + , m_ioc(ioc) + {} + + void settings_updated() override {} + + lt::storage_holder new_torrent(lt::storage_params const& params + , std::shared_ptr const&) override + { + TORRENT_ASSERT(m_files == nullptr); + // This test disk I/O system only supports a single torrent + // to keep it simple + lt::file_storage const& fs = params.files; + m_files = &fs; + m_blocks_per_piece = fs.piece_length() / lt::default_block_size; + m_have.resize(m_files->num_pieces() * m_blocks_per_piece, m_state.files == existing_files_mode::full_valid); + m_pad_bytes = compute_pad_bytes(fs); + + if (m_state.files == existing_files_mode::partial_valid) + { + // we have the first half of the blocks + for (std::size_t i = 0; i < m_have.size() / 2u; ++i) + m_have.set_bit(i); + } + + return lt::storage_holder(lt::storage_index_t{0}, *this); + } + + void remove_torrent(lt::storage_index_t const idx) override + { + TORRENT_ASSERT(static_cast(idx) == 0); + TORRENT_ASSERT(m_files != nullptr); + + m_files = nullptr; + m_blocks_per_piece = 0; + m_have.clear(); + } + + void abort(bool) override {} + + void async_read(lt::storage_index_t const storage, lt::peer_request const& r + , std::function h + , lt::disk_job_flags_t) override + { + TORRENT_ASSERT(static_cast(storage) == 0); + TORRENT_ASSERT(m_files); + + // a real diskt I/O implementation would have to support this, but in + // the simulations, we never send unaligned piece requests. + TORRENT_ASSERT((r.start % lt::default_block_size) == 0); + TORRENT_ASSERT((r.length <= lt::default_block_size)); + + auto const seek_time = disk_seek(r.piece, r.start, lt::default_block_size); + + queue_event(seek_time + m_state.read_time, [this,r, h=std::move(h)] () mutable { + lt::disk_buffer_holder buf(*this, new char[lt::default_block_size], r.length); + + if (m_have.get_bit(block_index(r))) + { + if (m_state.corrupt_data_in-- <= 0) + lt::aux::random_bytes(buf); + else + generate_block(buf.data(), r + , pads_in_req(m_pad_bytes, r, m_files->piece_size(r.piece)) + , m_files->piece_size(r.piece)); + } + + post(m_ioc, [h=std::move(h), b=std::move(buf)] () mutable { h(std::move(b), lt::storage_error{}); }); + }); + } + + bool async_write(lt::storage_index_t storage, lt::peer_request const& r + , char const* buf, std::shared_ptr o + , std::function handler + , lt::disk_job_flags_t) override + { + TORRENT_ASSERT(m_files); + + if (m_state.space_left < lt::default_block_size) + { + queue_event(lt::milliseconds(1), [this,h=std::move(handler)] () mutable { + post(m_ioc, [h=std::move(h)] + { + h(lt::storage_error(lt::error_code(boost::system::errc::no_space_on_device, lt::generic_category()) + , lt::operation_t::file_write)); + }); + if (m_state.recover_full_disk) + m_state.space_left = std::numeric_limits::max(); + }); + + if (m_write_queue > m_state.high_watermark || m_exceeded_max_size) + { + m_observers.push_back(std::move(o)); + return true; + } + return false; + } + + bool const valid = validate_block(*m_files, buf, r); + + auto const seek_time = disk_seek(r.piece, r.start, lt::default_block_size); + + queue_event(seek_time + m_state.write_time, [this,valid,r,h=std::move(handler)] () mutable { + + if (valid) + { + m_have.set_bit(block_index(r)); + m_state.space_left -= lt::default_block_size; + } + + post(m_ioc, [h=std::move(h)]{ h(lt::storage_error()); }); + + TORRENT_ASSERT(m_write_queue > 0); + --m_write_queue; + check_buffer_level(); + }); + + m_write_queue += 1; + if (m_write_queue > m_state.high_watermark || m_exceeded_max_size) + { + m_exceeded_max_size = true; + m_observers.push_back(std::move(o)); + return true; + } + + return false; + } + + void async_hash(lt::storage_index_t storage, lt::piece_index_t const piece + , lt::span block_hashes, lt::disk_job_flags_t + , std::function handler) override + { + TORRENT_ASSERT(m_files); + + auto const seek_time = disk_seek(piece, 0, m_blocks_per_piece * lt::default_block_size); + + auto const delay = seek_time + + m_state.read_time * m_blocks_per_piece + + m_state.hash_time * m_blocks_per_piece + + m_state.hash_time * block_hashes.size(); + + queue_event(delay, [this, piece, block_hashes, h=std::move(handler)] () mutable { + + int const piece_size = m_files->piece_size(piece); + int const pad_bytes = pads_in_piece(m_pad_bytes, piece); + int const payload_blocks = piece_size / lt::default_block_size - pad_bytes / lt::default_block_size; + int const block_idx = static_cast(piece) * m_blocks_per_piece; + for (int i = 0; i < payload_blocks; ++i) + { + if (m_have.get_bit(block_idx + i)) + continue; + + lt::sha1_hash ph{}; + if (m_state.files == existing_files_mode::full_invalid) + { + for (auto& h : block_hashes) + h = rand_sha256(); + ph = rand_sha1(); + } + // If we're missing a block, return an invalid hash + post(m_ioc, [h=std::move(h), piece, ph]{ h(piece, ph, lt::storage_error{}); }); + return; + } + + lt::sha1_hash hash; + if (block_hashes.empty()) + hash = generate_hash1(piece, m_files->piece_length(), pads_in_piece(m_pad_bytes, piece)); + else + hash = generate_hash2(piece, *m_files, block_hashes, pads_in_piece(m_pad_bytes, piece)); + post(m_ioc, [h=std::move(h), piece, hash]{ h(piece, hash, lt::storage_error{}); }); + }); + } + + void async_hash2(lt::storage_index_t storage, lt::piece_index_t const piece + , int const offset, lt::disk_job_flags_t + , std::function handler) override + { + TORRENT_ASSERT(m_files); + + auto const seek_time = disk_seek(piece, offset, m_blocks_per_piece * lt::default_block_size); + + auto const delay = seek_time + m_state.hash_time + m_state.read_time; + queue_event(delay, [this, piece, offset, h=std::move(handler)] () mutable { + int const block_idx = static_cast(piece) * m_blocks_per_piece + + offset / lt::default_block_size; + lt::sha256_hash hash; + if (!m_have.get_bit(block_idx)) + { + if (m_state.files == existing_files_mode::full_invalid) + hash = rand_sha256(); + } + else + { + hash = generate_block_hash(piece, offset); + } + post(m_ioc, [h=std::move(h),piece, hash] { h(piece, hash, lt::storage_error{}); }); + }); + } + + void async_move_storage(lt::storage_index_t, std::string p, lt::move_flags_t + , std::function handler) override + { + TORRENT_ASSERT(m_files); + post(m_ioc, [=]{ + handler(lt::status_t::fatal_disk_error, p + , lt::storage_error(lt::error_code(boost::system::errc::operation_not_supported, lt::system_category()))); + }); + } + + void async_release_files(lt::storage_index_t, std::function f) override + { + TORRENT_ASSERT(m_files); + queue_event(lt::microseconds(1), std::move(f)); + } + + void async_delete_files(lt::storage_index_t, lt::remove_flags_t + , std::function handler) override + { + TORRENT_ASSERT(m_files); + + queue_event(lt::microseconds(1), [this,h=std::move(handler)] () mutable { + m_have.clear_all(); + post(m_ioc, [h=std::move(h)]{ h(lt::storage_error()); }); + }); + } + + void async_check_files(lt::storage_index_t + , lt::add_torrent_params const* p + , lt::aux::vector + , std::function handler) override + { + TORRENT_ASSERT(m_files); + + auto ret = lt::status_t::need_full_check; + if (p && p->flags & lt::torrent_flags::seed_mode) + ret = lt::status_t::no_error; + else if (m_state.files == existing_files_mode::no_files) + ret = lt::status_t::no_error; + + if (p && lt::aux::contains_resume_data(*p)) + ret = lt::status_t::no_error; + + queue_event(lt::microseconds(1), [this,ret,h=std::move(handler)] () mutable { + post(m_ioc, [ret,h=std::move(h)] { h(ret, lt::storage_error()); }); + }); + } + + void async_rename_file(lt::storage_index_t + , lt::file_index_t const idx + , std::string const name + , std::function handler) override + { + TORRENT_ASSERT(m_files); + post(m_ioc, [=]{ handler(name, idx, lt::storage_error()); }); + } + + void async_stop_torrent(lt::storage_index_t, std::function handler) override + { + TORRENT_ASSERT(m_files); + post(m_ioc, handler); + } + + void async_set_file_priority(lt::storage_index_t + , lt::aux::vector prio + , std::function)> handler) override + { + TORRENT_ASSERT(m_files); + post(m_ioc, [=]{ + handler(lt::storage_error(lt::error_code( + boost::system::errc::operation_not_supported, lt::system_category())), std::move(prio)); + }); + } + + void async_clear_piece(lt::storage_index_t, lt::piece_index_t index + , std::function handler) override + { + TORRENT_ASSERT(m_files); + post(m_ioc, [=]{ handler(index); }); + } + + // implements buffer_allocator_interface + void free_disk_buffer(char* buf) override + { + delete[] buf; + } + + void update_stats_counters(lt::counters&) const override {} + + std::vector get_status(lt::storage_index_t) const override + { return {}; } + + void submit_jobs() override {} + +private: + + lt::time_duration disk_seek(lt::piece_index_t const piece, int start, int const size = lt::default_block_size) + { + std::int64_t const offset = std::int64_t(static_cast(piece)) * m_files->piece_length() + start; + return (std::exchange(m_last_disk_offset, offset + size) == offset) + ? lt::milliseconds(0) : m_state.seek_time; + } + + int block_index(lt::peer_request const& r) const + { + return static_cast(r.piece) * m_blocks_per_piece + r.start / lt::default_block_size; + } + + bool validate_block(lt::file_storage const& fs, char const* b, lt::peer_request const& r) const + { + auto const fill = generate_block_fill(r.piece, r.start / lt::default_block_size); + int const piece_size = fs.piece_size(r.piece); + int payload_bytes = (piece_size - pads_in_piece(m_pad_bytes, r.piece)) - r.start; + int offset = 0; + while (offset < r.length && payload_bytes > 0) + { + int const to_compare = std::min(payload_bytes, int(fill.size())); + if (std::memcmp(b, fill.data(), to_compare) != 0) return false; + b += to_compare; + offset += to_compare; + payload_bytes -= to_compare; + } + if (offset < r.length) + { + // the pad bytes must be zero + return std::all_of(b, b + r.length - offset, [](char const c) { return c == 0; }); + } + return true; + } + + void queue_event(lt::time_duration dt, std::function f) + { + if (m_event_queue.empty()) + { + m_event_queue.push_back({lt::clock_type::now() + dt, std::move(f)}); + m_timer.expires_after(dt); + using namespace std::placeholders; + m_timer.async_wait(std::bind(&test_disk_io::on_timer, this, _1)); + } + else + { + m_event_queue.push_back({m_event_queue.back().first + dt, std::move(f)}); + } + } + + void on_timer(lt::error_code const&) + { + if (m_event_queue.empty()) + return; + + { + auto f = std::move(m_event_queue.front().second); + m_event_queue.pop_front(); + if (f) f(); + } + + if (m_event_queue.empty()) + return; + + m_timer.expires_at(m_event_queue.back().first); + using namespace std::placeholders; + m_timer.async_wait(std::bind(&test_disk_io::on_timer, this, _1)); + } + + void check_buffer_level() + { + if (!m_exceeded_max_size || m_write_queue > m_state.low_watermark) return; + + m_exceeded_max_size = false; + + std::vector> cbs; + m_observers.swap(cbs); + post(m_ioc, std::bind(&watermark_callback, std::move(cbs))); + } + + std::vector> m_observers; + int m_write_queue = 0; + bool m_exceeded_max_size = false; + + // events that are supposed to trigger in the future are put in this queue + std::deque>> m_event_queue; + lt::deadline_timer m_timer; + + // the last read or write operation pushed at the end of the event queue. If + // the disk operation that's about to be pushed is immediately following + // this one, there is no seek delay + std::int64_t m_last_disk_offset = 0; + + test_disk m_state; + + // we only support a single torrent. This is set if it has been added + lt::file_storage const* m_files = nullptr; + + // marks blocks as they are written (as long as the correct block is written) + // when computing the hash of a piece where not all blocks are written, will + // fail + lt::bitfield m_have; + + int m_blocks_per_piece; + + // callbacks are posted on this + lt::io_context& m_ioc; + + std::unordered_map m_pad_bytes; +}; + +std::unique_ptr test_disk::operator()( + lt::io_context& ioc, lt::settings_interface const&, lt::counters&) +{ + return std::make_unique(ioc, *this); +} + + +std::ostream& operator<<(std::ostream& os, existing_files_mode const mode) +{ + switch (mode) + { + case existing_files_mode::no_files: return os << "no_files"; + case existing_files_mode::full_invalid: return os << "full_invalid"; + case existing_files_mode::partial_valid: return os << "partial_valid"; + case existing_files_mode::full_valid: return os << "full_valid"; + } + return os << ""; +} diff --git a/simulation/disk_io.hpp b/simulation/disk_io.hpp new file mode 100644 index 0000000..9e37d13 --- /dev/null +++ b/simulation/disk_io.hpp @@ -0,0 +1,131 @@ +/* + +Copyright (c) 2021, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SIMULATION_DISK_IO_HPP +#define SIMULATION_DISK_IO_HPP + +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/aux_/merkle.hpp" +#include "libtorrent/io_context.hpp" +#include "test_utils.hpp" + +#include +#include + +std::array generate_block_fill(lt::piece_index_t const p, int const block); +lt::sha1_hash generate_hash1(lt::piece_index_t const p, lt::file_storage const& fs); +lt::sha1_hash generate_hash2(lt::piece_index_t p, lt::file_storage const& fs + , lt::span const hashes); +lt::sha256_hash generate_block_hash(lt::piece_index_t p, int offset); +void generate_block(char* b, lt::peer_request r, int pad_bytes, int piece_size); +std::shared_ptr create_test_torrent(int piece_size + , int num_pieces, lt::create_flags_t flags, int num_files = 1); +lt::add_torrent_params create_test_torrent( + int num_pieces, lt::create_flags_t flags, int blocks_per_piece, int num_files = 1); + +enum class existing_files_mode : std::uint8_t +{ + no_files, full_invalid, partial_valid, full_valid +}; + +struct test_disk +{ + test_disk set_seed(bool const s = true) const + { + return set_files(s ? existing_files_mode::full_valid : existing_files_mode::no_files); + } + test_disk set_files(existing_files_mode const s) const + { + auto ret = *this; + ret.files = s; + return ret; + } + test_disk set_space_left(int const left) const + { + auto ret = *this; + ret.space_left = left; + return ret; + } + test_disk set_recover_full_disk() const + { + auto ret = *this; + ret.recover_full_disk = true; + return ret; + } + test_disk send_corrupt_data(int const blocks) const + { + auto ret = *this; + ret.corrupt_data_in = blocks; + return ret; + } + + // the number of blocks/write jobs in the queue before we exceed the write + // queue size. Once the level drops below the low watermark, we allow writes + // again + int high_watermark = 50; + int low_watermark = 40; + + std::unique_ptr operator()( + lt::io_context& ioc, lt::settings_interface const&, lt::counters&); + + // seek time in front of every read and write + lt::time_duration seek_time = lt::milliseconds(10); + + // hash time per block + lt::time_duration hash_time = lt::microseconds(15); + + // write time per block + lt::time_duration write_time = lt::microseconds(2); + + // read time per block + lt::time_duration read_time = lt::microseconds(1); + + // when checking files, say we have some files on disk already (but not with + // valid data) + existing_files_mode files = existing_files_mode::no_files; + + // after having failed with disk-full error, reset space_left to int_max + bool recover_full_disk = false; + + // after sending this many blocks, send corrupt data + int corrupt_data_in = std::numeric_limits::max(); + + // after having written this many bytes, fail with disk-full + int space_left = std::numeric_limits::max(); +}; + +std::ostream& operator<<(std::ostream& os, existing_files_mode const mode); + +#endif diff --git a/simulation/fake_peer.hpp b/simulation/fake_peer.hpp new file mode 100644 index 0000000..981460b --- /dev/null +++ b/simulation/fake_peer.hpp @@ -0,0 +1,473 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SIMULATION_FAKE_PEER_HPP +#define SIMULATION_FAKE_PEER_HPP + +#include +#include // for snprintf + +#include +#include "test.hpp" +#include "simulator/simulator.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/random.hpp" + +using namespace sim; + + +struct fake_peer +{ + fake_peer(simulation& sim, char const* ip) + : m_ioc(sim, asio::ip::make_address(ip)) + { + boost::system::error_code ec; + m_acceptor.open(asio::ip::tcp::v4(), ec); + TEST_CHECK(!ec); + m_acceptor.bind(asio::ip::tcp::endpoint(asio::ip::address_v4::any(), 6881), ec); + TEST_CHECK(!ec); + m_acceptor.listen(10, ec); + TEST_CHECK(!ec); + + m_acceptor.async_accept(m_socket, [&] (boost::system::error_code const& ec) + { + using namespace std::placeholders; + if (ec) return; + + asio::async_read(m_socket, asio::buffer(m_out_buffer.data(), 68) + , std::bind(&fake_peer::read_handshake, this, _1, _2)); + + m_accepted = true; + }); + } + + void close() + { + m_acceptor.close(); + m_socket.close(); + } + + void connect_to(asio::ip::tcp::endpoint ep, lt::sha1_hash const& ih) + { + using namespace std::placeholders; + + m_info_hash = ih; + + std::printf("fake_peer::connect_to(%s)\n", lt::print_endpoint(ep).c_str()); + m_socket.async_connect(ep, std::bind(&fake_peer::write_handshake + , this, _1, ih)); + } + + bool accepted() const { return m_accepted; } + bool connected() const { return m_connected; } + bool disconnected() const { return m_disconnected; } + + void send_request(lt::piece_index_t p, int block) + { + int const len = 4 + 1 + 4 * 3; + m_send_buffer.resize(m_send_buffer.size() + len); + char* ptr = m_send_buffer.data() + m_send_buffer.size() - len; + + lt::aux::write_uint32(len - 4, ptr); + lt::aux::write_uint8(6, ptr); + lt::aux::write_uint32(static_cast(p), ptr); + lt::aux::write_uint32(block * 0x4000, ptr); + lt::aux::write_uint32(0x4000, ptr); + } + + void send_keepalive() + { + int const len = 4; + m_send_buffer.resize(m_send_buffer.size() + len); + char* ptr = m_send_buffer.data() + m_send_buffer.size() - len; + + lt::aux::write_uint32(0, ptr); + } + + void send_bitfield(std::vector const& pieces) + { + int const bytes = (int(pieces.size()) + 7) / 8; + m_send_buffer.resize(m_send_buffer.size() + 5 + bytes); + char* ptr = m_send_buffer.data() + m_send_buffer.size() - 5 - bytes; + + lt::aux::write_uint32(1 + bytes, ptr); + lt::aux::write_uint8(5, ptr); + + std::uint8_t b = 0; + int cnt = 7; + for (auto i = pieces.begin(), end(pieces.end()); i != end; ++i) + { + if (*i) b |= 1 << cnt; + --cnt; + if (cnt < 0) + { + lt::aux::write_uint8(b, ptr); + b = 0; + cnt = 7; + } + } + if (cnt < 7) + lt::aux::write_uint8(b, ptr); + } + + void send_interested() { send_simple_msg(2); } + void send_not_interested() { send_simple_msg(3); } + void send_have_all() { send_simple_msg(0xe); } + void send_have_none() { send_simple_msg(0xf); } + void send_invalid_message() { send_simple_msg(0xff); } + void send_large_message() + { + m_send_buffer.resize(m_send_buffer.size() + 5); + char* ptr = m_send_buffer.data() + m_send_buffer.size() - 5; + + lt::aux::write_uint32(2000000, ptr); + lt::aux::write_uint8(0, ptr); + } + + void flush_send_buffer() + { + TORRENT_ASSERT(!m_writing); + m_writing = true; + asio::async_write(m_socket,asio::buffer(m_send_buffer) + , [this](boost::system::error_code const& ec , size_t len) + { + TORRENT_ASSERT(m_writing); + m_writing = false; + m_send_buffer.clear(); + }); + } + +private: + + void send_simple_msg(std::uint8_t const msg_code) + { + m_send_buffer.resize(m_send_buffer.size() + 5); + char* ptr = m_send_buffer.data() + m_send_buffer.size() - 5; + + lt::aux::write_uint32(1, ptr); + lt::aux::write_uint8(msg_code, ptr); + } + + void write_handshake(boost::system::error_code const& ec + , lt::sha1_hash ih) + { + using namespace std::placeholders; + + asio::ip::tcp::endpoint const ep = m_socket.remote_endpoint(); + std::printf("fake_peer::connect(%s) -> (%d) %s\n" + , lt::print_endpoint(ep).c_str() + , ec.value(), ec.message().c_str()); + if (ec) return; + + static char const handshake[] + = "\x13" "BitTorrent protocol\0\0\0\0\0\0\0\x04" + " " // space for info-hash + "aaaaaaaaaaaaaaaaaaaa"; // peer-id + int const len = sizeof(handshake) - 1; + memcpy(m_out_buffer.data(), handshake, len); + memcpy(&m_out_buffer[28], ih.data(), 20); + lt::aux::random_bytes({&m_out_buffer[48], 20}); + + TORRENT_ASSERT(!m_writing); + m_writing = true; + asio::async_write(m_socket, asio::buffer(m_out_buffer.data(), len) + , [this, ep](boost::system::error_code const& ec + , size_t /* bytes_transferred */) + { + TORRENT_ASSERT(m_writing); + m_writing = false; + std::printf("fake_peer::write_handshake(%s) -> (%d) %s\n" + , lt::print_endpoint(ep).c_str(), ec.value() + , ec.message().c_str()); + if (!m_send_buffer.empty()) + { + TORRENT_ASSERT(!m_writing); + m_writing = true; + asio::async_write(m_socket, asio::buffer(m_send_buffer) + , std::bind(&fake_peer::write_send_buffer, this, _1, _2)); + } + else + { + asio::async_read(m_socket, asio::buffer(m_out_buffer.data(), 68) + , std::bind(&fake_peer::read_handshake, this, _1, _2)); + } + }); + } + + void read_handshake(lt::error_code const& ec, size_t /* bytes_transferred */) + { + using namespace std::placeholders; + + std::printf("fake_peer::read_handshake -> (%d) %s\n" + , ec.value(), ec.message().c_str()); + if (ec) + { + m_socket.close(); + return; + } + + if (memcmp(m_out_buffer.data(), "\x13" "BitTorrent protocol", 20) != 0) + { + std::printf(" invalid protocol specifier\n"); + m_socket.close(); + return; + } + + // if this peer accepted an incoming connection, we don't know what the + // info hash is supposed to be + if (!m_info_hash.is_all_zeros() + && memcmp(&m_out_buffer[28], m_info_hash.data(), 20) != 0) + { + std::printf(" invalid info hash\n"); + m_socket.close(); + return; + } + + m_connected = true; + + // keep reading until we receie EOF, then set m_disconnected = true + m_socket.async_read_some(asio::buffer(m_out_buffer) + , std::bind(&fake_peer::on_read, this, _1, _2)); + } + + void on_read(lt::error_code const& ec, size_t bytes_transferred) + { + using namespace std::placeholders; + + std::printf("fake_peer::on_read(%d bytes) -> (%d) %s\n" + , int(bytes_transferred), ec.value(), ec.message().c_str()); + if (ec) + { + std::printf(" closing\n"); + m_disconnected = true; + m_socket.close(); + return; + } + + m_socket.async_read_some(asio::buffer(m_out_buffer) + , std::bind(&fake_peer::on_read, this, _1, _2)); + } + + void write_send_buffer(boost::system::error_code const& ec + , size_t /* bytes_transferred */) + { + using namespace std::placeholders; + + printf("fake_peer::write_send_buffer() -> (%d) %s\n" + , ec.value(), ec.message().c_str()); + + TORRENT_ASSERT(m_writing); + m_writing = false; + + m_send_buffer.clear(); + asio::async_read(m_socket, asio::buffer(m_out_buffer.data(), 68) + , std::bind(&fake_peer::read_handshake, this, _1, _2)); + } + + std::array m_out_buffer; + + asio::io_context m_ioc; + asio::ip::tcp::acceptor m_acceptor{m_ioc}; + asio::ip::tcp::socket m_socket{m_ioc}; + lt::sha1_hash m_info_hash; + + // set to true if this peer received an incoming connection + // if this is an outgoing connection, this will always be false + bool m_accepted = false; + + // set to true if this peer completed a bittorrent handshake + bool m_connected = false; + + // set to true if this peer has been disconnected by the other end + bool m_disconnected = false; + + // set to true while there's an outstanding asyn write operation on the + // socket + bool m_writing = false; + + std::vector m_send_buffer; +}; + +inline void add_fake_peer(lt::torrent_handle& h, int const i) +{ + char ep[30]; + std::snprintf(ep, sizeof(ep), "60.0.0.%d", i); + h.connect_peer(lt::tcp::endpoint( + asio::ip::make_address_v4(ep), 6881)); +} + +inline void add_fake_peers(lt::torrent_handle& h, int const n = 5) +{ + // add the fake peers + for (int i = 0; i < n; ++i) + { + add_fake_peer(h, i); + } +} + +struct udp_server +{ + udp_server(simulation& sim, char const* ip, int port + , std::function(char const*, int)> handler) + : m_ioc(sim, asio::ip::make_address(ip)) + , m_handler(handler) + { + boost::system::error_code ec; + m_socket.open(asio::ip::udp::v4(), ec); + TEST_CHECK(!ec); + m_socket.bind(asio::ip::udp::endpoint(asio::ip::address_v4::any() + , static_cast(port)), ec); + TEST_CHECK(!ec); + + m_socket.non_blocking(true); + + std::printf("udp_server::async_read_some\n"); + using namespace std::placeholders; + m_socket.async_receive_from(boost::asio::buffer(m_in_buffer) + , m_from, 0, std::bind(&udp_server::on_read, this, _1, _2)); + } + + void close() { m_socket.close(); } + +private: + + void on_read(boost::system::error_code const& ec, size_t bytes_transferred) + { + std::printf("udp_server::async_read_some callback. ec: %s transferred: %d\n" + , ec.message().c_str(), int(bytes_transferred)); + if (ec) return; + + std::vector send_buffer = m_handler(m_in_buffer.data(), int(bytes_transferred)); + + if (!send_buffer.empty()) + { + lt::error_code err; + m_socket.send_to(boost::asio::buffer(send_buffer), m_from, 0, err); + if (err) + { + std::printf("send_to FAILED: %s\n", err.message().c_str()); + } + else + { + std::printf("udp_server responding with %d bytes\n" + , int(send_buffer.size())); + } + } + + std::printf("udp_server::async_read_some\n"); + using namespace std::placeholders; + m_socket.async_receive_from(boost::asio::buffer(m_in_buffer) + , m_from, 0, std::bind(&udp_server::on_read, this, _1, _2)); + } + + std::array m_in_buffer; + + asio::io_context m_ioc; + asio::ip::udp::socket m_socket{m_ioc}; + asio::ip::udp::endpoint m_from; + + std::function(char const*, int)> m_handler; +}; + +struct fake_node : udp_server +{ + fake_node(simulation& sim, char const* ip, int port = 6881) + : udp_server(sim, ip, port, [&](char const* incoming, int size) + { + lt::bdecode_node n; + boost::system::error_code err; + int const ret = bdecode(incoming, incoming + size, n, err, nullptr, 10, 200); + TEST_EQUAL(ret, 0); + + m_incoming_packets.emplace_back(incoming, incoming + size); + + // TODO: ideally we would validate the DHT message + m_tripped = true; + return std::vector(); + }) + {} + + bool tripped() const { return m_tripped; } + + std::vector> const& incoming_packets() const + { return m_incoming_packets; } + +private: + + std::vector> m_incoming_packets; + bool m_tripped = false; +}; + +template +void check_accepted(std::array& test_peers + , std::array expected) +{ + int idx = 0; + for (auto p : test_peers) + { + TEST_EQUAL(p->accepted(), expected[idx]); + ++idx; + } +} + +template +void check_connected(std::array& test_peers + , std::array expected) +{ + int idx = 0; + for (auto p : test_peers) + { + TEST_EQUAL(p->connected(), expected[idx]); + ++idx; + } +} + +template +void check_disconnected(std::array& test_peers + , std::array expected) +{ + int idx = 0; + for (auto p : test_peers) + { + TEST_EQUAL(p->disconnected(), expected[idx]); + ++idx; + } +} + +#endif + diff --git a/simulation/libsimulator/.travis.yml b/simulation/libsimulator/.travis.yml new file mode 100644 index 0000000..1c02ca3 --- /dev/null +++ b/simulation/libsimulator/.travis.yml @@ -0,0 +1,65 @@ +language: cpp +dist: bionic + +os: + - linux + - osx + +env: + - variant=debug + - variant=release + +branches: + only: + - master + +# container-based builds +sudo: false +cache: + directories: + - $HOME/.ccache + +# sources list: https://github.com/travis-ci/apt-source-whitelist/blob/master/ubuntu.json, +# packages list: https://github.com/travis-ci/apt-package-whitelist/blob/master/ubuntu-precise +addons: + apt: + sources: + - sourceline: ppa:mhier/libboost-latest + - ubuntu-toolchain-r-test + packages: + - libboost1.68-dev + - g++-8 + - cmake + +install: + + - 'if [[ $TRAVIS_OS_NAME != "osx" ]]; then + export B2=bjam; + else + export B2=b2; + fi' + + # on linux, pull down a recent version of boost and build boost-build + - 'if [[ $TRAVIS_OS_NAME == "linux" ]]; then + cd ..; + wget https://dl.bintray.com/boostorg/release/1.68.0/source/boost_1_68_0.zip; + unzip -qq boost_1_68_0.zip; + export BOOST_ROOT=$PWD/boost_1_68_0; + (cd boost_1_68_0/tools/build/src/engine; ./build.sh); + export BOOST_BUILD_PATH=$BOOST_ROOT/tools/build; + export PATH=$BOOST_BUILD_PATH/src/engine/bin.linuxx86_64:$PATH; + cd libsimulator; + fi' + + - 'if [ $TRAVIS_OS_NAME == "linux" ]; then echo "using gcc : : ccache g++-8 ;" > ~/user-config.jam; export CXX="g++-8" ; fi' + - 'if [ $TRAVIS_OS_NAME == "osx" ]; then echo "using darwin : : ccache clang++ : -std=c++14 -Wno-deprecated-declarations ;" > ~/user-config.jam; fi' + - if [ $TRAVIS_OS_NAME == "osx" ]; then brew update > /dev/null && brew install ccache boost-build && brew upgrade cmake ; fi + - ccache -V && ccache --show-stats && ccache --zero-stats + +script: +# until we have a version of boost-build that's new enough, disable warnings +# as errors + - ${B2} --hash -j3 variant=$variant -l900 warnings-as-errors=off + - mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=$variant .. && make -j3 && make test + - ccache --show-stats + diff --git a/simulation/libsimulator/CMakeLists.txt b/simulation/libsimulator/CMakeLists.txt new file mode 100644 index 0000000..8ce97ca --- /dev/null +++ b/simulation/libsimulator/CMakeLists.txt @@ -0,0 +1,57 @@ +project(libsimulator) +cmake_minimum_required(VERSION 2.8.7) + +set(SRC_DIR src) +set(TEST_SRC_DIR test) +set(INCLUDE_DIR include) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Boost COMPONENTS system REQUIRED) +find_package(Threads REQUIRED) + +set(SIMULATOR_SRC_FILES + ${SRC_DIR}/acceptor.cpp + ${SRC_DIR}/high_resolution_timer.cpp + ${SRC_DIR}/io_service.cpp + ${SRC_DIR}/resolver.cpp + ${SRC_DIR}/sink_forwarder.cpp + ${SRC_DIR}/udp_socket.cpp + ${SRC_DIR}/default_config.cpp + ${SRC_DIR}/http_proxy.cpp + ${SRC_DIR}/pcap.cpp + ${SRC_DIR}/simulation.cpp + ${SRC_DIR}/socks_server.cpp + ${SRC_DIR}/high_resolution_clock.cpp + ${SRC_DIR}/http_server.cpp + ${SRC_DIR}/queue.cpp + ${SRC_DIR}/simulator.cpp + ${SRC_DIR}/tcp_socket.cpp + ${SRC_DIR}/nat.cpp +) + +add_library(simulator ${SIMULATOR_SRC_FILES}) +include_directories(${Boost_INCLUDE_DIR} ${INCLUDE_DIR}) + +if(WIN32) + target_link_libraries(simulator PRIVATE ws2_32) +endif() + +enable_testing() + +function(define_test) + set(NAME ${ARGV0}) + add_executable(${NAME} ${TEST_SRC_DIR}/${NAME}.cpp ${TEST_SRC_DIR}/main.cpp) + target_link_libraries(${NAME} simulator ${Boost_LIBRARIES}) + target_link_libraries(${NAME} Threads::Threads) + add_test(NAME ${NAME} COMMAND ${NAME}) +endfunction() + +define_test(acceptor) +define_test(multi_accept) +define_test(multi_homed) +define_test(null_buffers) +define_test(parse_request) +define_test(resolver) +define_test(timer) +define_test(udp_socket) diff --git a/simulation/libsimulator/Jamfile b/simulation/libsimulator/Jamfile new file mode 100644 index 0000000..373a463 --- /dev/null +++ b/simulation/libsimulator/Jamfile @@ -0,0 +1,129 @@ +# This Jamfile requires boost-build v2 to build. + +import path ; +import modules ; +import os ; +import testing ; + +BOOST_ROOT = [ modules.peek : BOOST_ROOT ] ; + +ECHO "BOOST_ROOT =" $(BOOST_ROOT) ; +ECHO "OS =" [ os.name ] ; + +lib wsock32 : : wsock32 shared ; +lib ws2_32 : : ws2_32 shared ; + +if $(BOOST_ROOT) +{ + use-project /boost : $(BOOST_ROOT) ; + alias boost_system : /boost/system//boost_system ; +} +else +{ + local boost-lib-search-path = + /usr/local/opt/boost/lib + /opt/homebrew/lib + ; + + local boost-include-path = + /usr/local/opt/boost/include + /opt/homebrew/include + ; + + lib boost_system : : boost_system $(boost-lib-search-path) + : : $(boost-include-path) ; +} + +SOURCES = + simulator + simulation + io_service + high_resolution_timer + high_resolution_clock + tcp_socket + udp_socket + queue + acceptor + default_config + http_server + socks_server + resolver + http_proxy + sink_forwarder + pcap + nat + ; + +lib simulator + : # sources + src/$(SOURCES).cpp + + : # requirements + include + boost_system + windows:ws2_32 + windows:wsock32 + multi + + shared:SIMULATOR_BUILDING_SHARED + _CRT_SECURE_NO_WARNINGS + + # https://github.com/chriskohlhoff/asio/issues/290#issuecomment-377727614 + _SILENCE_CXX17_ALLOCATOR_VOID_DEPRECATION_WARNING + + BOOST_ASIO_DISABLE_BOOST_DATE_TIME + BOOST_ASIO_HAS_MOVE + BOOST_ASIO_ENABLE_CANCELIO + + # make sure asio uses std::chrono + BOOST_ASIO_HAS_STD_CHRONO + + # disable auto-link + BOOST_ALL_NO_LIB + + : # default build + all + on + # boost.asio has a global tss_ptr which is the head of a + # linked list of all invocations of run on that thread. This + # determines whether dispatch() will run the handler immediately + # not. The simulator relies on this. On windows each DLL will have + # its own copy of this variable, effectively causing a deadlock + # in the simulator. So, default to static linking + static + + : # usage requirements + BOOST_ASIO_DISABLE_BOOST_DATE_TIME + BOOST_ASIO_HAS_MOVE + BOOST_ASIO_ENABLE_CANCELIO + multi + include + shared:SIMULATOR_LINKING_SHARED + + # https://github.com/chriskohlhoff/asio/issues/290#issuecomment-377727614 + _SILENCE_CXX17_ALLOCATOR_VOID_DEPRECATION_WARNING + ; + +project + : requirements + simulator + # disable auto-link + BOOST_ALL_NO_LIB + : default-build + static + multi + 14 + ; + +test-suite simulator-tests : [ run + test/main.cpp + test/resolver.cpp + test/multi_homed.cpp + test/timer.cpp + test/acceptor.cpp + test/multi_accept.cpp + test/null_buffers.cpp + test/udp_socket.cpp + test/parse_request.cpp + ] ; + diff --git a/simulation/libsimulator/Jamroot.jam b/simulation/libsimulator/Jamroot.jam new file mode 100644 index 0000000..e69de29 diff --git a/simulation/libsimulator/LICENSE b/simulation/libsimulator/LICENSE new file mode 100644 index 0000000..733c072 --- /dev/null +++ b/simulation/libsimulator/LICENSE @@ -0,0 +1,675 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + diff --git a/simulation/libsimulator/README.rst b/simulation/libsimulator/README.rst new file mode 100644 index 0000000..6b9379d --- /dev/null +++ b/simulation/libsimulator/README.rst @@ -0,0 +1,202 @@ +libsimulator +============ + +.. image:: https://travis-ci.org/arvidn/libsimulator.svg?branch=master + :target: https://travis-ci.org/arvidn/libsimulator + +.. image:: https://ci.appveyor.com/api/projects/status/0857n4g3f6mui90i/branch/master + :target: https://ci.appveyor.com/project/arvidn/libsimulator/branch/master + +*This is still in initial development, some of this README represents ambitions +rather than the current state* + +libsimulator is a library for running discrete event simulations, implementing +the ``boost.asio`` API (or a somewhat faithful emulation of a subset of it, +patches are welcome). This makes it practical to be used as a testing tool of +real implementations of network software as well as for writing simulators that +later turn into live production applications. + +The simulation has to have a single time-line to be deterministic, meaning it +must be single threaded and use a single ``io_service`` as the message queue. +These requirements may affect how the program to be tested is written. It may +for instance require that an external io_service can be provided rather than one +being wrapped in an internal thread. + +However, ``boost.asio`` programs may generally benefit from being transformed to +this form, as the become *composable*, i.e. agnostic to which io_service they +run on or how many threads are running it. + +features +-------- + +The currently (partially) supported classes are: + +* chrono::high_resolution_clock +* asio::high_resolution_timer +* asio::ip::tcp::acceptor +* asio::ip::tcp::endpoint +* asio::ip::address (v4 and v6 variants, these just defer to the actual + boost.asio types) +* asio::ip::tcp::socket +* asio::ip::udp::socket +* asio::io_service +* asio::ip::udp::resolver +* asio::ip::tcp::resolver + +The ``high_resolution_clock`` in the ``chrono`` namespace implements the timer +concept from the chrono library. + +usage +----- + +The ``io_service`` object is significantly different from the one in boost.asio. +This is because one simulation may only have a single message loop and a single +ordering of events. This single message loop is provided by the ``simulation`` +class. Each simulation should have only one such object. An ``io_service`` +object represents a single node on the network. When creating an io_service, you +have to pass in the simulation it belongs to as well as the IP address it should +have. It is also possible to pass in multiple addresses to form a multi-homed +node. For instance, one with both an IPv4 and IPv6 interface. + +When creating sockets, binding and connecting them, the io_service object +determines what ``INADDR_ANY`` resolves to (the first IP assigned to that node). + +The only aspects of the io_service interface that's preserved are ``post()``, +``dispatch()`` and constructing timers and sockets. In short, the ``run()`` and +``poll()`` family of functions do not exist. Every io_service object is assumed +to be run, and all of their events are handled by the simulation object. + +None of the synchronous APIs are supported, because that would require +integration with OS threads and scheduler. + +example +------- + +Here's a simple example illustrating the asio timer:: + + #include "simulator/simulator.hpp" + #include + #include + + void print_time(sim::asio::high_resolution_timer& timer + , boost::system::error_code const& ec) + { + using namespace sim::chrono; + static int counter = 0; + + printf("[%d] timer fired at: %d milliseconds. error: %s\n" + , counter + , int(duration_cast(high_resolution_clock::now() + .time_since_epoch()).count()) + , ec.message().c_str()); + + ++counter; + if (counter < 5) + { + timer.expires_from_now(seconds(counter)); + timer.async_wait(std::bind(&print_time, std::ref(timer), _1)); + } + } + + int main() + { + using namespace sim::chrono; + + default_config cfg; + simulation sim(cfg); + io_service ios(sim, ip::address_v4::from_string("1.2.3.4")); + sim::asio::high_resolution_timer timer(ios); + + timer.expires_from_now(seconds(1)); + timer.async_wait(std::bind(&print_time, std::ref(timer), _1)); + + boost::system::error_code ec; + sim.run(ec); + + printf("sim::run() returned: %s at: %d\n" + , ec.message().c_str() + , int(duration_cast(high_resolution_clock::now() + .time_since_epoch()).count())); + } + +The output from this program is:: + + [0] timer fired at: 1000 milliseconds. error: Undefined error: 0 + [1] timer fired at: 2000 milliseconds. error: Undefined error: 0 + [2] timer fired at: 4000 milliseconds. error: Undefined error: 0 + [3] timer fired at: 7000 milliseconds. error: Undefined error: 0 + [4] timer fired at: 11000 milliseconds. error: Undefined error: 0 + io_service::run() returned: Undefined error: 0 at: 11000 + +And obviously it doesn't take 11 wall-clock seconds to run (it returns +instantly). + +configuration +------------- + +The simulated network can be configured with per-node pair bandwidth, round-trip +latency and queue sizes. This is controlled via a callback interface that +libsimulator will ask for these properties when nodes get connected. + +The resolution of hostnames is also configurable by providing a callback on the +configuration object along with the latency of individual lookups. + +To configure the network for the simulation, pass in a reference to an object +implementing the ``sim::configuration`` interface:: + + struct configuration + { + // build the network + virtual void build(simulation& sim) = 0; + + // return the hops on the network packets from src to dst need to traverse + virtual route channel_route(asio::ip::address src + , asio::ip::address dst) = 0; + + // return the hops an incoming packet to ep need to traverse before + // reaching the socket (for instance a NAT) + virtual route incoming_route(asio::ip::address ip) = 0; + + // return the hops an outgoing packet from ep need to traverse before + // reaching the network (for instance a DSL modem) + virtual route outgoing_route(asio::ip::address ip) = 0; + + // return the path MTU between the two IP addresses + // For TCP sockets, this will be called once when the connection is + // established. For UDP sockets it's called for every burst of packets + // that are sent + virtual int path_mtu(asio::ip::address ip1, asio::ip::address ip2) = 0; + + // called for every hostname lookup made by the client. ``reqyestor`` is + // the node performing the lookup, ``hostname`` is the name being looked + // up. Resolve the name into addresses and fill in ``result`` or set + // ``ec`` if the hostname is not found or some other error occurs. The + // return value is the latency of the lookup. The client's callback won't + // be called until after waiting this long. + virtual chrono::high_resolution_clock::duration hostname_lookup( + asio::ip::address const& requestor + , std::string hostname + , std::vector& result + , boost::system::error_code& ec) = 0; + }; + +``build()`` is called right after the simulation is constructed. It gives the +configuration object an opportunity to construct the core queues, since they +need access to the simulator. + +``channel_route()`` is expected to return a *route* of network hops from the +source IP to the destination IP. A route is a series of ``sink`` objects. The +typical sink is a ``sim::queue``, which is a network node with a specific rate +limit, propagation delay and queue size. + +*TODO: finish document configuration interface* + +history +------- + +libsimulator grew out of libtorrent's unit tests, as a tool to make them reliable +and deterministic (i.e. not depend on external systems like sockets and timers) +and also easier to debug. The subset of the asio API initially supported by this +library is the subset used by libtorrent. Patches are welcome to improve +fidelity and support. + diff --git a/simulation/libsimulator/appveyor.yml b/simulation/libsimulator/appveyor.yml new file mode 100644 index 0000000..b5b1f48 --- /dev/null +++ b/simulation/libsimulator/appveyor.yml @@ -0,0 +1,27 @@ +version: 1.0.{build} +image: Visual Studio 2017 +branches: + only: + - master +skip_tags: true +clone_depth: 1 +install: +- set BUILD_DIR=%CD% +- set BOOST_ROOT=c:\Libraries\boost_1_67_0 +- set BOOST_BUILD_PATH=%BOOST_ROOT%\tools\build +- echo %BOOST_ROOT% +- echo %BOOST_BUILD_PATH% +- set PATH=%PATH%;%BOOST_BUILD_PATH%\src\engine\bin.ntx86 +- set PATH=c:\msys64\mingw32\bin;%PATH% +- g++ --version +- copy user-config.jam %HOMEDRIVE%%HOMEPATH%\user-config.jam +- cd %BOOST_BUILD_PATH%\src\engine +- build.bat >nul +- cd %BUILD_DIR% +environment: + matrix: + - compiler: msvc-14.1 + - compiler: gcc +build_script: +- b2.exe --hash warnings-as-errors=on -j2 address-model=32 %compiler% + diff --git a/simulation/libsimulator/include/simulator/chrono.hpp b/simulation/libsimulator/include/simulator/chrono.hpp new file mode 100644 index 0000000..90b3ef9 --- /dev/null +++ b/simulation/libsimulator/include/simulator/chrono.hpp @@ -0,0 +1,88 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef CHRONO_HPP_INCLUDED +#define CHRONO_HPP_INCLUDED + +#include +#include "simulator/config.hpp" + +#if defined BOOST_ASIO_HAS_STD_CHRONO +#include +#else +#include "simulator/push_warnings.hpp" + +#include +#include +#include + +#include "simulator/pop_warnings.hpp" +#endif + +namespace sim { namespace chrono +{ +#if defined BOOST_ASIO_HAS_STD_CHRONO + using std::chrono::seconds; + using std::chrono::milliseconds; + using std::chrono::microseconds; + using std::chrono::nanoseconds; + using std::chrono::minutes; + using std::chrono::hours; + using std::chrono::duration_cast; + using std::chrono::time_point; + using std::chrono::duration; +#else + using boost::chrono::seconds; + using boost::chrono::milliseconds; + using boost::chrono::microseconds; + using boost::chrono::nanoseconds; + using boost::chrono::minutes; + using boost::chrono::hours; + using boost::chrono::duration_cast; + using boost::chrono::time_point; + using boost::chrono::duration; +#endif + + // std.chrono / boost.chrono compatible high_resolution_clock using a simulated time + struct SIMULATOR_DECL high_resolution_clock + { + using rep = std::int64_t; +#if defined BOOST_ASIO_HAS_STD_CHRONO + using period = std::nano; + using time_point = std::chrono::time_point; + using duration = std::chrono::duration; +#else + using period = boost::nano; + using time_point = time_point; + using duration = duration; +#endif + static const bool is_steady = true; + static time_point now(); + + // private interface + static void fast_forward(high_resolution_clock::duration d); + }; + + // private interface + void reset_clock(); + +} // chrono +} // sim + +#endif + diff --git a/simulation/libsimulator/include/simulator/config.hpp b/simulation/libsimulator/include/simulator/config.hpp new file mode 100644 index 0000000..053a665 --- /dev/null +++ b/simulation/libsimulator/include/simulator/config.hpp @@ -0,0 +1,51 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef CONFIG_HPP_INCLUDED +#define CONFIG_HPP_INCLUDED + +#include "simulator/push_warnings.hpp" +#include +#include "simulator/pop_warnings.hpp" + +#ifdef SIMULATOR_BUILDING_SHARED +#define SIMULATOR_DECL BOOST_SYMBOL_EXPORT +#elif defined SIMULATOR_LINKING_SHARED +#define SIMULATOR_DECL BOOST_SYMBOL_IMPORT +#else +#define SIMULATOR_DECL +#endif + +#if defined __clang__ || defined __GNUC__ +#define LIBSIMULATOR_NO_RETURN __attribute((noreturn)) +#elif _MSC_VER +#define LIBSIMULATOR_NO_RETURN __declspec(noreturn) +#else +#define LIBSIMULATOR_NO_RETURN +#endif + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4251: X: class Y needs to have dll-interface to be used by clients of struct +#pragma warning( disable : 4251) +// warning C4661: X: no suitable definition provided for explicit template instantiation request +#pragma warning( disable : 4661) +#endif + +#endif + diff --git a/simulation/libsimulator/include/simulator/function.hpp b/simulation/libsimulator/include/simulator/function.hpp new file mode 100644 index 0000000..5a67a2b --- /dev/null +++ b/simulation/libsimulator/include/simulator/function.hpp @@ -0,0 +1,220 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SIMULATOR_FUNCTION_HPP_INCLUDED +#define SIMULATOR_FUNCTION_HPP_INCLUDED + +#include +#include // for allocator_traits + +#include "simulator/mallocator.hpp" + +namespace sim { +namespace aux { + + template + T exchange_(T& var, U&& new_val) + { + T temp = std::move(var); + var = std::forward(new_val); + return temp; + } + + template + T* allocate_handler(Fun h) + { + using alloc = typename boost::asio::associated_allocator::type>::type; + using our_alloc = typename std::allocator_traits::template rebind_alloc; + our_alloc al(boost::asio::get_associated_allocator(h)); + void* ptr = al.allocate(1); + if (ptr == nullptr) throw std::bad_alloc(); + try { + return new (ptr) T(std::move(h)); + } + catch (...) { + al.deallocate(reinterpret_cast(ptr), 1); + throw; + } + } + + // this is a std::function-like class that supports move-only function + // objects + template + struct callable + { + using call_fun_t = R (*)(void*, A&&...); + using deallocate_fun_t = void (*)(void*); + call_fun_t call_fun; + deallocate_fun_t deallocate_fun; + }; + + template + R call_impl(void* mem, A&&... a); + + template + void dealloc_impl(void* mem); + + template + struct function_impl : callable + { + function_impl(Handler h) + : handler(std::move(h)) + { + this->call_fun = call_impl; + this->deallocate_fun = dealloc_impl; + } + Handler handler; + }; + + template + R call_impl(void* mem, A&&... a) + { + auto* obj = static_cast*>(mem); + Handler handler = std::move(obj->handler); + + obj->~function_impl(); + using alloc = typename boost::asio::associated_allocator::type; + using our_alloc = typename std::allocator_traits:: + template rebind_alloc::type>; + our_alloc al(boost::asio::get_associated_allocator(handler)); + al.deallocate(obj, 1); + + return handler(std::forward(a)...); + } + + template + void dealloc_impl(void* mem) + { + auto* obj = static_cast*>(mem); + Handler h = std::move(obj->handler); + + obj->~function_impl(); + using alloc = typename boost::asio::associated_allocator::type; + using our_alloc = typename std::allocator_traits:: + template rebind_alloc::type>; + our_alloc al(boost::asio::get_associated_allocator(h)); + al.deallocate(obj, 1); + } + + template + struct function; + + template + struct function + { + using result_type = R; + + using allocator_type = aux::mallocator; + allocator_type get_allocator() const { return allocator_type{}; } + + template + function(C c) + : m_callable(allocate_handler>(std::move(c))) + {} + function(function&& other) noexcept + : m_callable(exchange_(other.m_callable, nullptr)) + {} + function& operator=(function&& other) noexcept + { + if (&other == this) return *this; + clear(); + m_callable = exchange_(other.m_callable, nullptr); + return *this; + } + + ~function() { clear(); } + + // boost.asio requires handlers to be copy-constructible, but it will move + // them, if they're movable. So we trick asio into accepting this handler. + // If it attempts to copy, it will cause a link error + function(function const&) { assert(false && "functions should not be copied"); } + function& operator=(function const&) = delete; + + function() = default; + explicit operator bool() const { return m_callable != nullptr; } + function& operator=(std::nullptr_t) { clear(); return *this; } + void clear() + { + if (m_callable == nullptr) return; + auto fun = m_callable->deallocate_fun; + fun(m_callable); + m_callable = nullptr; + } + template + R operator()(Args&&... a) + { + assert(m_callable); + auto fun = m_callable->call_fun; + return fun(exchange_(m_callable, nullptr), std::forward(a)...); + } + private: + callable* m_callable = nullptr; + }; + + // index sequence, to unpack tuple + template struct seq {}; + template struct gens : gens {}; + template struct gens<0, S...> { using type = seq; }; + + // a binder for move-only types, and movable arguments. It's not a general + // binder as it doesn't support partial application, it just binds all + // arguments and ignores any arguments passed to the call + template + struct move_binder + { + move_binder(Callable c, A&&... a) + : m_args(std::move(a)...) + , m_callable(std::move(c)) + {} + + move_binder(move_binder const&) = delete; + move_binder& operator=(move_binder const&) = delete; + + move_binder(move_binder&&) = default; + move_binder& operator=(move_binder&&) = default; + + // ignore any arguments passed in. This is used to ignore an error_code + // argument for instance + template + R operator()(Args...) + { + return call(typename gens::type()); + } + + private: + + template + R call(seq) + { + return m_callable(std::move(std::get(m_args))...); + } + std::tuple m_args; + Callable m_callable; + }; + + template + move_binder move_bind(C c, A&&... a) + { + return move_binder(std::move(c), std::forward(a)...); + } + +} +} + +#endif + diff --git a/simulation/libsimulator/include/simulator/handler_allocator.hpp b/simulation/libsimulator/include/simulator/handler_allocator.hpp new file mode 100644 index 0000000..a653483 --- /dev/null +++ b/simulation/libsimulator/include/simulator/handler_allocator.hpp @@ -0,0 +1,81 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef HANDLER_ALLOCATOR_HPP_INCLUDED +#define HANDLER_ALLOCATOR_HPP_INCLUDED + +namespace sim +{ +namespace aux +{ + +template +struct malloc_allocator +{ + using value_type = T; + using size_type = std::size_t; + + friend bool operator==(malloc_allocator, malloc_allocator) { return true; } + friend bool operator!=(malloc_allocator, malloc_allocator) { return false; } + + template + struct rebind { using other = malloc_allocator; }; + + malloc_allocator() = default; + template + malloc_allocator(malloc_allocator const&) {} + + T* allocate(std::size_t size) { return static_cast(std::malloc(size * sizeof(T))); } + void deallocate(T* pointer, std::size_t) { std::free(pointer); } + using is_always_equal = std::true_type; +}; + +// this is a handler wrapper that customizes the asio handler allocator to use +// malloc instead of new. The purpose is to distinguish allocations that are +// internal to the simulator and allocations part of the program under test. +template +struct malloc_wrapper +{ + malloc_wrapper(Handler h) : m_handler(std::move(h)) {} + + template + void operator()(Args&&... a) + { + m_handler(std::forward(a)...); + } + + using allocator_type = malloc_allocator>; + + allocator_type get_allocator() const noexcept + { return allocator_type{}; } + +private: + Handler m_handler; +}; + +template +malloc_wrapper make_malloc(T h) +{ + return malloc_wrapper(std::move(h)); +} + +} +} + +#endif + diff --git a/simulation/libsimulator/include/simulator/http_proxy.hpp b/simulation/libsimulator/include/simulator/http_proxy.hpp new file mode 100644 index 0000000..bd1d6f7 --- /dev/null +++ b/simulation/libsimulator/include/simulator/http_proxy.hpp @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef HTTP_PROXY_HPP_INCLUDED +#define HTTP_PROXY_HPP_INCLUDED + +#include "simulator/simulator.hpp" + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4251: X: class Y needs to have dll-interface to be used by clients of struct +#pragma warning( disable : 4251) +#endif + +namespace sim +{ + struct http_request; + +// This is a very simple http proxy that only supports a single +// concurrent connection +struct SIMULATOR_DECL http_proxy +{ + http_proxy(asio::io_context& ios, unsigned short listen_port); + + void stop(); + +private: + + void on_accept(boost::system::error_code const& ec); + void on_read_request(boost::system::error_code const& ec, size_t bytes_transferred); + + void forward_request(http_request const& req); + void open_forward_connection(const asio::ip::tcp::endpoint& target); + void on_connected(boost::system::error_code const& ec); + + void on_domain_lookup(boost::system::error_code const& ec + , const asio::ip::tcp::resolver::results_type ips); + + void write_server_send_buffer(); + void on_server_write(boost::system::error_code const& ec, size_t bytes_transferred); + + void on_server_receive(boost::system::error_code const& ec + , std::size_t bytes_transferred); + void on_server_forward(boost::system::error_code const& ec, size_t bytes_transferred); + + void error(int code, char const* message); + void close_connection(); + + asio::ip::tcp::resolver m_resolver; + asio::ip::tcp::acceptor m_listen_socket; + + // this is the client connection, i.e. the client connecting to us, sending + // HTTP requests that we forward + asio::ip::tcp::socket m_client_connection; + // client endpoint + asio::ip::tcp::endpoint m_ep; + + // this is the connection to the server the client's requests are forwarded + // to + asio::ip::tcp::socket m_server_connection; + // true while there is an outstanding write operation to the server + bool m_writing_to_server; + + // receive buffer for requests from the client. i.e. client -> proxy (us) -> server + char m_client_in_buffer[65536]; + // buffer size + int m_num_client_in_bytes; + + char m_server_out_buffer[65536]; + int m_num_server_out_bytes; + + // receive buffer for incoming responses, i.e. server -> proxy (us) -> client + char m_in_buffer[65536]; + // buffer size + int m_num_in_bytes; + + // set to true when shutting down + bool m_close; +}; + +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif + diff --git a/simulation/libsimulator/include/simulator/http_server.hpp b/simulation/libsimulator/include/simulator/http_server.hpp new file mode 100644 index 0000000..ef10c9b --- /dev/null +++ b/simulation/libsimulator/include/simulator/http_server.hpp @@ -0,0 +1,122 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef HTTP_SERVER_HPP_INCLUDED +#define HTTP_SERVER_HPP_INCLUDED + +#include "simulator/simulator.hpp" +#include + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4251: X: class Y needs to have dll-interface to be used by clients of struct +#pragma warning( disable : 4251) +#endif + +namespace sim +{ + std::string SIMULATOR_DECL trim(std::string s); + + std::string SIMULATOR_DECL lower_case(std::string s); + + std::string SIMULATOR_DECL normalize(const std::string& s); + + // returns the index to the last byte of the request, or -1 if the buffer + // does not contain a full http request + int SIMULATOR_DECL find_request_len(char const* buf, int len); + + struct http_request + { + std::string method; + std::string req; + std::string path; + std::map headers; + }; + + http_request parse_request(char const* start, int len); + + // builds an HTTP response buffer + std::string SIMULATOR_DECL send_response(int code, char const* status_message + , int len = 0, char const** extra_header = NULL); + +// This is a very simple http server that only supports a single concurrent +// connection +struct SIMULATOR_DECL http_server +{ + enum flags_t + { + keep_alive = 1 + }; + + http_server(asio::io_context& ios, unsigned short listen_port + , int flags = http_server::keep_alive); + + void stop(); + + using handler_t = std::function&)>; + using generator_t = std::function; + + void register_handler(std::string const& path, handler_t h); + void register_content(std::string const& path + , std::int64_t const size, generator_t gen); + void register_redirect(std::string const& path, std::string const& target); + void register_stall_handler(std::string const& path); + +private: + + void on_accept(boost::system::error_code const& ec); + void read(); + void on_read(boost::system::error_code const& ec, size_t bytes_transferred); + void on_write(boost::system::error_code const& ec, size_t bytes_transferred + , bool close); + void close_connection(); + + asio::io_context& m_ios; + + asio::ip::tcp::acceptor m_listen_socket; + + asio::ip::tcp::socket m_connection; + asio::ip::tcp::endpoint m_ep; + + std::unordered_map m_handlers; + std::set m_stall_handlers; + + // read buffer, we receive bytes into this buffer for the connection + std::string m_recv_buffer; + + // the number of bytes of m_recv_buffer that we've actually read data into. + // The remaining is uninitialized, possibly being read into in an async call + int m_bytes_used; + + std::string m_send_buffer; + + // set to true when shutting down + bool m_close; + + int m_flags; +}; + +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif + diff --git a/simulation/libsimulator/include/simulator/mallocator.hpp b/simulation/libsimulator/include/simulator/mallocator.hpp new file mode 100644 index 0000000..97baf67 --- /dev/null +++ b/simulation/libsimulator/include/simulator/mallocator.hpp @@ -0,0 +1,69 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SIMULATOR_MALLOCATOR_HPP_INCLUDED +#define SIMULATOR_MALLOCATOR_HPP_INCLUDED + +namespace sim +{ +namespace aux +{ + struct channel; + struct packet; + struct pcap; + + template + struct mallocator + { + template + friend struct mallocator; + + using value_type = T; + using pointer = T*; + using const_pointer = T const*; + using reference = T&; + using const_reference = T const&; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + template + struct rebind { + using other = mallocator; + }; + + mallocator() = default; + template + mallocator(mallocator const&) {} + + T* allocate(std::size_t size) + { return reinterpret_cast(std::malloc(size * sizeof(T))); } + + void deallocate(T* ptr) { std::free(ptr); } + void deallocate(T* ptr, std::size_t) { std::free(ptr); } + + void destroy(pointer p) { p->~T(); } + void construct(pointer p, T&& value) { new (p) T(std::move(value)); } + + bool operator==(mallocator const&) const { return true; } + bool operator!=(mallocator const&) const { return false; } + }; +} // aux +} // sim + +#endif // SIMULATOR_MALLOCATOR_HPP_INCLUDED + diff --git a/simulation/libsimulator/include/simulator/nat.hpp b/simulation/libsimulator/include/simulator/nat.hpp new file mode 100644 index 0000000..2edebe4 --- /dev/null +++ b/simulation/libsimulator/include/simulator/nat.hpp @@ -0,0 +1,51 @@ +/* + +Copyright (c) 2018, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef NAT_HPP_INCLUDED +#define NAT_HPP_INCLUDED + +#include "simulator/sink.hpp" +#include "simulator/simulator.hpp" +#include + +namespace sim { + +namespace aux { + struct packet; +} + + struct SIMULATOR_DECL nat : sink + { + nat(asio::ip::address external_addr); + ~nat() = default; + + void incoming_packet(aux::packet p) override; + + // used for visualization + std::string label() const override; + + private: + + asio::ip::address m_external_addr; + }; + +} // sim + +#endif + + diff --git a/simulation/libsimulator/include/simulator/noexcept_movable.hpp b/simulation/libsimulator/include/simulator/noexcept_movable.hpp new file mode 100644 index 0000000..32472d3 --- /dev/null +++ b/simulation/libsimulator/include/simulator/noexcept_movable.hpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef NOEXCEPT_MOVABLE_HPP_INCLUDED +#define NOEXCEPT_MOVABLE_HPP_INCLUDED + +namespace sim { +namespace aux { + + template + struct noexcept_movable : T + { + noexcept_movable() noexcept {} + noexcept_movable(noexcept_movable&& rhs) noexcept + : T(std::forward(rhs)) + {} + noexcept_movable(noexcept_movable const& rhs) + : T(static_cast(rhs)) + {} + noexcept_movable(T&& rhs) noexcept : T(std::forward(rhs)) {} // NOLINT + noexcept_movable(T const& rhs) : T(rhs) {} // NOLINT + noexcept_movable& operator=(noexcept_movable&& rhs) noexcept + { + this->T::operator=(std::forward(rhs)); + return *this; + } + noexcept_movable& operator=(noexcept_movable const& rhs) + { + this->T::operator=(rhs); + return *this; + } + + using T::T; + using T::operator=; + }; + +} +} + +#endif + diff --git a/simulation/libsimulator/include/simulator/packet.hpp b/simulation/libsimulator/include/simulator/packet.hpp new file mode 100644 index 0000000..f009bb2 --- /dev/null +++ b/simulation/libsimulator/include/simulator/packet.hpp @@ -0,0 +1,94 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef PACKET_HPP_INCLUDED +#define PACKET_HPP_INCLUDED + +#include "simulator/config.hpp" +#include "simulator/simulator.hpp" // for route, endpoint + +namespace sim { namespace aux { + + struct channel; + + struct packet + { + packet() = default; + + // this is move-only + packet(packet const&) = delete; + packet& operator=(packet const&) = delete; + packet(packet&&) = default; + packet& operator=(packet&&) = default; + + // to keep things simple, don't drop ACKs or errors + bool ok_to_drop() const + { + return type != type_t::syn_ack + && type != type_t::ack + && type != type_t::error; + } + + enum class type_t + { + uninitialized, // invalid type (used for debugging) + syn, // TCP connect + syn_ack, // TCP connection accepted + ack, // the seq_nr is interpreted as "we received this" + error, // the error_code (ec) is set + payload // the buffer is filled + }; + + type_t type = type_t::uninitialized; + + boost::system::error_code ec; + + // actual payload + std::vector buffer; + + // used for UDP packets + asio::ip::udp::endpoint from; + + // the number of bytes of overhead for this packet. The total packet + // size is the number of bytes in the buffer + this number + int overhead = 20; + + // each hop in the route will pop itself off and forward the packet to + // the next hop + route hops; + + // for SYN packets, this is set to the channel we're trying to + // establish + std::shared_ptr channel; + + // sequence number of this packet (used for debugging) + std::uint64_t seq_nr = 0; + + // the number of (payload) bytes sent over this channel so far. This is + // meant to map to the TCP sequence number + std::uint32_t byte_counter = 0; + + // this function must be called with this packet in case the packet is + // dropped. + aux::function drop_fun; + }; + +}} // sim + +#endif + diff --git a/simulation/libsimulator/include/simulator/pcap.hpp b/simulation/libsimulator/include/simulator/pcap.hpp new file mode 100644 index 0000000..2719f2e --- /dev/null +++ b/simulation/libsimulator/include/simulator/pcap.hpp @@ -0,0 +1,42 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef PCAP_HPP_INCLUDED +#define PCAP_HPP_INCLUDED + +#include +#include "simulator/simulator.hpp" // for endpoint + +namespace sim { namespace aux +{ + struct packet; + + struct pcap + { + pcap(char const* filename); + void log_tcp(packet const& p, asio::ip::tcp::endpoint src + , asio::ip::tcp::endpoint dst); + void log_udp(packet const& p, asio::ip::udp::endpoint src + , asio::ip::udp::endpoint dst); + private: + std::fstream m_file; + }; +}} + +#endif + diff --git a/simulation/libsimulator/include/simulator/pop_warnings.hpp b/simulation/libsimulator/include/simulator/pop_warnings.hpp new file mode 100644 index 0000000..8ffbe4f --- /dev/null +++ b/simulation/libsimulator/include/simulator/pop_warnings.hpp @@ -0,0 +1,44 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + diff --git a/simulation/libsimulator/include/simulator/push_warnings.hpp b/simulation/libsimulator/include/simulator/push_warnings.hpp new file mode 100644 index 0000000..1811434 --- /dev/null +++ b/simulation/libsimulator/include/simulator/push_warnings.hpp @@ -0,0 +1,89 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wall" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wswitch-enum" +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wundef" +#pragma GCC diagnostic ignored "-Wmissing-noreturn" +#pragma GCC diagnostic ignored "-Wdeprecated" +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wpedantic" +#if __GNUC__ >= 6 +#pragma GCC diagnostic ignored "-Wshift-overflow" +#pragma GCC diagnostic ignored "-Wshift-count-overflow" +#pragma GCC diagnostic ignored "-Wshift-count-negative" +#endif +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wall" +#pragma clang diagnostic ignored "-Weverything" +#pragma clang diagnostic ignored "-Wsign-conversion" +#pragma clang diagnostic ignored "-Wconversion" +#pragma clang diagnostic ignored "-Wswitch-enum" +#pragma clang diagnostic ignored "-Wcovered-switch-default" +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wundef" +#pragma clang diagnostic ignored "-Wweak-vtables" +#pragma clang diagnostic ignored "-Wmissing-noreturn" +#pragma clang diagnostic ignored "-Wdeprecated" +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#pragma clang diagnostic ignored "-Wcast-align" +#pragma clang diagnostic ignored "-Wweak-vtable" +#pragma clang diagnostic ignored "-Wundef" +#pragma clang diagnostic ignored "-Wshadow" +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +#pragma clang diagnostic ignored "-Wc++11-long-long" +#pragma clang diagnostic ignored "-Wc++11-extensions" +#pragma clang diagnostic ignored "-Wextra-semi" +#pragma clang diagnostic ignored "-Wunused-parameter" +#pragma clang diagnostic ignored "-Wreserved-id-macro" +#pragma clang diagnostic ignored "-Wunused-local-typedef" +#pragma clang diagnostic ignored "-Wgnu-folding-constant" +#pragma clang diagnostic ignored "-Wdouble-promotion" +#pragma clang diagnostic ignored "-Wfloat-equal" +#endif + +#ifdef _MSC_VER +#pragma warning(push, 1) +// warning C4005: macro redefinition +#pragma warning( disable : 4005 ) +#endif + diff --git a/simulation/libsimulator/include/simulator/queue.hpp b/simulation/libsimulator/include/simulator/queue.hpp new file mode 100644 index 0000000..2a5f988 --- /dev/null +++ b/simulation/libsimulator/include/simulator/queue.hpp @@ -0,0 +1,100 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef QUEUE_HPP_INCLUDED +#define QUEUE_HPP_INCLUDED + +#include "simulator/simulator.hpp" +#include "simulator/packet.hpp" +#include "simulator/mallocator.hpp" + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4251: X: class Y needs to have dll-interface to be used by clients of struct +#pragma warning( disable : 4251) +#endif + +namespace sim { + + struct timed_packet + { + timed_packet(chrono::high_resolution_clock::time_point t, aux::packet p) + : ts(t), pkt(std::move(p)) + {} + timed_packet(timed_packet&&) = default; + timed_packet& operator=(timed_packet&&) = default; + timed_packet(timed_packet const&) = delete; + timed_packet& operator=(timed_packet const&) = delete; + chrono::high_resolution_clock::time_point ts; + aux::packet pkt; + }; + + // this is a queue. It can be configured to contrain + struct SIMULATOR_DECL queue : sink + { + queue(asio::io_context& ios, int bandwidth + , chrono::high_resolution_clock::duration propagation_delay + , int max_queue_size, std::string name = "queue"); + + virtual void incoming_packet(aux::packet p) override final; + + virtual std::string label() const override final; + + queue(queue const&) = delete; + queue& operator=(queue const&) = delete; + + queue(queue&&) = default; + queue& operator=(queue&&) = delete; + + private: + + void begin_send_next_packet(); + void next_packet_sent(); + + // the queue can't hold more than this number of bytes. Once it's full, + // any new packets arriving will be dropped (tail drop) + int m_max_queue_size; + + // the amount of time it takes to forward a packet. Every packet is + // delayed by at least this much before being forwarded + chrono::high_resolution_clock::duration m_forwarding_latency; + + // the number of bytes per second that can be sent. This includes the + // packet overhead + int m_bandwidth; + + // the number of bytes currently in the packet queue + int m_queue_size; + + std::string m_node_name; + + // this is the queue of packets and the time each packet was enqueued + std::deque> m_queue; + asio::high_resolution_timer m_forward_timer; + + chrono::high_resolution_clock::time_point m_last_forward; + }; + +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif + diff --git a/simulation/libsimulator/include/simulator/simulator.hpp b/simulation/libsimulator/include/simulator/simulator.hpp new file mode 100644 index 0000000..4641c80 --- /dev/null +++ b/simulation/libsimulator/include/simulator/simulator.hpp @@ -0,0 +1,1358 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SIMULATOR_HPP_INCLUDED +#define SIMULATOR_HPP_INCLUDED + +#include "simulator/push_warnings.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "simulator/pop_warnings.hpp" + +#include "simulator/chrono.hpp" +#include "simulator/sink_forwarder.hpp" +#include "simulator/function.hpp" +#include "simulator/noexcept_movable.hpp" +#include "simulator/mallocator.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef IP_DONTFRAGMENT +#define IP_DONTFRAGMENT 1 +#endif + +namespace sim +{ + namespace aux + { + struct channel; + struct packet; + struct pcap; + + // this is outgoing NIC bandwidth + constexpr int nic_bandwidth = 100000000; // 100 MB/s + } + + // this represents a network route (a series of sinks to pass a packet + // through) + struct SIMULATOR_DECL route + { + friend route operator+(route lhs, route rhs) + { return std::move(lhs.append(std::move(rhs))); } + + std::shared_ptr next_hop() const { return hops.front(); } + std::shared_ptr pop_front() + { + if (hops.empty()) return std::shared_ptr(); + std::shared_ptr ret(std::move(hops.front())); + hops.erase(hops.begin()); + return ret; + } + void replace_last(std::shared_ptr s) { hops.back() = std::move(s); } + void prepend(route const& r) + { hops.insert(hops.begin(), r.hops.begin(), r.hops.end()); } + void prepend(std::shared_ptr s) { hops.insert(hops.begin(), std::move(s)); } + route& append(route const& r) + { hops.insert(hops.end(), r.hops.begin(), r.hops.end()); return *this; } + route& append(std::shared_ptr s) { hops.push_back(std::move(s)); return *this; } + bool empty() const { return hops.empty(); } + std::shared_ptr last() const + { return hops.back(); } + + private: + std::deque, aux::mallocator>> hops; + }; + + void forward_packet(aux::packet p); + + struct simulation; + struct configuration; + struct queue; + + namespace asio + { + + using boost::asio::buffer_size; + using boost::asio::const_buffer; + using boost::asio::mutable_buffer; + using boost::asio::buffer; + + using boost::asio::post; + using boost::asio::dispatch; + using boost::asio::defer; + + struct io_context; + + struct io_executor + { + io_executor(io_context& ctx) : m_ctx(&ctx) {} + io_context& context() const { return *m_ctx; } + + template + void dispatch(Handler handler, Allocator const& a) const; + + template + void post(Handler handler, Allocator const& a) const; + + template + void defer(Handler handler, Allocator const& a) const; + + void on_work_finished() const {} + void on_work_started() const {} + + bool running_in_this_thread() const { return true; } + + friend bool operator==(io_executor const& lhs, io_executor const& rhs) + { return lhs.m_ctx == rhs.m_ctx; } + + friend bool operator!=(io_executor const& lhs, io_executor const& rhs) + { return lhs.m_ctx != rhs.m_ctx; } + + private: + io_context* m_ctx; + }; + + struct SIMULATOR_DECL high_resolution_timer + { + friend struct sim::simulation; + + using time_type = chrono::high_resolution_clock::time_point; + using duration_type = chrono::high_resolution_clock::duration; + + using executor_type = io_executor; + executor_type get_executor(); + + explicit high_resolution_timer(io_context& io_context); + high_resolution_timer(io_context& io_context, + const time_type& expiry_time); + high_resolution_timer(io_context& io_context, + const duration_type& expiry_time); + high_resolution_timer(high_resolution_timer&&) noexcept = default; + high_resolution_timer& operator=(high_resolution_timer&&) noexcept = default; + ~high_resolution_timer(); + + std::size_t cancel(); + std::size_t cancel_one(); + + time_type expiry() const; + std::size_t expires_at(const time_type& expiry_time); + std::size_t expires_after(const duration_type& expiry_time); + + void wait(); + void wait(boost::system::error_code& ec); + + void async_wait(aux::function handler); + + private: + + void fire(boost::system::error_code ec); + + time_type m_expiration_time; + aux::function m_handler; + io_context* m_io_service; + bool m_expired; + }; + + using waitable_timer = high_resolution_timer; + + namespace error = boost::asio::error; + + template + struct socket_base + { + socket_base(io_context& ios) : m_io_service(ios) {} + socket_base(socket_base&& s) = default; + + enum wait_type_t + { + wait_read, wait_write, wait_error + }; + + // io_control + using reuse_address = boost::asio::socket_base::reuse_address; + using executor_type = io_executor; + executor_type get_executor(); + + // socket options + using send_buffer_size = boost::asio::socket_base::send_buffer_size; + using receive_buffer_size = boost::asio::socket_base::receive_buffer_size; + + template + void set_option(Option const& opt, boost::system::error_code&) + { + Protocol const p = Protocol::v4(); + (void)p; +#ifdef IP_DONTFRAG + if (opt.name(p) == IP_DONTFRAG) + m_dont_fragment = *reinterpret_cast(opt.data(p)) != 0; +#endif +#ifdef IP_DONTFRAGMENT + if (opt.name(p) == IP_DONTFRAGMENT) + m_dont_fragment = *reinterpret_cast(opt.data(p)) != 0; +#endif +#ifdef IP_MTU_DISCOVER + if (opt.name(p) == IP_MTU_DISCOVER) + m_dont_fragment = *reinterpret_cast(opt.data(p)) == IP_PMTUDISC_DO; +#endif + } + + void set_option(receive_buffer_size const& op, boost::system::error_code&) + { + m_max_receive_queue_size = op.value(); + } + + void set_option(send_buffer_size const& op, boost::system::error_code&) + { + // this limit is specified in microseconds. Given the line rate of + // nic_bandwidth, this is the time it takes to send the specified number + // of bytes. + m_send_queue_time = chrono::microseconds(std::int64_t(double(op.value()) * 1000000.0 / double(aux::nic_bandwidth))); + } + + void set_option(reuse_address const&, boost::system::error_code&) + { + // TODO: implement + } + + typename Protocol::endpoint local_endpoint(boost::system::error_code& ec) const + { + if (!m_open) + { + ec = error::bad_descriptor; + return typename Protocol::endpoint{}; + } + + return m_user_bound_to; + } + + typename Protocol::endpoint local_endpoint() const + { + boost::system::error_code ec; + auto const ret = local_endpoint(ec); + if (ec) throw boost::system::system_error(ec); + return ret; + } + + typename Protocol::endpoint local_bound_to(boost::system::error_code& ec) const + { + if (!m_open) + { + ec = error::bad_descriptor; + return typename Protocol::endpoint{}; + } + + return m_bound_to; + } + + typename Protocol::endpoint local_bound_to() const + { + boost::system::error_code ec; + auto const ret = local_bound_to(ec); + if (ec) throw boost::system::system_error(ec); + return ret; + } + + template + void get_option(Option&, boost::system::error_code&) { } + + void get_option(receive_buffer_size& op, boost::system::error_code&) + { + op = m_max_receive_queue_size; + } + + template + void io_control(IoControl const&, boost::system::error_code&) { } + + template + void io_control(IoControl const&) {} + + void non_blocking(bool b, boost::system::error_code&) + { m_non_blocking = b; } + + void non_blocking(bool b) + { m_non_blocking = b; } + + bool is_open() const + { + return m_open; + } + + using message_flags = int; + + // internal interface + + route get_incoming_route(); + route get_outgoing_route(); + + protected: + + io_context& m_io_service; + + typename Protocol::endpoint m_bound_to; + + // this is the interface the user requested to bind to (in order to + // distinguish the concrete interface it was bound to and INADDR_ANY if + // that was requested). We keep this separately to return it as the local + // endpoint + typename Protocol::endpoint m_user_bound_to; + + // this is an object implementing the sink interface, forwarding + // packets to this socket. If this socket is destructed, this forwarder + // is redirected to just drop packets. This is necessary since sinks + // must be held by shared_ptr, and socket objects aren't. + std::shared_ptr m_forwarder; + + // whether the socket is open or not + bool m_open = false; + + // true if the socket is set to non-blocking mode + bool m_non_blocking = false; + + // when true, the MTU limit is in effect + bool m_dont_fragment = false; + + // the max size of the incoming queue. This is to emulate the send and + // receive buffer. This should also depend on the bandwidth, to not + // make the queue size not grow too long in time. + int m_max_receive_queue_size = 64 * 1024; + + // the number of microseconds worth of send buffer this socket has, at + // NIC linerate. + chrono::microseconds m_send_queue_time{200000}; + }; + + namespace ip { + + using boost::asio::ip::address; + using boost::asio::ip::address_v4; + using boost::asio::ip::address_v6; + + using boost::asio::ip::make_address_v4; + using boost::asio::ip::make_address_v6; + using boost::asio::ip::make_address; + + using boost::asio::ip::make_network_v4; + + template + struct basic_endpoint : boost::asio::ip::basic_endpoint + { + basic_endpoint(ip::address const& addr, unsigned short port) + : boost::asio::ip::basic_endpoint(addr, port) {} + basic_endpoint() : boost::asio::ip::basic_endpoint() {} + }; + + template + struct basic_resolver_entry + { + using endpoint_type = typename Protocol::endpoint; + using protocol_type = Protocol; + + basic_resolver_entry() {} + basic_resolver_entry( + endpoint_type const& ep + , std::string const& host + , std::string const& service) + : m_endpoint(ep) + , m_host_name(host) + , m_service(service) + {} + + endpoint_type endpoint() const { return m_endpoint; } + std::string host_name() const { return m_host_name; } + operator endpoint_type() const { return m_endpoint; } + std::string service_name() const { return m_service; } + + private: + endpoint_type m_endpoint; + std::string m_host_name; + std::string m_service; + }; + + template + struct SIMULATOR_DECL basic_resolver + { + basic_resolver(io_context& ios); + + using protocol_type = Protocol; + using results_type = std::vector, aux::mallocator>>; + + void cancel(); + + void async_resolve(std::string hostname, char const* service + , aux::function handler); + + basic_resolver(basic_resolver&&) noexcept; + basic_resolver& operator=(basic_resolver&&) noexcept; + basic_resolver(basic_resolver const&) = delete; + basic_resolver& operator=(basic_resolver const&) = delete; + + //TODO: add remaining members + + private: + + void on_lookup(boost::system::error_code const& ec); + + struct result_t + { + chrono::high_resolution_clock::time_point completion_time; + boost::system::error_code err; + results_type ips; + aux::function handler; + + result_t( + chrono::high_resolution_clock::time_point ct + , boost::system::error_code e + , results_type ips_ + , aux::function h) + : completion_time(ct) + , err(e) + , ips(std::move(ips_)) + , handler(std::move(h)) + {} + + result_t(result_t&&) noexcept = default; + result_t& operator=(result_t&&) = default; + result_t(result_t const&) = delete; + result_t& operator=(result_t const&) = delete; + }; + + io_context* m_ios; + asio::high_resolution_timer m_timer; + using queue_t = aux::noexcept_movable>; + + queue_t m_queue; + }; + + struct SIMULATOR_DECL udp + { + static udp v4() { return udp(AF_INET); } + static udp v6() { return udp(AF_INET6); } + + using endpoint = basic_endpoint; + + struct SIMULATOR_DECL socket : socket_base, sink + { + using endpoint_type = ip::udp::endpoint; + using protocol_type = ip::udp; + using lowest_layer_type = socket; + + socket(io_context& ios); + ~socket() override; + + socket(socket const&) = delete; + socket& operator=(socket const&) = delete; + socket(socket&&); + + lowest_layer_type& lowest_layer() { return *this; } + + void bind(ip::udp::endpoint const& ep + , boost::system::error_code& ec); + void bind(ip::udp::endpoint const& ep); + + void close(); + void close(boost::system::error_code& ec); + + void cancel(boost::system::error_code& ec); + void cancel(); + + void open(udp protocol, boost::system::error_code& ec); + void open(udp protocol); + + template + std::size_t send_to(ConstBufferSequence const& bufs + , udp::endpoint const& destination + , socket_base::message_flags flags + , boost::system::error_code& ec) + { + std::vector b(buffer_sequence_begin(bufs) + , buffer_sequence_end(bufs)); + abort_send_handlers(); + return send_to_impl(b, destination, flags, ec); + } + + template + std::size_t send_to(ConstBufferSequence const& bufs + , udp::endpoint const& destination) + { + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + abort_send_handlers(); + boost::system::error_code ec; + std::size_t ret = send_to_impl(b, destination, 0, ec); + if (ec) throw boost::system::system_error(ec); + return ret; + } + + void async_wait(socket_base::wait_type_t w + , aux::function handler); + + template + void async_receive(BufferSequence const& bufs + , aux::function handler) + { + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + abort_recv_handlers(); + + async_receive_from_impl(b, nullptr, 0, std::move(handler)); + } + + template + void async_receive_from(BufferSequence const& bufs + , udp::endpoint& sender + , aux::function handler) + { + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + abort_recv_handlers(); + + async_receive_from_impl(b, &sender, 0, std::move(handler)); + } + + template + void async_receive_from(BufferSequence const& bufs + , udp::endpoint& sender + , socket_base::message_flags flags + , aux::function handler) + { + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + abort_recv_handlers(); + + async_receive_from_impl(b, &sender, flags, std::move(handler)); + } +/* + void async_read_from(null_buffers const& + , aux::function handler) + { + abort_recv_handlers(); + async_read_some_null_buffers_impl(std::move(handler)); + } +*/ + + template + std::size_t receive_from(BufferSequence const& bufs + , udp::endpoint& sender) + { + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + assert(!b.empty()); + abort_recv_handlers(); + boost::system::error_code ec; + std::size_t ret = receive_from_impl(b, &sender, 0, ec); + if (ec) throw boost::system::system_error(ec); + return ret; + } + + template + std::size_t receive_from(BufferSequence const& bufs + , udp::endpoint& sender + , socket_base::message_flags) + { + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + assert(!b.empty()); + abort_recv_handlers(); + boost::system::error_code ec; + std::size_t ret = receive_from_impl(b, &sender, 0, ec); + if (ec) throw boost::system::system_error(ec); + return ret; + } + + template + std::size_t receive_from(BufferSequence const& bufs + , udp::endpoint& sender + , socket_base::message_flags + , boost::system::error_code& ec) + { + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + assert(!b.empty()); + abort_recv_handlers(); + return receive_from_impl(b, &sender, 0, ec); + } + + // TODO: support connect and remote_endpoint + + // internal interface + + // implements sink + virtual void incoming_packet(aux::packet p) override final; + virtual std::string label() const override final + { return m_bound_to.address().to_string(); } + + void async_receive_from_impl(std::vector const& bufs + , udp::endpoint* sender + , socket_base::message_flags flags + , aux::function handler); + + std::size_t receive_from_impl( + std::vector const& bufs + , udp::endpoint* sender + , socket_base::message_flags flags + , boost::system::error_code& ec); + + void async_wait_receive_impl( + udp::endpoint* sender + , aux::function handler); + + private: + + void maybe_wakeup_reader(); + void abort_send_handlers(); + void abort_recv_handlers(); + + std::size_t send_to_impl(std::vector const& b + , udp::endpoint const& dst, message_flags flags + , boost::system::error_code& ec); + + // this is the next time we'll have an opportunity to send another + // outgoing packet. This is used to implement the bandwidth constraints + // of channels. This may be in the past, in which case it's OK to send + // a packet immediately. + chrono::high_resolution_clock::time_point m_next_send; + + // while we're blocked in an async_write_some operation, this is the + // handler that should be called once we're done sending + aux::function + m_send_handler; + aux::function + m_wait_send_handler; + + // if we have an outstanding read on this socket, this is set to the + // handler. + aux::function + m_recv_handler; + aux::function + m_wait_recv_handler; + + // if we have an outstanding read operation, this is the buffer to + // receive into + std::vector m_recv_buffer; + + // if we have an outstanding receive operation, this may point to an + // endpoint to fill in the senders IP in + udp::endpoint* m_recv_sender; + + asio::high_resolution_timer m_recv_timer; + asio::high_resolution_timer m_send_timer; + + // this is the incoming queue of packets for each socket + std::list> m_incoming_queue; + + bool m_recv_null_buffers; + + // the number of bytes in the incoming packet queue + int m_queue_size; + + // our address family + bool m_is_v4; + }; + + using resolver = basic_resolver; + + int family() const { return m_family; } + + friend bool operator==(udp const& lhs, udp const& rhs) + { return lhs.m_family == rhs.m_family; } + + friend bool operator!=(udp const& lhs, udp const& rhs) + { return lhs.m_family != rhs.m_family; } + + private: + // Construct with a specific family. + explicit udp(int protocol_family) + : m_family(protocol_family) + {} + + int m_family; + + }; // udp + + struct SIMULATOR_DECL tcp + { + // temporary fix until the resolvers are implemented using our endpoint + tcp(boost::asio::ip::tcp p) : m_family(p.family()) {} + + static tcp v4() { return tcp(AF_INET); } + static tcp v6() { return tcp(AF_INET6); } + + int family() const { return m_family; } + + using endpoint = basic_endpoint; + + struct SIMULATOR_DECL socket : socket_base, sink + { + using endpoint_type = ip::tcp::endpoint; + using protocol_type = ip::tcp; + using lowest_layer_type = socket; + + explicit socket(io_context& ios); + socket(socket const&) = delete; + socket& operator=(socket const&) = delete; + socket(socket&&); + socket& operator=(socket&&); + + ~socket() override; + + void close(); + void close(boost::system::error_code& ec); + void open(tcp protocol, boost::system::error_code& ec); + void open(tcp protocol); + void bind(ip::tcp::endpoint const& ep + , boost::system::error_code& ec); + void bind(ip::tcp::endpoint const& ep); + tcp::endpoint remote_endpoint(boost::system::error_code& ec) const; + tcp::endpoint remote_endpoint() const; + + lowest_layer_type& lowest_layer() { return *this; } + + void async_connect(tcp::endpoint const& target + , aux::function h); + + template + void async_write_some(ConstBufferSequence const& bufs + , aux::function handler) + { + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + abort_send_handlers(); + async_write_some_impl(b, std::move(handler)); + } + + void async_wait(socket_base::wait_type_t const w + , aux::function handler) + { + if (w == socket_base::wait_type_t::wait_write) + { + abort_send_handlers(); + assert(false && "not supported yet"); + } + else if (w == socket_base::wait_type_t::wait_read) + { + abort_recv_handlers(); + async_wait_read_impl(std::move(handler)); + } + } + + template + std::size_t read_some(BufferSequence const& bufs + , boost::system::error_code& ec) + { + assert(m_non_blocking && "blocking operations not supported"); + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + return read_some_impl(b, ec); + } + + template + std::size_t write_some(ConstBufferSequence const& bufs + , boost::system::error_code& ec) + { + assert(m_non_blocking && "blocking operations not supported"); + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + return write_some_impl(b, ec); + } + + template + void async_read_some(BufferSequence const& bufs + , aux::function handler) + { + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + abort_recv_handlers(); + + async_read_some_impl(b, std::move(handler)); + } + + std::size_t available(boost::system::error_code & ec) const; + std::size_t available() const; + + void cancel(boost::system::error_code& ec); + void cancel(); + + using socket_base::set_option; + using socket_base::get_option; + using socket_base::io_control; + + // private interface + + // implements sink + virtual void incoming_packet(aux::packet p) override; + virtual std::string label() const override final + { return m_bound_to.address().to_string(); } + + void internal_connect(tcp::endpoint const& bind_ip + , std::shared_ptr const& c + , boost::system::error_code& ec); + + void abort_send_handlers(); + void abort_recv_handlers(); + + virtual bool internal_is_listening(); + protected: + + void maybe_wakeup_reader(); + void maybe_wakeup_writer(); + + void async_write_some_impl(std::vector const& bufs + , aux::function handler); + void async_read_some_impl(std::vector const& bufs + , aux::function handler); + void async_wait_read_impl( + aux::function handler); + std::size_t write_some_impl(std::vector const& bufs + , boost::system::error_code& ec); + std::size_t read_some_impl(std::vector const& bufs + , boost::system::error_code& ec); + + void send_packet(aux::packet p); + + // called when a packet is dropped + void packet_dropped(aux::packet p); + + aux::function m_connect_handler; + + asio::high_resolution_timer m_connect_timer; + + // the tcp "packet size" (segment size) + // TODO: name this constant! + int m_mss = 1475; + + // while we're blocked in an async_write_some operation, this is the + // handler that should be called once we're done sending + aux::function m_send_handler; + aux::function m_wait_send_handler; + + std::vector m_send_buffer; + + // this is the incoming queue of packets for each socket + std::list> m_incoming_queue; + + // the number of bytes in the incoming packet queue + int m_queue_size = 0; + + // if we have an outstanding read on this socket, this is set to the + // handler. + aux::function m_recv_handler; + aux::function m_wait_recv_handler; + + // if we have an outstanding buffer to receive into, these are them + std::vector m_recv_buffer; + + asio::high_resolution_timer m_recv_timer; + + // our address family + bool m_is_v4 = true; + + // true if the currently outstanding read operation is for null_buffers + bool m_recv_null_buffers = false; + + // true if the currenly outstanding write operation is for null_buffers + bool m_send_null_buffers = false; + + // if this socket is connected to another endpoint, this object is + // shared between both sockets and contain information and state about + // the channel. + std::shared_ptr m_channel; + + std::uint64_t m_next_outgoing_seq = 0; + std::uint64_t m_next_incoming_seq = 0; + + // the sequence number of the last dropped packet. We should only cut + // the cwnd in half once per round-trip. If a whole window is lost, we + // need to only halve it once + std::uint64_t m_last_drop_seq = 0; + + // the current congestion window size (in bytes) + int m_cwnd = m_mss * 2; + + // the number of bytes that have been sent but not ACKed yet + int m_bytes_in_flight = 0; + + // reorder buffer for when packets are dropped + std::map, aux::mallocator>> m_reorder_buffer; + + // the sizes of packets given their sequence number + std::unordered_map, std::equal_to, aux::mallocator>> m_outstanding_packet_sizes; + + // packets to re-send (because they were dropped) + std::list> m_outgoing_packets; + }; + + struct SIMULATOR_DECL acceptor : socket + { + explicit acceptor(io_context& ios); + acceptor(acceptor&&); + ~acceptor() override; + + void cancel(boost::system::error_code& ec); + void cancel(); + + void listen(int qs = -1); + void listen(int qs, boost::system::error_code& ec); + + void async_accept(ip::tcp::socket& peer + , aux::function h); + void async_accept(ip::tcp::socket& peer + , ip::tcp::endpoint& peer_endpoint + , aux::function h); + void async_accept(aux::function h); + + void close(boost::system::error_code& ec); + void close(); + + // private interface + + // implements sink + virtual void incoming_packet(aux::packet p) override final; + virtual bool internal_is_listening() override final; + + private: + // check the incoming connection queue to see if any connection in + // there is ready to be accepted and delivered to the user + void check_accept_queue(); + void do_check_accept_queue(boost::system::error_code const& ec); + + aux::function m_accept_handler; + aux::function m_accept_handler2; + + // the number of half-open incoming connections this listen socket can + // hold. If this is -1, this socket is not yet listening and incoming + // connection attempts should be rejected. + int m_queue_size_limit; + + // these are incoming connection attempts. Both half-open and + // completely connected. When accepting a connection, this queue is + // checked first before waiting for a connection attempt. + using incoming_conns_t = std::vector, aux::mallocator>>; + incoming_conns_t m_incoming_conns; + + // for new-style accept, allocate socket in here just to fail early + boost::optional m_new_socket; + + // the socket to accept a connection into + tcp::socket* m_accept_into; + + // the endpoint to write the remote endpoint into when accepting + tcp::endpoint* m_remote_endpoint; + + // non copyable + acceptor(acceptor const&); + acceptor& operator=(acceptor const&); + }; + + using resolver = basic_resolver; + + friend bool operator==(tcp const& lhs, tcp const& rhs) + { return lhs.m_family == rhs.m_family; } + + friend bool operator!=(tcp const& lhs, tcp const& rhs) + { return lhs.m_family != rhs.m_family; } + + private: + // Construct with a specific family. + explicit tcp(int protocol_family) + : m_family(protocol_family) + {} + + int m_family; + }; + + extern template struct basic_resolver; + extern template struct basic_resolver; + + } // ip + + } // asio + + struct SIMULATOR_DECL simulation + { + // it calls fire() when a timer fires + friend struct high_resolution_timer; + + simulation(configuration& config); + ~simulation(); + + std::size_t run(); + + std::size_t poll(boost::system::error_code& ec); + std::size_t poll(); + + std::size_t poll_one(boost::system::error_code& ec); + std::size_t poll_one(); + + void stop(); + bool stopped() const; + void restart(); + // private interface + + void add_timer(asio::high_resolution_timer* t); + void remove_timer(asio::high_resolution_timer* t); + + boost::asio::io_context& get_internal_service() + { return m_service; } + + asio::io_context& get_io_context() + { return *m_internal_ios; } + + asio::ip::tcp::endpoint bind_socket(asio::ip::tcp::socket* socket + , asio::ip::tcp::endpoint ep + , boost::system::error_code& ec); + void unbind_socket(asio::ip::tcp::socket* socket + , asio::ip::tcp::endpoint const& ep); + void rebind_socket(asio::ip::tcp::socket* prev, asio::ip::tcp::socket* s, asio::ip::tcp::endpoint ep); + + asio::ip::udp::endpoint bind_udp_socket(asio::ip::udp::socket* socket + , asio::ip::udp::endpoint ep + , boost::system::error_code& ec); + void unbind_udp_socket(asio::ip::udp::socket* socket + , asio::ip::udp::endpoint const& ep); + void rebind_udp_socket(asio::ip::udp::socket* socket, asio::ip::udp::endpoint ep); + + std::shared_ptr internal_connect(asio::ip::tcp::socket* s + , asio::ip::tcp::endpoint const& target, boost::system::error_code& ec); + + route find_udp_socket( + asio::ip::udp::socket const& socket + , asio::ip::udp::endpoint const& ep); + + configuration& config() const { return m_config; } + + void add_io_service(asio::io_context* ios); + void remove_io_service(asio::io_context* ios); + std::vector get_all_io_services() const; + + aux::pcap* get_pcap() const { return m_pcap.get(); } + void log_pcap(char const* filename); + + private: + struct timer_compare + { + bool operator()(asio::high_resolution_timer const* lhs + , asio::high_resolution_timer const* rhs) const + { return lhs->expiry() < rhs->expiry(); } + }; + + configuration& m_config; + + std::unique_ptr m_pcap; + + // all non-expired timers + std::mutex m_timer_queue_mutex; + using timer_queue_t = std::vector>; + timer_queue_t m_timer_queue; + + // these are the io services that represent nodes on the network + std::unordered_set, std::equal_to, aux::mallocator> m_nodes; + + using listen_sockets_t = std::map; + using listen_socket_iter_t = listen_sockets_t::iterator; + listen_sockets_t m_listen_sockets; + + using udp_sockets_t = std::map; + using udp_socket_iter_t = udp_sockets_t::iterator; + udp_sockets_t m_udp_sockets; + + // used for internal timers. this is a pimpl sine our io_context is + // incomplete at this point + std::unique_ptr m_internal_ios; + + // the next port to use for an outgoing connection, where the port is not + // specified. We want this to be as unique as possible, to distinguish the + // TCP streams. + std::uint16_t m_next_bind_port = 2000; + + bool m_stopped = false; + + // underlying message queue + boost::asio::io_context m_service; + }; + + namespace asio { + + using boost::asio::async_write; + using boost::asio::async_read; + + // boost.asio compatible io_context class that simulates the network + // and time. + struct SIMULATOR_DECL io_context : boost::asio::execution_context + { + io_context(sim::simulation& sim); + io_context(sim::simulation& sim, ip::address const& ip); + io_context(sim::simulation& sim, std::vector const& ips); + io_context(std::size_t threads_hint = 0); + ~io_context(); + + // not copyable and non movable (it's not movable because we currently + // keep pointers to the io_context instances in the simulator object) + io_context(io_context const&) = delete; + io_context(io_context&&) = delete; + io_context& operator=(io_context const&) = delete; + io_context& operator=(io_context&&) = delete; + + std::size_t run(boost::system::error_code& ec); + std::size_t run(); + + std::size_t poll(boost::system::error_code& ec); + std::size_t poll(); + + std::size_t poll_one(boost::system::error_code& ec); + std::size_t poll_one(); + + void stop(); + bool stopped() const; + void restart(); + + template + void dispatch_impl(Handler handler, Allocator const& a) + { get_internal_service().get_executor().dispatch(std::move(handler), a); } + + template + void post_impl(Handler handler, Allocator const& a) + { get_internal_service().get_executor().post(std::move(handler), a); } + + template + void defer_impl(Handler handler, Allocator const& a) + { get_internal_service().get_executor().defer(std::move(handler), a); } + + // internal interface + boost::asio::io_context& get_internal_service(); + + void add_timer(high_resolution_timer* t); + void remove_timer(high_resolution_timer* t); + + ip::tcp::endpoint bind_socket(ip::tcp::socket* socket, ip::tcp::endpoint ep + , boost::system::error_code& ec); + void unbind_socket(ip::tcp::socket* socket + , ip::tcp::endpoint const& ep); + void rebind_socket(asio::ip::tcp::socket* prev, asio::ip::tcp::socket* s, asio::ip::tcp::endpoint ep); + + ip::udp::endpoint bind_udp_socket(ip::udp::socket* socket, ip::udp::endpoint ep + , boost::system::error_code& ec); + void unbind_udp_socket(ip::udp::socket* socket + , ip::udp::endpoint const& ep); + void rebind_udp_socket(asio::ip::udp::socket* socket, asio::ip::udp::endpoint ep); + + std::shared_ptr internal_connect(ip::tcp::socket* s + , ip::tcp::endpoint const& target, boost::system::error_code& ec); + + route find_udp_socket(asio::ip::udp::socket const& socket + , ip::udp::endpoint const& ep); + + route const& get_outgoing_route(ip::address ip) const + { return m_outgoing_route.find(ip)->second; } + + route const& get_incoming_route(ip::address ip) const + { return m_incoming_route.find(ip)->second; } + + int get_path_mtu(const asio::ip::address& source, const asio::ip::address& dest) const; + std::vector const& get_ips() const { return m_ips; } + + sim::simulation& sim() { return m_sim; } + + using executor_type = io_executor; + executor_type get_executor() { return executor_type(*this); } + + private: + + sim::simulation& m_sim; + std::vector m_ips; + + // these are determined by the configuration. They may include NATs and + // DSL modems (queues) + std::map m_outgoing_route; + std::map m_incoming_route; + + bool m_stopped = false; + }; + + template + void io_executor::dispatch(Handler handler, Allocator const& a) const + { m_ctx->dispatch_impl(std::move(handler), a); } + + template + void io_executor::post(Handler handler, Allocator const& a) const + { m_ctx->post_impl(std::move(handler), a); } + + template + void io_executor::defer(Handler handler, Allocator const& a) const + { m_ctx->defer_impl(std::move(handler), a); } + + template + io_executor + socket_base::get_executor() + { return io_executor(m_io_service); } + + template + route socket_base::get_incoming_route() + { + route ret = m_io_service.get_incoming_route(m_bound_to.address()); + assert(m_forwarder); + ret.append(std::static_pointer_cast(m_forwarder)); + return ret; + } + + template + route socket_base::get_outgoing_route() + { + return route(m_io_service.get_outgoing_route(m_bound_to.address())); + } + + } // asio + + // user supplied configuration of the network to simulate + struct SIMULATOR_DECL configuration + { + virtual ~configuration() {} + + // build the network + virtual void build(simulation& sim) = 0; + + // return the hops on the network packets from src to dst need to traverse + virtual route channel_route(asio::ip::address src + , asio::ip::address dst) = 0; + + // return the hops an incoming packet to ep need to traverse before + // reaching the socket (for instance a NAT) + virtual route incoming_route(asio::ip::address ip) = 0; + + // return the hops an outgoing packet from ep need to traverse before + // reaching the network (for instance a DSL modem) + virtual route outgoing_route(asio::ip::address ip) = 0; + + // return the path MTU between the two IP addresses + // For TCP sockets, this will be called once when the connection is + // established. For UDP sockets it's called for every burst of packets + // that are sent + virtual int path_mtu(asio::ip::address ip1, asio::ip::address ip2) = 0; + + // called for every hostname lookup made by the client. ``reqyestor`` is + // the node performing the lookup, ``hostname`` is the name being looked + // up. Resolve the name into addresses and fill in ``result`` or set + // ``ec`` if the hostname is not found or some other error occurs. The + // return value is the latency of the lookup. The client's callback won't + // be called until after waiting this long. + virtual chrono::high_resolution_clock::duration hostname_lookup( + asio::ip::address const& requestor + , std::string hostname + , std::vector& result + , boost::system::error_code& ec) = 0; + + virtual void clear() = 0; + }; + + struct SIMULATOR_DECL default_config : configuration + { + default_config() : m_sim(nullptr) {} + + void build(simulation& sim) override; + route channel_route(asio::ip::address src, asio::ip::address dst) override; + route incoming_route(asio::ip::address ip) override; + route outgoing_route(asio::ip::address ip) override; + int path_mtu(asio::ip::address ip1, asio::ip::address ip2) override; + chrono::high_resolution_clock::duration hostname_lookup( + asio::ip::address const& requestor + , std::string hostname + , std::vector& result + , boost::system::error_code& ec) override; + + void clear() override; + protected: + std::shared_ptr m_network; + std::map> m_incoming; + std::map> m_outgoing; + simulation* m_sim; + }; + + namespace aux + { + /* the channel can be in the following states: + 1. handshake-1 - the initiating socket has sent SYN + 2. handshake-2 - the accepting connection has sent SYN+ACK + 3. handshake-3 - the initiating connection has received the SYN+ACK and + considers the connection open, but the 3rd handshake + message is still in flight. + 4. connected - the accepting side has received the 3rd handshake + packet and considers it open + + Whenever a connection attempt is made to a listening socket, as long as + there is still space in the incoming socket queue, the accepting side + will always respond immediately and complete the handshake, then wait + until the user calls async_accept (which in this case would complete + immediately). + */ + struct SIMULATOR_DECL channel + { + channel() {} + // index 0 is the incoming route to the socket that initiated the connection. + // index 1 may be empty while the connection is half-open + route hops[2]; + + // the actual endpoint of each end of the channel + asio::ip::tcp::endpoint ep[2]; + + // observable endpoint of each side of the channel. This is not how you + // see yourself, just the other end + asio::ip::tcp::endpoint visible_ep[2]; + + // the number of bytes sent from respective direction + // this is used to simulate the TCP sequence number, so it deliberately + // is meant to wrap at 32 bits + std::uint32_t bytes_sent[2]; + + int remote_idx(asio::ip::tcp::endpoint const& self) const; + int self_idx(asio::ip::tcp::endpoint const& self) const; + }; + + } // aux + + void SIMULATOR_DECL dump_network_graph(simulation const& s, const std::string& filename); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // SIMULATOR_HPP_INCLUDED + diff --git a/simulation/libsimulator/include/simulator/sink.hpp b/simulation/libsimulator/include/simulator/sink.hpp new file mode 100644 index 0000000..35f19b3 --- /dev/null +++ b/simulation/libsimulator/include/simulator/sink.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SINK_HPP_INCLUDED +#define SINK_HPP_INCLUDED + +#include "simulator/config.hpp" +#include + +namespace sim { + +namespace aux { + struct packet; +} + + // this is an interface for somthing that can accept incoming packets, + // such as queues, sockets, NATs and TCP congestion windows + struct SIMULATOR_DECL sink + { + virtual void incoming_packet(aux::packet p) = 0; + + // used for visualization + virtual std::string label() const = 0; + + virtual std::string attributes() const { return "shape=box"; } + virtual ~sink() = default; + }; + +} // sim + +#endif + diff --git a/simulation/libsimulator/include/simulator/sink_forwarder.hpp b/simulation/libsimulator/include/simulator/sink_forwarder.hpp new file mode 100644 index 0000000..318f0a8 --- /dev/null +++ b/simulation/libsimulator/include/simulator/sink_forwarder.hpp @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SINK_FORWARDER_HPP_INCLUDED +#define SINK_FORWARDER_HPP_INCLUDED + +#include "simulator/config.hpp" +#include "simulator/sink.hpp" + +namespace sim { namespace aux { + + struct packet; + + struct SIMULATOR_DECL sink_forwarder final : sink + { + sink_forwarder(sink* dst); + void incoming_packet(packet p) override; + std::string label() const override; + void reset(sink* s = nullptr); + + private: + sink* m_dst; + }; + +}} // sim + +#endif + diff --git a/simulation/libsimulator/include/simulator/socks_server.hpp b/simulation/libsimulator/include/simulator/socks_server.hpp new file mode 100644 index 0000000..fb37397 --- /dev/null +++ b/simulation/libsimulator/include/simulator/socks_server.hpp @@ -0,0 +1,202 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SOCKS_SERVER_HPP_INCLUDED +#define SOCKS_SERVER_HPP_INCLUDED + +#include "simulator/simulator.hpp" + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#if __GNUC__ >= 9 +#pragma GCC diagnostic ignored "-Wparentheses" +#endif +#endif + +#include + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4251: X: class Y needs to have dll-interface to be used by clients of struct +#pragma warning( disable : 4251) +#endif + +namespace sim +{ + +enum socks_flag +{ + // when this flag is set, the proxy will close the client connection + // immediately after sending the response to a UDP ASSOCIATE command + disconnect_udp_associate = 1, + + // when this flag is set, the reponse to UDP ASSOCIATE will contain an empty + // hostname, rather than the relay IP address + udp_associate_respond_empty_hostname = 2 +}; + +struct SIMULATOR_DECL socks_connection : std::enable_shared_from_this +{ + socks_connection(asio::io_context& ios, int version, std::array& cmd_counts + , std::uint32_t flags, int& bind_port); + + asio::ip::tcp::socket& socket() { return m_client_connection; } + + void start(); + +private: + + void on_handshake1(boost::system::error_code const& ec, size_t bytes_transferred); + void on_handshake2(boost::system::error_code const& ec, size_t bytes_transferred); + void on_handshake3(boost::system::error_code const& ec, size_t bytes_transferred); + void on_request1(boost::system::error_code const& ec, size_t bytes_transferred); + void on_request2(boost::system::error_code const& ec, size_t bytes_transferred); + + void on_write(boost::system::error_code const& ec, size_t bytes_transferred + , bool close); + void close_connection(); + + int format_response(asio::ip::address const& addr, int port, int response); + int format_hostname_response(char const* hostname, int port, int response); + + void on_connected(boost::system::error_code const& ec); + void on_request_domain_name(boost::system::error_code const& ec, size_t bytes_transferred); + void on_request_domain_lookup(boost::system::error_code const& ec + , const asio::ip::tcp::resolver::results_type ips); + + void open_forward_connection(asio::ip::tcp::endpoint const& target); + void bind_connection(asio::ip::tcp::endpoint const& target); + void start_accept(boost::system::error_code const& ec); + + void udp_associate(asio::ip::tcp::endpoint const& target); + void on_read_udp(boost::system::error_code const& ec, std::size_t bytes_transferred); + void wait_for_eof(boost::system::error_code const& ec, std::size_t bytes_transferred); + + void on_server_receive(boost::system::error_code const& ec + , std::size_t bytes_transferred); + void on_server_forward(boost::system::error_code const& ec + , size_t bytes_transferred); + + void on_client_receive(boost::system::error_code const& ec + , std::size_t bytes_transferred); + void on_client_forward(boost::system::error_code const& ec + , size_t bytes_transferred); + + char const* command() const; + + int& m_bind_port; + + asio::io_context& m_ios; + + asio::ip::udp::resolver m_udp_resolver; + + asio::ip::tcp::resolver m_resolver; + + boost::bimap m_name_mapping; + + // this is the SOCKS client connection, i.e. the client connecting to us and + // being forwarded + asio::ip::tcp::socket m_client_connection; + + // this is the connection to the server the socks client is being forwarded + // to + asio::ip::tcp::socket m_server_connection; + asio::ip::tcp::acceptor m_bind_socket; + + asio::ip::udp::socket m_udp_associate; + asio::ip::udp::endpoint m_udp_associate_ep; + asio::ip::udp::endpoint m_udp_from; + + std::array m_udp_buffer; + + // receive buffer for data going out, i.e. client -> proxy (us) -> server + char m_out_buffer[65536]; + // buffer size + int m_num_out_bytes; + + // receive buffer for data coming in, i.e. server -> proxy (us) -> client + char m_in_buffer[65536]; + // buffer size + int m_num_in_bytes; + + // set to true when shutting down + bool m_close = false; + + // the SOCKS protocol version (4 or 5) + const int m_version; + + int m_command; + + std::array& m_cmd_counts; + + std::uint32_t const m_flags; +}; + +// This is a very simple socks4 and 5 server that only supports a single +// concurrent connection +struct SIMULATOR_DECL socks_server +{ + socks_server(asio::io_context& ios, unsigned short listen_port + , int version = 5, std::uint32_t flags = 0); + + void stop(); + + void bind_start_port(int const port) { m_bind_port = port; } + + // return the number of CONNECT, BIND and UDP_ASSOCIATE commands the proxy + // has received + std::array cmd_counts() const + { return m_cmd_counts; } + +private: + + void on_accept(boost::system::error_code const& ec); + + asio::io_context& m_ios; + + asio::ip::tcp::acceptor m_listen_socket; + + std::shared_ptr m_conn; + + asio::ip::tcp::endpoint m_ep; + + int m_bind_port = 2048; + + // set to true when shutting down + bool m_close = false; + + // the SOCKS protocol version (4 or 5) + const int m_version; + + std::array m_cmd_counts; + + std::uint32_t const m_flags; +}; + +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif + diff --git a/simulation/libsimulator/include/simulator/utils.hpp b/simulation/libsimulator/include/simulator/utils.hpp new file mode 100644 index 0000000..a45bb03 --- /dev/null +++ b/simulation/libsimulator/include/simulator/utils.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef UTILS_HPP_INCLUDED +#define UTILS_HPP_INCLUDED + +#include "simulator/simulator.hpp" + +namespace sim +{ + +// shortcut for creating a timer with a timeout and action +struct timer +{ + timer(simulation& sim, chrono::high_resolution_clock::duration timeout + , aux::function&& f) + : m_ios(sim, asio::ip::address_v4()) + , m_timer(m_ios) + { + m_timer.expires_after(timeout); + m_timer.async_wait(std::move(f)); + } + +private: + sim::asio::io_context m_ios; + sim::asio::high_resolution_timer m_timer; +}; + +} // sim + +#endif + diff --git a/simulation/libsimulator/src/acceptor.cpp b/simulation/libsimulator/src/acceptor.cpp new file mode 100644 index 0000000..c937628 --- /dev/null +++ b/simulation/libsimulator/src/acceptor.cpp @@ -0,0 +1,379 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/packet.hpp" + +#include +#include + +#include + +using boost::beast::bind_handler; + +typedef sim::chrono::high_resolution_clock::time_point time_point; +typedef sim::chrono::high_resolution_clock::duration duration; + +namespace sim { +namespace asio { +namespace ip { + + tcp::acceptor::acceptor(io_context& ios) + : socket(ios) + , m_queue_size_limit(-1) + {} + + tcp::acceptor::acceptor(acceptor&&) = default; + + tcp::acceptor::~acceptor() + { + boost::system::error_code ec; + close(ec); + } + + void tcp::acceptor::listen(int qs) + { + boost::system::error_code ec; + listen(qs, ec); + if (ec) throw boost::system::system_error(ec); + } + + void tcp::acceptor::listen(int qs, boost::system::error_code& ec) + { + if (qs == -1) qs = 20; + + if (!m_open) + { + ec = error::bad_descriptor; + return; + } + if (m_bound_to == ip::tcp::endpoint()) + { + ec = error::invalid_argument; + return; + } + + m_queue_size_limit = qs; + ec.clear(); + } + + void tcp::acceptor::close(boost::system::error_code& ec) + { + m_queue_size_limit = -1; + cancel(ec); + socket::close(ec); + } + + void tcp::acceptor::close() + { + if (m_accept_handler) + { + post(m_io_service, bind_handler(std::exchange(m_accept_handler, nullptr) + , boost::system::error_code(error::operation_aborted))); + } + if (m_accept_handler2) + { + post(m_io_service, [&, h = std::exchange(m_accept_handler2, nullptr)] () mutable { + h(boost::system::error_code(error::operation_aborted) + , ip::tcp::socket(m_io_service)); + }); + } + } + + void tcp::acceptor::cancel(boost::system::error_code& ec) + { + ec.clear(); + if (m_accept_handler) + { + try + { + post(m_io_service, bind_handler(std::exchange(m_accept_handler, nullptr) + , boost::system::error_code(error::operation_aborted))); + } + catch (std::bad_alloc const&) + { + ec = error::no_memory; + } + catch (std::exception const&) + { + ec = error::no_memory; + } + } + if (m_accept_handler2) + { + try + { + post(m_io_service, [&, h = std::exchange(m_accept_handler2, nullptr)] () mutable { + h(boost::system::error_code(error::operation_aborted) + , ip::tcp::socket(m_io_service)); + }); + } + catch (std::bad_alloc const&) + { + ec = error::no_memory; + } + catch (std::exception const&) + { + ec = error::no_memory; + } + } + } + + void tcp::acceptor::cancel() + { + boost::system::error_code ec; + cancel(ec); + if (ec) throw boost::system::system_error(ec); + } + + void tcp::acceptor::async_accept(ip::tcp::socket& peer + , aux::function h) + { + // TODO: assert that the io_context we use is the same as the one peer use + if (peer.is_open()) + { + boost::system::error_code ec; + peer.close(ec); + } + + if (m_accept_handler) + { + m_accept_into = nullptr; + m_remote_endpoint = nullptr; + post(m_io_service, bind_handler(std::exchange(m_accept_handler, nullptr) + , boost::system::error_code(error::operation_aborted))); + } + if (m_accept_handler2) + { + m_accept_into = nullptr; + m_remote_endpoint = nullptr; + post(m_io_service, [&, h = std::exchange(m_accept_handler2, nullptr)] () mutable { + h(boost::system::error_code(error::operation_aborted) + , ip::tcp::socket(m_io_service)); + }); + } + m_accept_handler = std::move(h); + m_accept_into = &peer; + m_remote_endpoint = nullptr; + + check_accept_queue(); + } + + void tcp::acceptor::async_accept(ip::tcp::socket& peer + , ip::tcp::endpoint& peer_endpoint + , aux::function h) + { + if (peer.is_open()) + { + boost::system::error_code ec; + peer.close(ec); + } + + if (m_accept_handler) + { + post(m_io_service, bind_handler(std::exchange(m_accept_handler, nullptr) + , boost::system::error_code(error::operation_aborted))); + } + if (m_accept_handler2) + { + post(m_io_service, [&, h = std::exchange(m_accept_handler2, nullptr)] () mutable { + h(boost::system::error_code(error::operation_aborted) + , ip::tcp::socket(m_io_service)); + }); + } + m_accept_handler = std::move(h); + m_accept_into = &peer; + m_remote_endpoint = &peer_endpoint; + + check_accept_queue(); + } + + void tcp::acceptor::async_accept(aux::function h) + { + m_remote_endpoint = nullptr; + if (m_accept_handler) + { + m_accept_into = nullptr; + post(m_io_service, bind_handler(std::exchange(m_accept_handler, nullptr) + , boost::system::error_code(error::operation_aborted))); + } + if (m_accept_handler2) + { + m_accept_into = nullptr; + post(m_io_service, [&, h = std::exchange(m_accept_handler2, nullptr)] () mutable { + h(boost::system::error_code(error::operation_aborted) + , ip::tcp::socket(m_io_service)); + }); + } + m_new_socket.emplace(m_io_service); + m_accept_handler2 = std::move(h); + m_accept_into = &*m_new_socket; + + check_accept_queue(); + } + + void tcp::acceptor::do_check_accept_queue(boost::system::error_code const& ec) + { + if (ec) return; + check_accept_queue(); + } + + void tcp::acceptor::incoming_packet(aux::packet p) + { + switch (p.type) + { + case aux::packet::type_t::syn: + m_incoming_conns.push_back(p.channel); + check_accept_queue(); + return; + case aux::packet::type_t::error: + assert(false); // something is not wired up correctly + if (m_accept_handler) + { + m_accept_into = nullptr; + m_remote_endpoint = nullptr; + post(m_io_service, bind_handler(std::exchange(m_accept_handler, nullptr) + , boost::system::error_code(error::operation_aborted))); + } + if (m_accept_handler2) + { + m_accept_into = nullptr; + m_remote_endpoint = nullptr; + post(m_io_service, [&, h = std::exchange(m_accept_handler2, nullptr)] () mutable { + h(boost::system::error_code(error::operation_aborted) + , ip::tcp::socket(m_io_service)); + }); + } + return; + default: + // if this happens, it implies that an incoming connection sent + // payload before receiving a syn_ack. Alternatively that the + // acceptor sent the syn_ack but still left the last-hop in the + // incoming route to point to this socket, instead of the + // accepted-into socket + assert(false); + return; + } + } + + void tcp::acceptor::check_accept_queue() + { + if (!is_open()) + { + // if the acceptor socket is closed. Any potential socket in the queue + // should be closed too. + for (auto const& incoming : m_incoming_conns) + { + aux::packet p; + p.from = asio::ip::udp::endpoint( + m_bound_to.address(), m_bound_to.port()); + p.type = aux::packet::type_t::error; + p.ec = boost::system::error_code(error::connection_reset); + p.overhead = 28; + p.hops = incoming->hops[0]; + + forward_packet(std::move(p)); + } + m_incoming_conns.clear(); + + if (m_accept_handler) + { + m_accept_into = nullptr; + m_remote_endpoint = nullptr; + post(m_io_service, bind_handler(std::exchange(m_accept_handler, nullptr) + , boost::system::error_code(error::operation_aborted))); + } + if (m_accept_handler2) + { + m_accept_into = nullptr; + m_remote_endpoint = nullptr; + post(m_io_service, [&, h = std::exchange(m_accept_handler2, nullptr)] () mutable { + h(boost::system::error_code(error::operation_aborted) + , ip::tcp::socket(m_io_service)); + }); + } + } + + // if the user is not waiting for an incoming connection, there's no point + // in checking the queue + if (!m_accept_handler && !m_accept_handler2) return; + + if (m_incoming_conns.empty()) return; + + std::shared_ptr c = std::move(m_incoming_conns.front()); + m_incoming_conns.erase(m_incoming_conns.begin()); + + // this was initiated at least one 3-way handshake ago. + // we can pick it up and consider it connected + if (m_remote_endpoint) *m_remote_endpoint = c->ep[0]; + m_remote_endpoint = nullptr; + + boost::system::error_code ec; + // if the acceptor socket is closed. Any potential socket in the queue + m_accept_into->internal_connect(m_bound_to, c, ec); + + // notify the other end + aux::packet p; + p.from = asio::ip::udp::endpoint(m_bound_to.address(), m_bound_to.port()); + if (ec) + { + c->hops[1] = route(); + p.type = aux::packet::type_t::error; + p.ec = ec; + } + else + { + // TODO: extend pcap logging to include SYN+ACK packets + p.type = aux::packet::type_t::syn_ack; + } + p.channel = c; + p.overhead = 28; + p.hops = p.channel->hops[0]; + + forward_packet(std::move(p)); + + try + { + if (m_accept_handler) + { + post(m_io_service, bind_handler(std::exchange(m_accept_handler, nullptr), ec)); + } + else if (m_accept_handler2) + { + post(m_io_service, bind_handler(std::exchange(m_accept_handler2, nullptr) + , ec, std::move(*m_accept_into))); + } + } + catch (...) + { + m_new_socket.reset(); + m_accept_into = nullptr; + throw; + } + m_new_socket.reset(); + m_accept_into = nullptr; + } + + bool tcp::acceptor::internal_is_listening() + { + return m_queue_size_limit > 0; + } +} +} +} + diff --git a/simulation/libsimulator/src/default_config.cpp b/simulation/libsimulator/src/default_config.cpp new file mode 100644 index 0000000..934983f --- /dev/null +++ b/simulation/libsimulator/src/default_config.cpp @@ -0,0 +1,106 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/queue.hpp" +#include + +typedef sim::chrono::high_resolution_clock::time_point time_point; +typedef sim::chrono::high_resolution_clock::duration duration; +using sim::asio::ip::address_v4; +using sim::asio::ip::address_v6; +using sim::asio::ip::address; +using sim::chrono::milliseconds; +using sim::chrono::duration_cast; + +namespace sim { + + void default_config::build(simulation& sim) + { + // 0 bandwidth and 0 queue means infinite. The network itself only adds + // 50 ms latency + m_network = std::make_shared(std::ref(sim.get_io_context()) + , 0, duration_cast(milliseconds(30)), 0, "network"); + m_sim = ∼ + } + + void default_config::clear() + { + m_network.reset(); + m_outgoing.clear(); + m_incoming.clear(); + } + + route default_config::channel_route( + asio::ip::address /* src */ + , asio::ip::address /* dst */) + { + return route().append(m_network); + } + + route default_config::incoming_route(asio::ip::address ip) + { + // incoming download rate is 800kB/s with a 200 kB queue + // and 1 ms forwarding delay + auto it = m_incoming.find(ip); + if (it != m_incoming.end()) return route().append(it->second); + it = m_incoming.insert(it, std::make_pair(ip, std::make_shared( + std::ref(m_sim->get_io_context()), 800 * 1000 + , duration_cast(milliseconds(1)), 200 * 1000, "DSL modem in"))); + return route().append(it->second); + } + + int default_config::path_mtu( + asio::ip::address /* ip1 */ + , asio::ip::address /* ip2 */) + { + return 1475; + } + + // return the hops an outgoing packet from ep need to traverse before + // reaching the network (for instance a DSL modem) + route default_config::outgoing_route(asio::ip::address ip) + { + // outgoing upload rate is 200kB/s with a 200 kB queue + // and 1 ms forwarding delay + auto it = m_outgoing.find(ip); + if (it != m_outgoing.end()) return route().append(it->second); + it = m_outgoing.insert(it, std::make_pair(ip, std::make_shared( + std::ref(m_sim->get_io_context()), 200 * 1000 + , duration_cast(milliseconds(1)), 200 * 1000, "DSL modem out"))); + return route().append(it->second); + } + + duration default_config::hostname_lookup( + asio::ip::address const& /* requestor */ + , std::string hostname + , std::vector& result + , boost::system::error_code& ec) + { + if (hostname == "localhost") + { + result = { asio::ip::make_address_v6("::1") + , asio::ip::make_address_v4("127.0.0.1") }; + return duration_cast(chrono::microseconds(1)); + } + + ec = boost::system::error_code(asio::error::host_not_found); + return duration_cast(chrono::milliseconds(100)); + } +} + diff --git a/simulation/libsimulator/src/high_resolution_clock.cpp b/simulation/libsimulator/src/high_resolution_clock.cpp new file mode 100644 index 0000000..c942efc --- /dev/null +++ b/simulation/libsimulator/src/high_resolution_clock.cpp @@ -0,0 +1,51 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" + +#include + +#include "simulator/push_warnings.hpp" +#include +#include "simulator/pop_warnings.hpp" + +namespace sim { namespace chrono { + namespace { + + // this is the global simulation timer + high_resolution_clock::time_point g_simulation_time; + } + + high_resolution_clock::time_point high_resolution_clock::now() + { + return g_simulation_time; + } + + void high_resolution_clock::fast_forward(high_resolution_clock::duration d) + { + g_simulation_time += d; + } + + void reset_clock() + { + g_simulation_time = high_resolution_clock::time_point{}; + } + +} // chrono +} // sim + diff --git a/simulation/libsimulator/src/high_resolution_timer.cpp b/simulation/libsimulator/src/high_resolution_timer.cpp new file mode 100644 index 0000000..6542248 --- /dev/null +++ b/simulation/libsimulator/src/high_resolution_timer.cpp @@ -0,0 +1,141 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/handler_allocator.hpp" + +#include +#include + +namespace sim +{ + + namespace asio { + + high_resolution_timer::high_resolution_timer(io_context& ioc) + : m_expiration_time(time_type()) + , m_io_service(&ioc) + , m_expired(true) + { + } + + high_resolution_timer::high_resolution_timer(io_context& ioc, + const time_type& expiry_time) + : m_expiration_time(time_type()) + , m_io_service(&ioc) + , m_expired(true) + { + expires_at(expiry_time); + } + + high_resolution_timer::high_resolution_timer(io_context& ioc, + const duration_type& expiry_time) + : m_expiration_time(time_type()) + , m_io_service(&ioc) + , m_expired(true) + { + expires_after(expiry_time); + } + + high_resolution_timer::~high_resolution_timer() + { + cancel(); + } + + std::size_t high_resolution_timer::cancel() + { + if (m_expired) return 0; + m_expired = true; + m_io_service->remove_timer(this); + if (!m_handler) return 0; + fire(boost::asio::error::operation_aborted); + return 1; + } + + std::size_t high_resolution_timer::cancel_one() + { + // TODO: support multiple handlers + return cancel(); + } + + high_resolution_timer::time_type high_resolution_timer::expiry() const + { return m_expiration_time; } + + std::size_t high_resolution_timer::expires_at(high_resolution_timer::time_type const& expiry_time) + { + std::size_t ret = cancel(); + m_expiration_time = expiry_time; + m_expired = false; + m_io_service->add_timer(this); + return ret; + } + + std::size_t high_resolution_timer::expires_after(const duration_type& expiry_time) + { + std::size_t ret = cancel(); + m_expiration_time = chrono::high_resolution_clock::now() + expiry_time; + m_expired = false; + m_io_service->add_timer(this); + return ret; + } + + void high_resolution_timer::wait() + { + assert(false && "can't use synchcronous calls in simulator"); + boost::system::error_code ec; + wait(ec); + } + + void high_resolution_timer::wait(boost::system::error_code&) + { + assert(false && "can't use synchcronous calls in simulator"); + time_type now = chrono::high_resolution_clock::now(); + if (now >= m_expiration_time) return; + chrono::high_resolution_clock::fast_forward(m_expiration_time - now); + } + + void high_resolution_timer::async_wait(aux::function handler) + { + // TODO: support multiple handlers + assert(!m_handler); + m_handler = std::move(handler); + if (m_expired) + { + fire(boost::system::error_code()); + return; + } + } + + void high_resolution_timer::fire(boost::system::error_code ec) + { + m_expired = true; + if (!m_handler) return; + auto h = std::move(m_handler); + m_handler = nullptr; + post(*m_io_service, make_malloc(std::bind(std::move(h), ec))); + } + + high_resolution_timer::executor_type high_resolution_timer::get_executor() + { + return (*m_io_service).get_executor(); + } + + } // asio + +} // sim + diff --git a/simulation/libsimulator/src/http_proxy.cpp b/simulation/libsimulator/src/http_proxy.cpp new file mode 100644 index 0000000..82aa80d --- /dev/null +++ b/simulation/libsimulator/src/http_proxy.cpp @@ -0,0 +1,362 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/http_proxy.hpp" +#include "simulator/http_server.hpp" // for helper functions + +#include +#include // for printf + +using namespace sim::asio; +using namespace sim::asio::ip; +using namespace std::placeholders; + +using boost::system::error_code; + +namespace sim +{ + using namespace aux; + + http_proxy::http_proxy(io_context& ios, unsigned short const listen_port) + : m_resolver(ios) + , m_listen_socket(ios) + , m_client_connection(ios) + , m_server_connection(ios) + , m_writing_to_server(false) + , m_num_client_in_bytes(0) + , m_num_server_out_bytes(0) + , m_num_in_bytes(0) + , m_close(false) + { + address local_ip = ios.get_ips().front(); + if (local_ip.is_v4()) + { + m_listen_socket.open(tcp::v4()); + m_listen_socket.bind(tcp::endpoint(address_v4::any(), listen_port)); + } + else + { + m_listen_socket.open(tcp::v6()); + m_listen_socket.bind(tcp::endpoint(address_v6::any(), listen_port)); + } + m_listen_socket.listen(); + + m_listen_socket.async_accept(m_client_connection, m_ep + , std::bind(&http_proxy::on_accept, this, _1)); + } + + void http_proxy::on_accept(error_code const& ec) + { + if (ec == asio::error::operation_aborted) + return; + + if (ec) + { + std::printf("http_proxy::on_accept: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + std::printf("http_proxy accepted connection from: %s : %d\n", + m_ep.address().to_string().c_str(), m_ep.port()); + + // read http request + m_client_connection.async_read_some(asio::buffer( + &m_client_in_buffer[0], sizeof(m_client_in_buffer)) + , std::bind(&http_proxy::on_read_request, this, _1, _2)); + } + + void http_proxy::on_read_request(error_code const& ec, size_t bytes_transferred) try + { + if (ec) + { + std::printf("http_proxy::on_read_request: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + m_num_client_in_bytes += int(bytes_transferred); + + // scan for end of HTTP request + int req_len = find_request_len(m_client_in_buffer, m_num_client_in_bytes); + while (req_len >= 0) + { + // parse request from [0, eor), connect to target server and forward + // the request. + http_request const req = parse_request(m_client_in_buffer, req_len); + forward_request(req); + + // pop this request from the receive buffer + memmove(m_client_in_buffer, m_client_in_buffer + req_len + , m_num_client_in_bytes - req_len); + m_num_client_in_bytes -= req_len; + + // is there another request in the buffer? + req_len = find_request_len(m_client_in_buffer, m_num_client_in_bytes); + } + + // read more from the client + m_client_connection.async_read_some(asio::buffer( + &m_client_in_buffer[m_num_client_in_bytes] + , sizeof(m_client_in_buffer) - m_num_client_in_bytes) + , std::bind(&http_proxy::on_read_request, this, _1, _2)); + } + catch (std::runtime_error& e) + { + std::printf("http_proxy::on_read_request() failed: %s\n" + , e.what()); + close_connection(); + } + + void http_proxy::forward_request(http_request const& req) + { + std::string out_request; + out_request = req.method; + out_request += ' '; + if (req.req.compare(0, 7, "http://") != 0) + { + std::printf("http_proxy::forward_request: expected full URL in request, got: %s\n" + , req.req.c_str()); + throw std::runtime_error("invalid request"); + } + + std::string::size_type const path_start = req.req.find_first_of('/', 7); + if (path_start == std::string::npos) out_request += '/'; + else out_request.append(req.req, path_start, std::string::npos); + out_request += " HTTP/1.1\r\n"; + + std::string::size_type const host_end = req.req.substr(0, path_start).find_last_of(':'); + + std::string host = req.req.substr(7, (host_end != std::string::npos && host_end > 7) + ? host_end - 7 : path_start - 7); + + // if the hostname is an IPv6 address, strip the brackets around it to + // make it parse correctly + if (host.size() >= 2 && host.front() == '[' && host.back() == ']') + host = host.substr(1, host.size() - 2); + + int const port = host_end == std::string::npos || host_end <= 7 ? 80 + : atoi(req.req.substr(host_end + 1, path_start).c_str()); + assert(port >= 0 && port < 0xffff); + + bool found_host = false; + for (auto const& h : req.headers) + { + if (h.first == "host") found_host = true; + + out_request += h.first; + out_request += ": "; + out_request += h.second; + out_request += "\r\n"; + } + if (!found_host) + { + out_request += "host: "; + out_request += host; + out_request += "\r\n"; + } + out_request += "\r\n"; + + if (m_num_server_out_bytes + out_request.size() > sizeof(m_server_out_buffer)) + { + std::printf("http_proxy: Too many queued server requests: %d bytes\n" + , int(m_num_server_out_bytes + out_request.size())); + throw std::runtime_error("pipeline too deep"); + } + memmove(&m_server_out_buffer[m_num_server_out_bytes] + , out_request.data(), out_request.size()); + m_num_server_out_bytes += int(out_request.size()); + + if (!m_server_connection.is_open()) + { + boost::system::error_code err; + tcp::endpoint target(make_address(host.c_str(), err) + , static_cast(port)); + if (err) + { + char port_str[10]; + std::snprintf(port_str, sizeof(port_str), "%d", port); + m_resolver.async_resolve(host, port_str + , std::bind(&http_proxy::on_domain_lookup, this, _1, _2)); + return; + } + open_forward_connection(target); + return; + } + + // TODO: make sure we're connecting/connected to the same (host, port) + // that this request is for. Don't support multiple servers + + write_server_send_buffer(); + } + + void http_proxy::on_domain_lookup(boost::system::error_code const& ec + , const asio::ip::tcp::resolver::results_type ips) + { + if (ec || ips.empty()) + { + if (ec) + { + std::printf("http_proxy::on_domain_lookup: (%d) %s\n" + , ec.value(), ec.message().c_str()); + } + else + { + std::printf("http_proxy::on_request_domain_lookup: empty response\n"); + } + error(503, "Resource Temporarily Unavailable"); + return; + } + open_forward_connection(ips.front().endpoint()); + } + + void http_proxy::open_forward_connection(const asio::ip::tcp::endpoint& target) + { + m_server_connection.open(target.protocol()); + + std::printf("http_proxy: async_connect: %s:%d\n" + , target.address().to_string().c_str(), target.port()); + m_server_connection.async_connect(target + , std::bind(&http_proxy::on_connected, this, _1)); + } + + void http_proxy::error(int code, char const* message) + { + std::string send_buffer = send_response(code, message); + memcpy(m_in_buffer, send_buffer.data(), send_buffer.size()); + asio::async_write(m_client_connection, asio::buffer( + &m_in_buffer[0], send_buffer.size()) + , std::bind(&http_proxy::close_connection, this)); + } + + void http_proxy::on_connected(boost::system::error_code const& ec) + { + if (ec) + { + std::printf("http_proxy::on_connected() connection failed: %s\n", ec.message().c_str()); + m_server_connection.close(); + error(503, "Service Temporarily Unavailable"); + return; + } + + std::printf("http_proxy: connected\n"); + + write_server_send_buffer(); + + m_server_connection.async_read_some( + asio::buffer(m_in_buffer, sizeof(m_in_buffer)) + , std::bind(&http_proxy::on_server_receive, this, _1, _2)); + } + + void http_proxy::write_server_send_buffer() + { + if (m_writing_to_server) return; + m_writing_to_server = true; + m_server_connection.async_write_some(asio::buffer( + &m_server_out_buffer[0], m_num_server_out_bytes) + , std::bind(&http_proxy::on_server_write, this, _1, _2)); + } + + void http_proxy::on_server_write(error_code const& ec, size_t bytes_transferred) + { + m_writing_to_server = false; + if (ec) + { + std::printf("http_proxy::on_server_write: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + memmove(&m_server_out_buffer[0], &m_server_out_buffer[bytes_transferred] + , m_num_server_out_bytes - bytes_transferred); + m_num_server_out_bytes -= int(bytes_transferred); + + if (m_num_server_out_bytes > 0) + write_server_send_buffer(); + } + + // we received some data from the server, forward it to the server + void http_proxy::on_server_receive(boost::system::error_code const& ec + , std::size_t bytes_transferred) + { + if (ec) + { + std::printf("http_proxy: error reading from server: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + asio::async_write(m_client_connection, asio::buffer(&m_in_buffer[0], bytes_transferred) + , std::bind(&http_proxy::on_server_forward, this, _1, _2)); + } + + void http_proxy::on_server_forward(error_code const& ec + , size_t) + { + if (ec) + { + std::printf("http_proxy: error writing to client: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + m_server_connection.async_read_some( + sim::asio::buffer(m_in_buffer, sizeof(m_in_buffer)) + , std::bind(&http_proxy::on_server_receive, this, _1, _2)); + } + + void http_proxy::stop() + { + m_close = true; + m_listen_socket.close(); + } + + void http_proxy::close_connection() + { + m_num_client_in_bytes = 0; + m_num_server_out_bytes = 0; + m_num_in_bytes = 0; + + error_code err; + m_client_connection.close(err); + if (err) + { + std::printf("http_proxy::close: failed to close client connection (%d) %s\n" + , err.value(), err.message().c_str()); + } + m_server_connection.close(err); + if (err) + { + std::printf("http_proxy::close: failed to close server connection (%d) %s\n" + , err.value(), err.message().c_str()); + } + + if (m_close) return; + + // now we can accept another connection + m_listen_socket.async_accept(m_client_connection, m_ep + , std::bind(&http_proxy::on_accept, this, _1)); + } +} + diff --git a/simulation/libsimulator/src/http_server.cpp b/simulation/libsimulator/src/http_server.cpp new file mode 100644 index 0000000..86a3c4a --- /dev/null +++ b/simulation/libsimulator/src/http_server.cpp @@ -0,0 +1,393 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/http_server.hpp" + +#include +#include // for printf + +using namespace sim::asio; +using namespace sim::asio::ip; +using namespace std::placeholders; + +using boost::system::error_code; + +namespace sim +{ + using namespace aux; + + namespace { + char const* find(char const* hay, int const hsize + , char const* needle, int const nsize) + { + for (int i = 0; i < hsize - nsize + 1; ++i) + { + if (memcmp(hay + i, needle, nsize) == 0) return hay + i; + } + return nullptr; + } + } + + std::string trim(std::string s) + { + if (s.empty()) return s; + + int start = 0; + int end = int(s.size()); + while (strchr(" \r\n\t", s[start]) != NULL && start < end) + { + ++start; + } + + while (strchr(" \r\n\t", s[end-1]) != NULL && end > start) + { + --end; + } + return s.substr(start, end - start); + } + + std::string lower_case(std::string s) + { + std::string ret; + std::transform(s.begin(), s.end(), std::back_inserter(ret) + , [](char c) { return static_cast(tolower(c)); } ); + return ret; + } + + std::string normalize(const std::string& s) + { + std::vector elements; + char const* start = s.c_str(); + if (*start == '/') ++start; + char const* slash = strchr(start, '/'); + while (slash != NULL) + { + std::string element(start, slash - start); + if (element != "..") + { + elements.push_back(element); + } else if (!elements.empty()) + { + elements.erase(elements.end()-1); + } + start = slash + 1; + slash = strchr(start, '/'); + } + elements.push_back(start); + + std::string ret; + for (auto const& e : elements) + { + ret += '/'; + ret += e; + } + + return ret; + } + + // TODO: extra_header should be a std::vector + std::string send_response(int code, char const* status_message + , int len, char const** extra_header) + { + std::string ret = "HTTP/1.1 " + std::to_string(code) + " " + status_message + "\r\n"; + + ret += "content-length: " + std::to_string(len) + "\r\n"; + + if (extra_header) + { + ret += extra_header[0]; + ret += extra_header[1]; + ret += extra_header[2]; + ret += extra_header[3]; + } + ret += "\r\n"; + + return ret; + } + + http_server::http_server(io_context& ios, unsigned short listen_port, int flags) + : m_ios(ios) + , m_listen_socket(ios) + , m_connection(ios) + , m_bytes_used(0) + , m_close(false) + , m_flags(flags) + { + address local_ip = ios.get_ips().front(); + if (local_ip.is_v4()) + { + m_listen_socket.open(tcp::v4()); + m_listen_socket.bind(tcp::endpoint(address_v4::any(), listen_port)); + } + else + { + m_listen_socket.open(tcp::v6()); + m_listen_socket.bind(tcp::endpoint(address_v6::any(), listen_port)); + } + m_listen_socket.listen(); + + m_listen_socket.async_accept(m_connection, m_ep + , std::bind(&http_server::on_accept, this, _1)); + } + + void http_server::on_accept(error_code const& ec) + { + if (ec) + { + std::printf("http_server::on_accept: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + std::printf("http_server accepted connection from: %s : %d\n", + m_ep.address().to_string().c_str(), m_ep.port()); + + read(); + } + + void http_server::register_handler(std::string const& path, handler_t h) + { + m_handlers[path] = std::move(h); + } + + void http_server::register_content(std::string const& path + , std::int64_t const size, generator_t gen) + { + m_handlers[path] = [gen,size](std::string, std::string, std::map& hdr) + { + std::int64_t start = 0; + std::int64_t end = size; + + auto it = hdr.find("range"); + bool const range_req = it != hdr.end(); + if (range_req) + { + std::string range = it->second; + // skip "bytes " + range = range.substr(range.find_first_of('=') + 1); + start = std::stoll(range.substr(0, range.find('-'))); + end = std::stoll(range.substr(range.find_first_of('-') + 1)) + 1; + } + + std::string header = "Content-Range: bytes " + std::to_string(start) + + "-" + std::to_string(end-1) + "/" + std::to_string(end-start) + "\r\n"; + char const* extra_headers[4] = { header.c_str(), "", "", ""}; + + return sim::send_response(range_req ? 206 : 200 + , range_req ? "Partial Content" : "OK" + , int(end - start), range_req ? extra_headers : nullptr) + + gen(start, end - start); + }; + } + + void http_server::register_redirect(std::string const& path, std::string const& target) + { + m_handlers[path] = [target](std::string, std::string, std::map&) + { + std::string header = "Location: " + target + "\r\n"; + char const* extra_headers[4] = { header.c_str(), "", "", ""}; + return sim::send_response(301, "Moved Permanently", 0, extra_headers); + }; + } + + void http_server::register_stall_handler(std::string const& path){ + m_stall_handlers.insert(path); + } + + void http_server::read() + { + if (m_bytes_used >= int(m_recv_buffer.size()) / 2) + { + m_recv_buffer.resize((std::max)(500, m_bytes_used * 2)); + } + assert(int(m_recv_buffer.size()) > m_bytes_used); + m_connection.async_read_some(asio::buffer(&m_recv_buffer[m_bytes_used] + , m_recv_buffer.size() - m_bytes_used) + , std::bind(&http_server::on_read, this, _1, _2)); + } + + http_request parse_request(char const* start, int len) + { + http_request ret; + + char const* const end_of_request = start + len; + char const* const space = find(start, len, " ", 1); + if (space == nullptr) + { + std::printf("http_server: failed to parse request:\n%s\n" + , std::string(start, len).c_str()); + throw std::runtime_error("parse failed"); + } + + char const* const space2 = find(space + 1 + , int(len - (space - start + 1)), " ", 1); + if (space2 == nullptr) + { + std::printf("http_server: failed to parse request:\n%s\n" + , std::string(start, len).c_str()); + throw std::runtime_error("parse failed"); + } + ret.method.assign(start, space); + ret.req.assign(space+1, space2); + if (ret.method != "CONNECT") { + ret.path.assign(normalize(ret.req.substr(0, ret.req.find_first_of('?')))); + } else { + ret.path.assign(ret.req); + } + std::printf("parse_request: %s %s [%s]\n" + , ret.method.c_str(), ret.path.c_str(), ret.req.c_str()); + + char const* header = find(space2, int(len - (space2 - start)), "\r\n", 2); + while (header != end_of_request - 4) + { + if (header == nullptr) + { + std::printf("http_server: failed to parse request:\n%s\n" + , std::string(start, len).c_str()); + throw std::runtime_error("parse failed"); + } + char const* const next = find(header + 2 + , int(len - (header + 2 - start)), "\r\n", 2); + char const* const value = static_cast(memchr(header, ':', len - (header - start))); + if (value == nullptr || next == nullptr || value > next) + { + std::printf("http_server: failed to parse request:\n%s\n" + , std::string(start, len).c_str()); + throw std::runtime_error("parse failed"); + } + + ret.headers[lower_case(trim(std::string(header, value)))] + = trim(std::string(value+1, next)); + + header = next; + } + return ret; + } + + int find_request_len(char const* buf, int const len) + { + char const* end_of_request = find(buf, len, "\r\n\r\n", 4); + if (end_of_request == nullptr) return -1; + return int(end_of_request - buf + 4); + } + + void http_server::on_read(error_code const& ec, size_t bytes_transferred) try + { + if (ec) + { + std::printf("http_server::on_read: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + m_bytes_used += int(bytes_transferred); + + int const req_len = find_request_len(m_recv_buffer.data(), m_bytes_used); + if (req_len < 0) + { + read(); + return; + } + + http_request req = parse_request(m_recv_buffer.data(), req_len); + + m_recv_buffer.erase(m_recv_buffer.begin(), m_recv_buffer.begin() + req_len); + m_bytes_used -= req_len; + + auto it = m_handlers.find(req.path); + if (it == m_handlers.end()) + { + if (m_stall_handlers.find(req.path) != m_stall_handlers.end()) + { + return; + } + // no handler found, 404 + m_send_buffer = send_response(404, "Not Found"); + } + else + { + m_send_buffer = it->second(req.method, req.req, req.headers); + } + + bool close = lower_case(req.headers["connection"]) == "close"; + + async_write(m_connection, asio::buffer(m_send_buffer.data() + , m_send_buffer.size()), std::bind(&http_server::on_write + , this, _1, _2, close)); + } + catch (std::exception& e) + { + std::printf("http_server::on_read() failed: %s\n" + , e.what()); + close_connection(); + } + + void http_server::on_write(error_code const& ec + , size_t /* bytes_transferred */ + , bool close) + { + if (ec) + { + std::printf("http_server::on_write: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + if (!close && (m_flags & keep_alive)) + { + // try to read another request out of the buffer + post(m_ios, std::bind(&http_server::on_read, this, error_code(), 0)); + } + else + { + close_connection(); + } + } + + void http_server::stop() + { + m_close = true; + m_listen_socket.close(); + } + + void http_server::close_connection() + { + m_recv_buffer.clear(); + m_bytes_used = 0; + + error_code err; + m_connection.close(err); + if (err) + { + std::printf("http_server::close: failed to close connection (%d) %s\n" + , err.value(), err.message().c_str()); + return; + } + + if (m_close) return; + + // now we can accept another connection + m_listen_socket.async_accept(m_connection, m_ep + , std::bind(&http_server::on_accept, this, _1)); + } +} + diff --git a/simulation/libsimulator/src/io_service.cpp b/simulation/libsimulator/src/io_service.cpp new file mode 100644 index 0000000..b48d7a5 --- /dev/null +++ b/simulation/libsimulator/src/io_service.cpp @@ -0,0 +1,257 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" + +#include +#include + +namespace sim { namespace asio { + + io_context::io_context(sim::simulation& sim) + : io_context(sim, std::vector()) + {} + + io_context::io_context(sim::simulation& sim, asio::ip::address const& ip) + : io_context(sim, std::vector{ip}) + {} + + io_context::io_context(sim::simulation& sim, std::vector const& ips) + : m_sim(sim) + , m_ips(ips) + , m_stopped(false) + { + for (auto const& ip : m_ips) + { + m_outgoing_route[ip] = m_sim.config().outgoing_route(ip); + m_incoming_route[ip] = m_sim.config().incoming_route(ip); + } + m_sim.add_io_service(this); + } + + io_context::~io_context() + { + m_sim.remove_io_service(this); + } + + io_context::io_context(std::size_t) + : m_sim(*reinterpret_cast(0)) + { + assert(false); + } + + int io_context::get_path_mtu(const asio::ip::address& source, const asio::ip::address& dest) const + { + // TODO: it would be nice to actually traverse the virtual network nodes + // and ask for their MTU instead + assert(std::count(m_ips.begin(), m_ips.end(), source) > 0 && "source address must be a local address to this node/io_context"); + return m_sim.config().path_mtu(source, dest); + } + + void io_context::stop() + { + // TODO: cancel all outstanding handler associated with this io_context + m_stopped = true; + } + + bool io_context::stopped() const + { + return m_stopped; + } + + void io_context::restart() + { + m_stopped = false; + } + + std::size_t io_context::run() + { + assert(false); + return 0; + } + + std::size_t io_context::run(boost::system::error_code&) + { + assert(false); + return 0; + } + + std::size_t io_context::poll() + { + assert(false); + return 0; + } + + std::size_t io_context::poll(boost::system::error_code&) + { + assert(false); + return 0; + } + + std::size_t io_context::poll_one() + { + assert(false); + return 0; + } + + std::size_t io_context::poll_one(boost::system::error_code&) + { + assert(false); + return 0; + } + + // private interface + + void io_context::add_timer(high_resolution_timer* t) + { + m_sim.add_timer(t); + } + + void io_context::remove_timer(high_resolution_timer* t) + { + m_sim.remove_timer(t); + } + + boost::asio::io_context& io_context::get_internal_service() + { return m_sim.get_internal_service(); } + + ip::tcp::endpoint io_context::bind_socket(ip::tcp::socket* socket + , ip::tcp::endpoint ep, boost::system::error_code& ec) + { + assert(!m_ips.empty() && "you cannot use an internal io_context (one without an IP address) for creating and binding sockets"); + if (ep.address() == ip::address_v4::any()) + { + auto it = std::find_if(m_ips.begin(), m_ips.end() + , [](ip::address const& ip) { return ip.is_v4(); } ); + if (it == m_ips.end()) + { + ec.assign(boost::system::errc::address_not_available + , boost::system::generic_category()); + return ip::tcp::endpoint(); + } + // TODO: pick the first local endpoint for now. In the future we may + // want have a bias toward + ep.address(*it); + } + else if (ep.address() == ip::address_v6::any()) + { + auto it = std::find_if(m_ips.begin(), m_ips.end() + , [](ip::address const& ip) { return ip.is_v6(); } ); + if (it == m_ips.end()) + { + ec.assign(boost::system::errc::address_not_available + , boost::system::generic_category()); + return ip::tcp::endpoint(); + } + // TODO: pick the first local endpoint for now. In the future we may + // want have a bias toward + ep.address(*it); + } + else if (std::count(m_ips.begin(), m_ips.end(), ep.address()) == 0) + { + // you can only bind to the IP assigned to this node. + // TODO: support loopback + ec.assign(boost::system::errc::address_not_available + , boost::system::generic_category()); + return ip::tcp::endpoint(); + } + + return m_sim.bind_socket(socket, ep, ec); + } + + void io_context::unbind_socket(ip::tcp::socket* socket + , const ip::tcp::endpoint& ep) + { + m_sim.unbind_socket(socket, ep); + } + + void io_context::rebind_socket(asio::ip::tcp::socket* prev, asio::ip::tcp::socket* s, asio::ip::tcp::endpoint ep) + { + m_sim.rebind_socket(prev, s, ep); + } + + ip::udp::endpoint io_context::bind_udp_socket(ip::udp::socket* socket + , ip::udp::endpoint ep, boost::system::error_code& ec) + { + assert(!m_ips.empty() && "you cannot use an internal io_context (one without an IP address) for creating and binding sockets"); + if (ep.address() == ip::address_v4::any()) + { + auto it = std::find_if(m_ips.begin(), m_ips.end() + , [](ip::address const& ip) { return ip.is_v4(); }); + if (it == m_ips.end()) + { + ec.assign(boost::system::errc::address_not_available + , boost::system::generic_category()); + return ip::udp::endpoint(); + } + // TODO: pick the first local endpoint for now. In the future we may + // want have a bias toward + ep.address(*it); + } + else if (ep.address() == ip::address_v6::any()) + { + auto it = std::find_if(m_ips.begin(), m_ips.end() + , [](ip::address const& ip) { return ip.is_v6(); }); + if (it == m_ips.end()) + { + ec.assign(boost::system::errc::address_not_available + , boost::system::generic_category()); + return ip::udp::endpoint(); + } + // TODO: pick the first local endpoint for now. In the future we may + // want have a bias toward + ep.address(*it); + } + else if (std::count(m_ips.begin(), m_ips.end(), ep.address()) == 0) + { + // you can only bind to the IP assigned to this node. + // TODO: support loopback + ec.assign(boost::system::errc::address_not_available + , boost::system::generic_category()); + return ip::udp::endpoint(); + } + + return m_sim.bind_udp_socket(socket, ep, ec); + } + + void io_context::unbind_udp_socket(ip::udp::socket* socket + , const ip::udp::endpoint& ep) + { + m_sim.unbind_udp_socket(socket, ep); + } + + void io_context::rebind_udp_socket(asio::ip::udp::socket* socket, asio::ip::udp::endpoint ep) + { + m_sim.rebind_udp_socket(socket, ep); + } + + std::shared_ptr io_context::internal_connect(ip::tcp::socket* s + , ip::tcp::endpoint const& target, boost::system::error_code& ec) + { + return m_sim.internal_connect(s, target, ec); + } + + route io_context::find_udp_socket(asio::ip::udp::socket const& socket + , ip::udp::endpoint const& ep) + { + return m_sim.find_udp_socket(socket, ep); + } + +} // asio +} // sim + diff --git a/simulation/libsimulator/src/nat.cpp b/simulation/libsimulator/src/nat.cpp new file mode 100644 index 0000000..de27f48 --- /dev/null +++ b/simulation/libsimulator/src/nat.cpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2018, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/nat.hpp" +#include "simulator/simulator.hpp" +#include "simulator/packet.hpp" +#include + +namespace sim { + + nat::nat(asio::ip::address external_addr) : m_external_addr(external_addr) {} + + void nat::incoming_packet(aux::packet p) + { + // unconditionally replacing the "from" address is fine because of our + // simplified network model where the two paths of a connection are set up + // independently, and we can set up the nat hop only on the outgoing path + p.from.address(m_external_addr); + if (p.channel) { + p.channel->visible_ep[0].address(m_external_addr); + } + forward_packet(std::move(p)); + } + + // used for visualization + std::string nat::label() const + { + return std::string("NAT [") + m_external_addr.to_string() + "]"; + } + +} // sim + diff --git a/simulation/libsimulator/src/pcap.cpp b/simulation/libsimulator/src/pcap.cpp new file mode 100644 index 0000000..d77aa4e --- /dev/null +++ b/simulation/libsimulator/src/pcap.cpp @@ -0,0 +1,204 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/pcap.hpp" +#include "simulator/chrono.hpp" +#include "simulator/packet.hpp" + +using sim::chrono::duration_cast; +using sim::chrono::seconds; + +using sim::asio::ip::address_v4; +using sim::asio::ip::tcp; +using sim::asio::ip::udp; + +namespace sim { namespace aux { + +namespace { + + template ::value>::type> + void write(std::fstream& o, T const& value) + { o.write(reinterpret_cast(&value), sizeof(value)); } + + // documented here: + // http://www.tcpdump.org/linktypes.html + std::uint32_t const LINKTYPE_RAW = 101; + + struct ip_header + { + std::uint8_t version_len; + std::uint8_t dscp; + std::uint16_t length; + std::uint16_t identification; + std::uint16_t fragment; + std::uint8_t ttl; + std::uint8_t protocol; + std::uint16_t checksum; + std::uint32_t source_ip; + std::uint32_t destination_ip; + }; + + struct udp_header + { + std::uint16_t src_port; + std::uint16_t dst_port; + std::uint16_t length; + std::uint16_t checksum; + }; + + struct tcp_header + { + std::uint16_t src_port; + std::uint16_t dst_port; + std::uint32_t seq_nr; + std::uint32_t ack_nr; + std::uint8_t offset; + std::uint8_t flags; + std::uint16_t window_size; + std::uint16_t checksum; + std::uint16_t urgent; + }; +} + + pcap::pcap(char const* filename) + : m_file(filename, std::ios_base::out | std::ios_base::trunc | std::ios_base::binary) + { + // file format documented here: + // https://wiki.wireshark.org/Development/LibpcapFileFormat + // write pcap file header + write(m_file, std::uint32_t(0xa1b2c3d4)); // magic number + write(m_file, std::uint16_t(2)); // versopm major + write(m_file, std::uint16_t(4)); // versopm minor + write(m_file, std::int32_t(0)); // thiszone + write(m_file, std::uint32_t(0)); // sigfigs + write(m_file, std::uint32_t(0xffff)); // snaplen + write(m_file, LINKTYPE_RAW); // network + } + + void write_ip_header(std::fstream& file, int const size, int const protocol + , address_v4 const source, address_v4 const dest) + { + ip_header const header = { + (4 << 4) | 5, // version_len + 0, // dscp + htons(std::uint16_t(size)), // length + 0, // identification + 0, // fragment + 200, // ttl + std::uint8_t(protocol), // protocol + 0, // checksum + htonl(std::uint32_t(source.to_uint())), // source ip + htonl(std::uint32_t(dest.to_uint())) // destination ip + }; + + write(file, header); + } + + void write_udp_header(std::fstream& file, int const size, int const src_port + , int const dst_port) + { + assert(src_port != 0); + assert(dst_port != 0); + udp_header const header = { + htons(std::uint16_t(src_port)), // source port + htons(std::uint16_t(dst_port)), // destination port + htons(std::uint16_t(sizeof(udp_header) + size)), // length + 0 // checksum + }; + + write(file, header); + } + + void write_tcp_header(std::fstream& file, int const src_port + , int const dst_port, std::uint32_t const seq_nr) + { + assert(src_port != 0); + assert(dst_port != 0); + tcp_header const header = { + htons(std::uint16_t(src_port)), // source port + htons(std::uint16_t(dst_port)), // destination port + htonl(seq_nr), // sequence number + std::uint32_t(0), // acknowledgement number + std::uint8_t(5 << 4), // header size (data offset) + std::uint8_t(0), // flags + htons(std::uint16_t(65535)), // window size + std::uint16_t(0), // checksum + std::uint16_t(0) // urgent offset + }; + + write(file, header); + } + + void pcap::log_tcp(packet const& p, tcp::endpoint const src + , tcp::endpoint const dst) + { + // synthesize IP/TCP header and write packet + auto const now = chrono::high_resolution_clock::now(); + + // just an arbitrary posix time used as starting point + std::uint32_t const sim_start_time = 441794304; + std::uint32_t const secs = static_cast(duration_cast(now.time_since_epoch()).count()); + std::uint32_t const usecs = static_cast(duration_cast(now.time_since_epoch() - seconds(secs)).count()); + + std::uint32_t const packet_size = static_cast(sizeof(ip_header) + sizeof(tcp_header) + p.buffer.size()); + + write(m_file, sim_start_time + secs); + write(m_file, usecs); + write(m_file, packet_size); + write(m_file, packet_size); + + // 6 is the protocol number for TCP + write_ip_header(m_file, packet_size, 6 + , src.address().to_v4(), dst.address().to_v4()); + + // TODO: if the packet has error set with asio::error::eof + // set the FIN flag + write_tcp_header(m_file, p.from.port(), dst.port(), p.byte_counter); + + m_file.write(reinterpret_cast(p.buffer.data()), p.buffer.size()); + } + + void pcap::log_udp(packet const& p, udp::endpoint const src + , udp::endpoint const dst) + { + // synthesize IP/UDP header and write packet + auto const now = chrono::high_resolution_clock::now(); + + // just an arbitrary posix time used as starting point + std::uint32_t const sim_start_time = 441794304; + std::uint32_t const secs = static_cast(duration_cast(now.time_since_epoch()).count()); + std::uint32_t const usecs = static_cast(duration_cast(now.time_since_epoch() - seconds(secs)).count()); + + std::uint32_t const packet_size = static_cast(sizeof(ip_header) + sizeof(udp_header) + p.buffer.size()); + + write(m_file, sim_start_time + secs); + write(m_file, usecs); + write(m_file, packet_size); + write(m_file, packet_size); + + // 17 is the protocol number for UDP + write_ip_header(m_file, packet_size, 17 + , src.address().to_v4(), dst.address().to_v4()); + + write_udp_header(m_file, static_cast(p.buffer.size()), p.from.port(), static_cast(dst.port())); + + m_file.write(reinterpret_cast(p.buffer.data()), p.buffer.size()); + } + +}} + diff --git a/simulation/libsimulator/src/queue.cpp b/simulation/libsimulator/src/queue.cpp new file mode 100644 index 0000000..69811bc --- /dev/null +++ b/simulation/libsimulator/src/queue.cpp @@ -0,0 +1,142 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/queue.hpp" +#include "simulator/handler_allocator.hpp" +#include +#include // for printf + +typedef sim::chrono::high_resolution_clock::time_point time_point; +typedef sim::chrono::high_resolution_clock::duration duration; + +namespace sim +{ + using namespace aux; + + queue::queue(asio::io_context& ios + , int bandwidth + , chrono::high_resolution_clock::duration propagation_delay + , int max_queue_size + , std::string name) + : m_max_queue_size(max_queue_size) + , m_forwarding_latency(propagation_delay) + , m_bandwidth(bandwidth) + , m_queue_size(0) + , m_node_name(name) + , m_forward_timer(ios) + , m_last_forward(chrono::high_resolution_clock::now()) + {} + + std::string queue::label() const + { + char ret[400]; + int p = std::snprintf(ret, sizeof(ret), "%s\n", m_node_name.c_str()); + + if (m_bandwidth != 0) + { + p += std::snprintf(ret + p, sizeof(ret) - p, "rate: %d kB/s\n" + , m_bandwidth / 1000); + } + + if (m_queue_size != 0) + { + p += std::snprintf(ret + p, sizeof(ret) - p, "queue: %d kB\n" + , m_queue_size / 1000); + } + + if (m_forwarding_latency.count() != 0) + { + p += std::snprintf(ret + p, sizeof(ret) - p, "latency: %d ms\n" + , int(chrono::duration_cast(m_forwarding_latency).count())); + } + + return ret; + } + + void queue::incoming_packet(aux::packet p) + { + const int packet_size = int(p.buffer.size() + p.overhead); + + // tail-drop + if (p.ok_to_drop() + && m_max_queue_size > 0 + && m_queue_size + packet_size > m_max_queue_size) + { + // if any hop on the network drops a packet, it has to return it to the + // sender. + auto drop_fun = std::move(p.drop_fun); + if (drop_fun) drop_fun(std::move(p)); + return; + } + + time_point const now = chrono::high_resolution_clock::now(); + + m_queue.emplace_back(now + m_forwarding_latency, std::move(p)); + m_queue_size += packet_size; + if (m_queue.size() > 1) return; + + begin_send_next_packet(); + } + + void queue::begin_send_next_packet() + { + time_point now = chrono::high_resolution_clock::now(); + + if (m_queue.front().ts > now) + { + m_forward_timer.expires_at(m_queue.front().ts); + m_forward_timer.async_wait(make_malloc(std::bind(&queue::begin_send_next_packet + , this))); + return; + } + + m_last_forward = now; + if (m_bandwidth == 0) + { + post(m_forward_timer.get_executor(), make_malloc(std::bind(&queue::next_packet_sent + , this))); + return; + } + const double nanoseconds_per_byte = 1000000000.0 + / double(m_bandwidth); + + aux::packet const& p = m_queue.front().pkt; + const int packet_size = int(p.buffer.size() + p.overhead); + + m_last_forward += chrono::duration_cast(chrono::nanoseconds( + boost::int64_t(nanoseconds_per_byte * packet_size))); + + m_forward_timer.expires_at(m_last_forward); + m_forward_timer.async_wait(make_malloc(std::bind(&queue::next_packet_sent + , this))); + } + + void queue::next_packet_sent() + { + aux::packet p = std::move(m_queue.front().pkt); + m_queue.erase(m_queue.begin()); + const int packet_size = int(p.buffer.size() + p.overhead); + m_queue_size -= packet_size; + + forward_packet(std::move(p)); + + if (m_queue.size()) + begin_send_next_packet(); + } +} + diff --git a/simulation/libsimulator/src/resolver.cpp b/simulation/libsimulator/src/resolver.cpp new file mode 100644 index 0000000..01bc1ea --- /dev/null +++ b/simulation/libsimulator/src/resolver.cpp @@ -0,0 +1,143 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/handler_allocator.hpp" +#include + +typedef sim::chrono::high_resolution_clock::time_point time_point; +typedef sim::chrono::high_resolution_clock::duration duration; + +using namespace std::placeholders; + +namespace sim { +namespace asio { +namespace ip { + + template + basic_resolver::basic_resolver(io_context& ios) + : m_ios(&ios) + , m_timer(ios) + {} + + template + basic_resolver::basic_resolver(basic_resolver&&) noexcept = default; + + template + basic_resolver& basic_resolver::operator=(basic_resolver&&) noexcept = default; + + template + void basic_resolver::async_resolve(std::string hostname, char const* service + , aux::function handler) + { + std::vector result; + boost::system::error_code ec; + + const chrono::high_resolution_clock::time_point start_time = + m_queue.empty() ? chrono::high_resolution_clock::now() : + m_queue.front().completion_time; + + assert(!m_ios->get_ips().empty() && "internal io service objects can only " + "be used for timers"); + + // if the hostname is an IP address, resolve it immediately + asio::ip::address addr = make_address_v4(hostname, ec); + if (ec) addr = make_address_v6(hostname, ec); + if (!ec) + { + const chrono::high_resolution_clock::time_point t = chrono::high_resolution_clock::now() + + chrono::microseconds(1); + results_type ips; + int const port = atoi(service); + assert(port >= 0 && port <= 0xffff); + ips.emplace_back( + typename Protocol::endpoint(addr, static_cast(port)) + , hostname, service); + result_t res{t, ec, std::move(ips), std::move(handler) }; + m_queue.insert(m_queue.begin(), std::move(res)); + m_timer.expires_at(m_queue.front().completion_time); + m_timer.async_wait(aux::make_malloc(std::bind(&basic_resolver::on_lookup, this, _1))); + return; + } + ec.clear(); + + const chrono::high_resolution_clock::time_point completion_time = + start_time + + m_ios->sim().config().hostname_lookup(m_ios->get_ips().front(), hostname + , result, ec); + + results_type ips; + + int const port = atoi(service); + assert(port >= 0 && port <= 0xffff); + + for (auto const& ip : result) + { + ips.emplace_back( + typename Protocol::endpoint(ip, static_cast(port)) + , hostname, service); + } + + result_t res{ completion_time, ec, std::move(ips), std::move(handler)}; + m_queue.emplace_back(std::move(res)); + + m_timer.expires_at(m_queue.front().completion_time); + m_timer.async_wait(aux::make_malloc(std::bind(&basic_resolver::on_lookup, this, _1))); + } + + template + void basic_resolver::on_lookup(boost::system::error_code const& ec) + { + if (ec == asio::error::operation_aborted) return; + + if (m_queue.empty()) return; + + typename queue_t::value_type v = std::move(m_queue.front()); + m_queue.erase(m_queue.begin()); + + // once the handler is called, it's possible the last reference keeping + // this object (basic_resolver) alive is released and we're deleted. Make + // sure to not touch any members after the handler in that case. + bool const empty = m_queue.empty(); + v.handler(v.err, std::move(v.ips)); + if (empty) return; + + m_timer.expires_at(m_queue.front().completion_time); + m_timer.async_wait(aux::make_malloc(std::bind(&basic_resolver::on_lookup, this, _1))); + } + + template + void basic_resolver::cancel() + { + queue_t q; + m_queue.swap(q); + for (auto& r : q) + { + r.err = asio::error::operation_aborted; + post(m_timer.get_executor(), aux::make_malloc(std::bind(std::move(r.handler) + , r.err, std::move(r.ips)))); + } + } + + // explicitly instantiate the functions + template struct basic_resolver; + template struct basic_resolver; +} +} +} + diff --git a/simulation/libsimulator/src/simulation.cpp b/simulation/libsimulator/src/simulation.cpp new file mode 100644 index 0000000..acc17b2 --- /dev/null +++ b/simulation/libsimulator/src/simulation.cpp @@ -0,0 +1,354 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/packet.hpp" +#include "simulator/pcap.hpp" + +#include // for tie +#include // for make_shared +#include // for printf +#include + +using namespace sim::asio; + +namespace sim +{ + simulation::simulation(configuration& config) + : m_config(config) + , m_internal_ios(new asio::io_context(*this)) + , m_service(1) + { + sim::chrono::reset_clock(); + m_config.build(*this); + } + + simulation::~simulation() + { + m_config.clear(); + + assert(m_timer_queue.empty()); + } + + std::size_t simulation::run() try + { + std::size_t ret = 0; + std::size_t last_executed = 0; + do { + + m_service.restart(); + last_executed = m_service.poll(); + ret += last_executed; + + chrono::high_resolution_clock::time_point now + = chrono::high_resolution_clock::now(); + + std::lock_guard l(m_timer_queue_mutex); + if (!m_timer_queue.empty()) { + asio::high_resolution_timer* next_timer = *m_timer_queue.begin(); + chrono::high_resolution_clock::fast_forward(next_timer->expiry() - now); + + now = chrono::high_resolution_clock::now(); + + while (!m_timer_queue.empty() + && (*m_timer_queue.begin())->expiry() <= now) { + + next_timer = *m_timer_queue.begin(); + m_timer_queue.erase(m_timer_queue.begin()); + next_timer->fire(boost::system::error_code()); + ++last_executed; + ++ret; + } + } + +// std::fprintf(stderr, "run: last_executed: %d stopped: %d timer-queue: %d\n" +// , int(last_executed), m_stopped, int(m_timer_queue.size())); + } while (last_executed > 0 && !m_stopped); + +// std::fprintf(stderr, "exiting simulation::run(): last_executed: %d stopped: %d timer-queue: %d ret: %d\n" +// , int(last_executed), m_stopped, int(m_timer_queue.size()), int(ret)); + return ret; + } + catch (...) + { + // cancel all outstanding timers + // we make a copy, since cancelling the timers will mutate m_timer_queue + auto queue = m_timer_queue; + for (auto& t : queue) + t->cancel(); + + auto listen_sockets = m_listen_sockets; + for (auto& s : listen_sockets) + s.second->cancel(); + + auto udp_sockets = m_udp_sockets; + for (auto& s : udp_sockets) + s.second->cancel(); + m_stopped = true; + throw; + } + + void simulation::stop() { m_stopped = true; } + bool simulation::stopped() const { return m_stopped; } + void simulation::restart() { m_stopped = false; } + + void simulation::add_timer(asio::high_resolution_timer* t) + { + assert(!m_stopped); + if (t->expiry() == sim::chrono::high_resolution_clock::now()) + { + std::fprintf(stderr, "WARNING: timer scheduled for current time!\n"); + } + std::lock_guard l(m_timer_queue_mutex); + // make sure we get a deterministic ordering of timers with the same + // expiration time + auto it = std::upper_bound(m_timer_queue.begin(), m_timer_queue.end(), t, timer_compare()); + m_timer_queue.insert(it, t); + } + + void simulation::remove_timer(asio::high_resolution_timer* t) + { + std::lock_guard l(m_timer_queue_mutex); + if (m_timer_queue.empty()) return; + timer_queue_t::iterator begin; + timer_queue_t::iterator end; + std::tie(begin, end) = std::equal_range(m_timer_queue.begin(), m_timer_queue.end(), t, timer_compare()); + if (begin == end) return; + begin = std::find(begin, end, t); + if (begin == end) return; + m_timer_queue.erase(begin); + } + + void simulation::rebind_socket(ip::tcp::socket* prev, ip::tcp::socket* s, ip::tcp::endpoint ep) + { + auto i = m_listen_sockets.find(ep); + assert(i != m_listen_sockets.end()); + if (i->second != prev) return; + i->second = s; + } + + ip::tcp::endpoint simulation::bind_socket(ip::tcp::socket* socket + , ip::tcp::endpoint ep, boost::system::error_code& ec) + { + assert(ep.address() != boost::asio::ip::address()); + + if (ep.port() < 1024 && ep.port() > 0) + { + // emulate process not running as root + ec = boost::asio::error::access_denied; + return ip::tcp::endpoint(); + } + + if (ep.port() == 0) + { + // if the socket is being bound to port 0, it means the system picks a + // free port. We want to avoid re-using ports, because that may confuse + // wireshark when threading together the TCP streams. + ep.port(m_next_bind_port++); + if (m_next_bind_port > 65534) m_next_bind_port = 2000; + + listen_socket_iter_t i = m_listen_sockets.lower_bound(ep); + while (i != m_listen_sockets.end() && i->first == ep) + { + ep.port(ep.port() + 1); + if (ep.port() > 65530) + { + ec = boost::asio::error::address_in_use; + return ip::tcp::endpoint(); + } + i = m_listen_sockets.lower_bound(ep); + } + } + + listen_socket_iter_t i = m_listen_sockets.lower_bound(ep); + if (i != m_listen_sockets.end() && i->first == ep) + { + ec = boost::asio::error::address_in_use; + return ip::tcp::endpoint(); + } + + m_listen_sockets.insert(i, std::make_pair(ep, socket)); + ec.clear(); + return ep; + } + + void simulation::unbind_socket(ip::tcp::socket* socket + , const ip::tcp::endpoint& ep) + { + listen_socket_iter_t i = m_listen_sockets.find(ep); + if (i == m_listen_sockets.end() || i->second != socket) return; + m_listen_sockets.erase(i); + } + + void simulation::rebind_udp_socket(ip::udp::socket* socket, ip::udp::endpoint ep) + { + auto i = m_udp_sockets.find(ep); + assert(i != m_udp_sockets.end()); + i->second = socket; + } + + ip::udp::endpoint simulation::bind_udp_socket(ip::udp::socket* socket + , ip::udp::endpoint ep, boost::system::error_code& ec) + { + assert(ep.address() != boost::asio::ip::address()); + + if (ep.port() < 1024 && ep.port() > 0) + { + // emulate process not running as root + ec = boost::asio::error::access_denied; + return ip::udp::endpoint(); + } + + if (ep.port() == 0) + { + // if the socket is being bound to port 0, it means the system picks a + // free port. + + ep.port(m_next_bind_port++); + if (m_next_bind_port > 65534) m_next_bind_port = 2000; + udp_socket_iter_t i = m_udp_sockets.lower_bound(ep); + while (i != m_udp_sockets.end() && i->first == ep) + { + ep.port(ep.port() + 1); + if (ep.port() > 65530) + { + ec = boost::asio::error::address_in_use; + return ip::udp::endpoint(); + } + i = m_udp_sockets.lower_bound(ep); + } + } + + udp_socket_iter_t i = m_udp_sockets.lower_bound(ep); + if (i != m_udp_sockets.end() && i->first == ep) + { + ec = boost::asio::error::address_in_use; + return ip::udp::endpoint(); + } + + m_udp_sockets.insert(i, std::make_pair(ep, socket)); + ec.clear(); + return ep; + } + + void simulation::unbind_udp_socket(ip::udp::socket* socket + , const ip::udp::endpoint& ep) + { + udp_socket_iter_t i = m_udp_sockets.find(ep); + if (i == m_udp_sockets.end() || i->second != socket) return; + m_udp_sockets.erase(i); + } + + std::shared_ptr simulation::internal_connect( + asio::ip::tcp::socket* s + , ip::tcp::endpoint const& target, boost::system::error_code& ec) + { + // find remote socket + listen_sockets_t::iterator i = m_listen_sockets.find(target); + if (i == m_listen_sockets.end()) + { + ec = boost::system::error_code(error::connection_refused); + return std::shared_ptr(); + } + + // make sure it's a listening socket + ip::tcp::socket* remote = i->second; + if (!remote->internal_is_listening()) + { + ec = boost::system::error_code(error::connection_refused); + return std::shared_ptr(); + } + + // create a channel + std::shared_ptr c = std::make_shared(); + + asio::ip::tcp::endpoint from = s->local_bound_to(ec); + + route network_route = m_config.channel_route(from.address() + , target.address()); + c->hops[0] = remote->get_outgoing_route() + network_route + s->get_incoming_route(); + c->hops[1] = s->get_outgoing_route() + network_route + remote->get_incoming_route(); + + c->ep[0] = s->local_bound_to(ec); + c->ep[1] = remote->local_bound_to(ec); + + c->visible_ep[0] = s->local_bound_to(ec); + c->visible_ep[1] = remote->local_bound_to(ec); + + aux::packet p; + p.type = aux::packet::type_t::syn; + p.overhead = 28; + p.from = asio::ip::udp::endpoint(from.address(), from.port()); + p.channel = c; + if (ec) return std::shared_ptr(); + + p.hops = c->hops[1]; + + forward_packet(std::move(p)); + + return c; + } + + route simulation::find_udp_socket(asio::ip::udp::socket const& socket + , ip::udp::endpoint const& ep) + { + udp_socket_iter_t i = m_udp_sockets.find(ep); + if (i == m_udp_sockets.end()) + return route(); + + ip::udp::endpoint src = socket.local_bound_to(); + route network_route = m_config.channel_route(src.address(), ep.address()); + + // ask the socket for its incoming route + network_route.append(i->second->get_incoming_route()); + + return network_route; + } + + void simulation::add_io_service(asio::io_context* ios) + { + bool added = m_nodes.insert(ios).second; + (void)added; + assert(added); + } + + void simulation::remove_io_service(asio::io_context* ios) + { + auto it = m_nodes.find(ios); + assert(it != m_nodes.end()); + m_nodes.erase(it); + } + + std::vector simulation::get_all_io_services() const + { + std::vector ret; + ret.reserve(m_nodes.size()); + std::remove_copy_if( + m_nodes.begin(), m_nodes.end(), std::back_inserter(ret) + , [](io_context* ios) { return ios->get_ips().empty(); }); + return ret; + } + + void simulation::log_pcap(char const* filename) + { + std::printf("saving packet capture to: \"%s\"\n", filename); + m_pcap = std::unique_ptr(new aux::pcap(filename)); + } + +} + diff --git a/simulation/libsimulator/src/simulator.cpp b/simulation/libsimulator/src/simulator.cpp new file mode 100644 index 0000000..79cfa4e --- /dev/null +++ b/simulation/libsimulator/src/simulator.cpp @@ -0,0 +1,262 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/packet.hpp" + +#include +#include +#include +#include // for printf + +#include "simulator/push_warnings.hpp" +#include +#include "simulator/pop_warnings.hpp" + +typedef sim::chrono::high_resolution_clock::time_point time_point; +typedef sim::chrono::high_resolution_clock::duration duration; + +namespace sim { +namespace asio { +namespace ip { + + default_config default_cfg; + +} // ip +} // asio + +void forward_packet(aux::packet p) +{ + std::shared_ptr next_hop = p.hops.pop_front(); + if (!next_hop) + { + std::fprintf(stderr, "packet lost\n"); + return; + } + next_hop->incoming_packet(std::move(p)); +} + +namespace +{ + // this is a dummy sink for endpoints, wrapping an io_context + struct endpoint : sink + { + endpoint(asio::io_context& ioc) + : m_ioc(ioc) + {} + + virtual void incoming_packet(aux::packet p) override final { assert(false); } + + virtual std::string label() const override final + { + std::string ret; + for (auto const& ip : m_ioc.get_ips()) + { + ret += ip.to_string(); + ret += " "; + } + return ret; + } + + virtual std::string attributes() const override final + { + return "shape=ellipse"; + } + + private: + asio::io_context& m_ioc; + }; +} + +namespace +{ + std::string escape_label(std::string n) + { + std::string ret; + for (auto c : n) + { + if (c == '\n') + { + ret += "\\n"; + continue; + } + if (c == '\"') + { + ret += "\\\""; + continue; + } + ret += c; + } + return ret; + } +} + +void dump_network_graph(simulation const& s, const std::string& filename) +{ + // all edges (directed). + std::set, std::shared_ptr>> edges; + + // all network nodes + std::unordered_set> nodes; + + // local nodes (subgrapgs) + std::vector>> local_nodes; + + const std::vector io_services = s.get_all_io_services(); + + for (auto ioc : io_services) + { + std::shared_ptr ep = std::make_shared(*ioc); + local_nodes.push_back(std::unordered_set>()); + local_nodes.back().insert(ep); + + for (auto const& ip : ioc->get_ips()) + { + route in = ioc->get_incoming_route(ip); + route out = ioc->get_outgoing_route(ip); + + // this is the outgoing node for this endpoint. This is + // how it connects to the network. + const std::shared_ptr egress = out.empty() ? ep : out.last(); + + // first add both the incoming and outgoing chains + std::shared_ptr prev; + while (!in.empty()) + { + auto node = in.pop_front(); + local_nodes.back().insert(node); + if (prev) edges.insert({prev, node}); + prev = node; + } + if (prev) edges.insert({prev, ep}); + + prev = ep; + while (!out.empty()) + { + auto node = out.pop_front(); + local_nodes.back().insert(node); + edges.insert({prev, node}); + prev = node; + } + + // then connect the endpoint of those chains to the rest of the network. + // Since the network may be arbitrarily complex, we actually have to + // completely iterate over all other endpoints + + for (auto ios2 : io_services) + { + for (auto const& ip2 : ios2->get_ips()) + { + route network = s.config().channel_route( + ip, ip2); + + std::shared_ptr last = ios2->get_incoming_route(ip2).next_hop(); + + prev = egress; + while (!network.empty()) + { + auto node = network.pop_front(); + nodes.insert(node); + edges.insert({prev, node}); + prev = node; + } + edges.insert({prev, last}); + } + } + } + } + + // by now, the nodes and edges should represent the complete graph. Render it + // into dot. + + FILE* f = fopen(filename.c_str(), "w+"); + + std::fprintf(f, "digraph network {\n" + "concentrate=true;\n" + "overlap=scale;\n" + "splines=true;\n"); + + std::fprintf(f, "\n// nodes\n\n"); + + for (const auto& n : nodes) + { + std::string attributes = n->attributes(); + std::fprintf(f, " \"%p\" [label=\"%s\",style=\"filled\",color=\"red\"%s%s];\n" + , static_cast(n.get()) + , escape_label(n->label()).c_str() + , attributes.empty() ? "" : ", " + , attributes.c_str()); + } + + std::fprintf(f, "\n// local networks\n\n"); + + int idx = 0; + for (auto ln : local_nodes) + { + std::fprintf(f, "subgraph cluster_%d {\n", idx++); + + for (const auto& n : ln) + { + std::string attributes = n->attributes(); + std::fprintf(f, " \"%p\" [label=\"%s\",style=\"filled\",color=\"green\"%s%s];\n" + , static_cast(n.get()) + , escape_label(n->label()).c_str() + , attributes.empty() ? "" : ", " + , attributes.c_str()); + } + + std::fprintf(f, "}\n"); + } + + std::fprintf(f, "\n// edges\n\n"); + + while (!edges.empty()) + { + auto edge = *edges.begin(); + edges.erase(edges.begin()); + + std::fprintf(f, "\"%p\" -> \"%p\"\n" + , static_cast(edge.first.get()) + , static_cast(edge.second.get())); + } + + std::fprintf(f, "}\n"); + fclose(f); +} + +namespace aux { + + int channel::remote_idx(const asio::ip::tcp::endpoint& self) const + { + if (ep[0] == self) return 1; + if (ep[1] == self) return 0; + assert(false && "invalid socket"); + return -1; + } + + int channel::self_idx(const asio::ip::tcp::endpoint& self) const + { + if (ep[0] == self) return 0; + if (ep[1] == self) return 1; + assert(false && "invalid socket"); + return -1; + } + +} // aux +} // sim + diff --git a/simulation/libsimulator/src/sink_forwarder.cpp b/simulation/libsimulator/src/sink_forwarder.cpp new file mode 100644 index 0000000..e98dc2f --- /dev/null +++ b/simulation/libsimulator/src/sink_forwarder.cpp @@ -0,0 +1,45 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/sink_forwarder.hpp" +#include "simulator/packet.hpp" + +namespace sim { namespace aux { + + sink_forwarder::sink_forwarder(sink* dst) + : m_dst(dst) + {} + + void sink_forwarder::incoming_packet(packet p) + { + if (m_dst == nullptr) return; + m_dst->incoming_packet(std::move(p)); + } + + std::string sink_forwarder::label() const + { + return m_dst ? m_dst->label() : ""; + } + + void sink_forwarder::reset(sink* s) + { + m_dst = s; + } + +}} + diff --git a/simulation/libsimulator/src/socks_server.cpp b/simulation/libsimulator/src/socks_server.cpp new file mode 100644 index 0000000..54513f1 --- /dev/null +++ b/simulation/libsimulator/src/socks_server.cpp @@ -0,0 +1,1033 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/socks_server.hpp" + +#include +#include +#include +#include // for printf + +using namespace sim::asio; +using namespace sim::asio::ip; + +using boost::system::error_code; + +namespace sim +{ + using namespace aux; + + socks_server::socks_server(io_context& ios, unsigned short listen_port, int version + , std::uint32_t const flags) + : m_ios(ios) + , m_listen_socket(ios) + , m_conn(std::make_shared(m_ios, version, m_cmd_counts, flags, m_bind_port)) + , m_version(version) + , m_flags(flags) + { + m_cmd_counts.fill(0); + address local_ip = ios.get_ips().front(); + if (local_ip.is_v4()) + { + m_listen_socket.open(tcp::v4()); + m_listen_socket.bind(tcp::endpoint(address_v4::any(), listen_port)); + } + else + { + m_listen_socket.open(tcp::v6()); + m_listen_socket.bind(tcp::endpoint(address_v6::any(), listen_port)); + } + m_listen_socket.listen(); + + m_listen_socket.async_accept(m_conn->socket(), m_ep + , std::bind(&socks_server::on_accept, this, std::placeholders::_1)); + } + + void socks_server::on_accept(error_code const& ec) + { + if (ec == asio::error::operation_aborted) + return; + + if (ec) + { + std::printf("socks_server::on_accept: (%d) %s\n" + , ec.value(), ec.message().c_str()); + return; + } + + std::printf("socks_server accepted connection from: %s : %d\n", + m_ep.address().to_string().c_str(), m_ep.port()); + + m_conn->start(); + + // create a new connection to accept into + m_conn = std::make_shared(m_ios, m_version, m_cmd_counts, m_flags, m_bind_port); + + // now we can accept another connection + m_listen_socket.async_accept(m_conn->socket(), m_ep + , std::bind(&socks_server::on_accept, this, std::placeholders::_1)); + } + + void socks_server::stop() + { + m_close = true; + m_listen_socket.close(); + } + + socks_connection::socks_connection(asio::io_context& ios + , int version, std::array& cmd_counts, std::uint32_t const flags, int& bind_port) + : m_bind_port(bind_port) + , m_ios(ios) + , m_udp_resolver(ios) + , m_resolver(m_ios) + , m_client_connection(ios) + , m_server_connection(m_ios) + , m_bind_socket(m_ios) + , m_udp_associate(m_ios) + , m_num_out_bytes(0) + , m_num_in_bytes(0) + , m_version(version) + , m_command(0) + , m_cmd_counts(cmd_counts) + , m_flags(flags) + { + } + + void socks_connection::start() + { + if (m_version == 4) + { + asio::async_read(m_client_connection, asio::buffer(&m_out_buffer[0], 9) + , std::bind(&socks_connection::on_request1, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); + } else { + // read protocol version and number of auth-methods + asio::async_read(m_client_connection, asio::buffer(&m_out_buffer[0], 2) + , std::bind(&socks_connection::on_handshake1, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); + } + } + + void socks_connection::on_handshake1(error_code const& ec, size_t bytes_transferred) + { + if (ec || bytes_transferred != 2) + { + std::printf("socks_connection::on_handshake1: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + if (m_out_buffer[0] != 4 && m_out_buffer[0] != 5) + { + std::printf("socks_connection::on_handshake1: unexpected socks protocol version: %d" + , int(m_out_buffer[0])); + close_connection(); + return; + } + + int num_methods = unsigned(m_out_buffer[1]); + + // read list of auth-methods + asio::async_read(m_client_connection, asio::buffer(&m_out_buffer[0], + num_methods) + , std::bind(&socks_connection::on_handshake2, shared_from_this() + , std::placeholders::_1, std::placeholders::_2)); + } + + void socks_connection::on_handshake2(error_code const& ec, size_t bytes_transferred) + { + if (ec) + { + std::printf("socks_connection::on_handshake2: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + if (std::count(m_out_buffer, m_out_buffer + bytes_transferred, 0) == 0) + { + std::printf("socks_connection: could not find auth-method 0 (no-auth) in socks handshake\n"); + close_connection(); + return; + } + + m_in_buffer[0] = 5; // socks version + m_in_buffer[1] = 0; // auth-method (no-auth) + + asio::async_write(m_client_connection, asio::buffer(&m_in_buffer[0], 2) + , std::bind(&socks_connection::on_handshake3, shared_from_this() + , std::placeholders::_1, std::placeholders::_2)); + } + + void socks_connection::on_handshake3(error_code const& ec, size_t bytes_transferred) + { + if (ec || bytes_transferred != 2) + { + std::printf("socks_connection::on_handshake3: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + asio::async_read(m_client_connection, asio::buffer(&m_out_buffer[0], 10) + , std::bind(&socks_connection::on_request1, shared_from_this() + , std::placeholders::_1, std::placeholders::_2)); + } + + void socks_connection::on_request1(error_code const& ec, size_t bytes_transferred) + { + size_t const expected = m_version == 4 ? 9 : 10; + if (ec || bytes_transferred != expected) + { + std::printf("socks_connection::on_request1: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + +// +----+-----+-------+------+----------+----------+ +// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | +// +----+-----+-------+------+----------+----------+ +// | 1 | 1 | X'00' | 1 | Variable | 2 | +// +----+-----+-------+------+----------+----------+ + + int const version = m_out_buffer[0]; + int const command = m_out_buffer[1]; + m_command = command; + ++m_cmd_counts[command - 1]; + + if (version != m_version) + { + std::printf("socks_connection::on_request1: unexpected socks protocol version: %d expected: %d\n" + , int(m_out_buffer[0]), m_version); + close_connection(); + return; + } + + if (m_version == 4) + { + if (command != 1 && command != 2) + { + std::printf("socks_connection::on_request1: unexpected socks command: %d\n" + , command); + close_connection(); + return; + } + + std::uint16_t port = m_out_buffer[2] & 0xff; + port <<= 8; + port |= m_out_buffer[3] & 0xff; + + std::uint32_t addr = m_out_buffer[4] & 0xff; + addr <<= 8; + addr |= m_out_buffer[5] & 0xff; + addr <<= 8; + addr |= m_out_buffer[6] & 0xff; + addr <<= 8; + addr |= m_out_buffer[7] & 0xff; + + if (m_out_buffer[8] != 0) + { + // in this case, we would have to read one byte at a time until we + // get to the null terminator. + std::printf("socks_connection::on_request1: username in SOCKS4 mode not supported\n"); + close_connection(); + return; + } + asio::ip::tcp::endpoint target(asio::ip::address_v4(addr), port); + if (command == 1) + { + open_forward_connection(target); + } + else if (command == 2) + { + bind_connection(target); + } + + return; + } + + if (command != 1 && command != 2 && command != 3) + { + std::printf("socks_connection::on_request1: unexpected command: %d\n" + , command); + close_connection(); + return; + } + + if (m_out_buffer[2] != 0) + { + std::printf("socks_connection::on_request1: reserved byte is non-zero: %d\n" + , int(m_out_buffer[2])); + close_connection(); + return; + } + + int atyp = unsigned(m_out_buffer[3]); + + if (atyp != 1 && atyp != 3 && atyp != 4) + { + std::printf("socks_connection::on_request1: unexpected address type in SOCKS request: %d\n" + , atyp); + close_connection(); + return; + } + + std::printf("socks_connection: received %s request address type: %d\n" + , command == 1 ? "CONNECT" + : command == 2 ? "BIND" + : "UDP_ASSOCIATE", atyp); + + switch (atyp) + { + case 1: { // IPv4 address (we have the whole request already) + +// +----+-----+-------+------+----------+----------+ +// |VER | CMD | RSV | ATYP | BND.ADDR | BND.PORT | +// +----+-----+-------+------+----------+----------+ +// | 1 | 1 | X'00' | 1 | 4 | 2 | +// +----+-----+-------+------+----------+----------+ + + std::uint32_t addr = m_out_buffer[4] & 0xff; + addr <<= 8; + addr |= m_out_buffer[5] & 0xff; + addr <<= 8; + addr |= m_out_buffer[6] & 0xff; + addr <<= 8; + addr |= m_out_buffer[7] & 0xff; + + std::uint16_t port = m_out_buffer[8] & 0xff; + port <<= 8; + port |= m_out_buffer[9] & 0xff; + + asio::ip::tcp::endpoint target(asio::ip::address_v4(addr), port); + if (command == 1) + { + open_forward_connection(target); + } + else if (command == 2) + { + bind_connection(target); + } + else if (command == 3) + { + if (target.address() == address()) + { + target.address(m_client_connection.remote_endpoint().address()); + } + udp_associate(target); + } + + break; + } + case 3: { // domain name + +// +----+-----+-------+------+-----+----------+----------+ +// |VER | CMD | RSV | ATYP | LEN | BND.ADDR | BND.PORT | +// +----+-----+-------+------+-----+----------+----------+ +// | 1 | 1 | X'00' | 1 | 1 | Variable | 2 | +// +----+-----+-------+------+-----+----------+----------+ + + if (command == 2) + { + std::printf("ERROR: cannot BIND to hostname address (only IPv4 or IPv6 addresses)\n"); + close_connection(); + return; + } + + const int len = std::uint8_t(m_out_buffer[4]); + // we already read an address of length 4, assuming it was an IPv4 + // address. Now, with a domain name, one of those bytes was the + // length-prefix, but we still read 3 bytes already. + const int additional_bytes = len - 3; + asio::async_read(m_client_connection, asio::buffer(&m_out_buffer[10], additional_bytes) + , std::bind(&socks_connection::on_request_domain_name + , shared_from_this(), std::placeholders::_1, std::placeholders::_2)); + break; + } + case 4: // IPv6 address + +// +----+-----+-------+------+----------+----------+ +// |VER | CMD | RSV | ATYP | BND.ADDR | BND.PORT | +// +----+-----+-------+------+----------+----------+ +// | 1 | 1 | X'00' | 1 | 16 | 2 | +// +----+-----+-------+------+----------+----------+ + + std::printf("ERROR: unsupported address type %d\n", atyp); + close_connection(); + } + } + + void socks_connection::on_request_domain_name(error_code const& ec, size_t bytes_transferred) + { + if (ec) + { + std::printf("socks_connection::on_request_domain_name(%s): (%d) %s\n" + , command(), ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + int const buffer_size = int(10 + bytes_transferred); + + std::uint16_t port = m_out_buffer[buffer_size - 2] & 0xff; + port <<= 8; + port |= m_out_buffer[buffer_size - 1] & 0xff; + + std::string hostname(&m_out_buffer[5], std::uint8_t(m_out_buffer[4])); + std::printf("socks_connection::on_request_domain_name(%s): hostname: %s port: %d\n" + , command(), hostname.c_str(), port); + + char port_str[10]; + std::snprintf(port_str, sizeof(port_str), "%d", port); + m_resolver.async_resolve(hostname, port_str + , std::bind(&socks_connection::on_request_domain_lookup + , shared_from_this(), std::placeholders::_1, std::placeholders::_2)); + } + + void socks_connection::on_request_domain_lookup(boost::system::error_code const& ec + , asio::ip::tcp::resolver::results_type const ips) + { + if (ec || ips.empty()) + { + if (ec) + { + std::printf("socks_connection::on_request_domain_lookup(%s): (%d) %s\n" + , command(), ec.value(), ec.message().c_str()); + } + else + { + std::printf("socks_connection::on_request_domain_lookup(%s): empty response\n" + , command()); + } + +// +----+-----+-------+------+----------+----------+ +// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | +// +----+-----+-------+------+----------+----------+ +// | 1 | 1 | X'00' | 1 | Variable | 2 | +// +----+-----+-------+------+----------+----------+ + + m_in_buffer[0] = char(m_version); // version + m_in_buffer[1] = 4; // response (host unreachable) + m_in_buffer[2] = 0; // reserved + m_in_buffer[3] = 1; // IPv4 + memset(&m_in_buffer[4], 0, 4); + m_in_buffer[8] = 0; // port + m_in_buffer[9] = 0; + + auto self = shared_from_this(); + asio::async_write(m_client_connection + , asio::buffer(&m_in_buffer[0], 10) + , [=](boost::system::error_code const&, size_t) + { + self->close_connection(); + }); + return; + } + + std::printf("socks_connection::on_request_domain_lookup(%s): connecting to: %s port: %d\n" + , command() + , ips.front().endpoint().address().to_string().c_str() + , ips.front().endpoint().port()); + open_forward_connection(ips.front().endpoint()); + } + + void socks_connection::open_forward_connection(const asio::ip::tcp::endpoint& target) + { + std::printf("socks_connection::open_forward_connection(%s): connecting to %s port %d\n" + , command(), target.address().to_string().c_str(), target.port()); + + m_server_connection.open(target.protocol()); + m_server_connection.async_connect(target + , std::bind(&socks_connection::on_connected, shared_from_this() + , std::placeholders::_1)); + } + + void socks_connection::bind_connection(const asio::ip::tcp::endpoint& target) + { + std::printf("socks_connection::bind_connection(%s): binding to %s port %d\n" + , command(), target.address().to_string().c_str(), target.port()); + + error_code ec; + m_bind_socket.open(target.protocol(), ec); + if (ec) + { + std::printf("ERROR: open bind socket failed: (%d) %s\n", ec.value() + , ec.message().c_str()); + } + else + { + m_bind_socket.bind(target, ec); + } + + int const response = ec + ? (m_version == 4 ? 91 : 1) + : (m_version == 4 ? 90 : 0); + tcp::endpoint ep = m_bind_socket.local_endpoint(); + int const len = format_response(ep.address(), ep.port(), response); + + if (ec) + { + std::printf("ERROR: binding socket to %s %d failed: (%d) %s\n" + , target.address().to_string().c_str() + , target.port() + , ec.value() + , ec.message().c_str()); + + auto self = shared_from_this(); + + asio::async_write(m_client_connection + , asio::buffer(&m_in_buffer[0], len) + , [=](boost::system::error_code const&, size_t) + { + self->close_connection(); + }); + return; + } + + // send response + asio::async_write(m_client_connection + , asio::buffer(&m_in_buffer[0], len) + , std::bind(&socks_connection::start_accept, shared_from_this(), std::placeholders::_1)); + } + + void socks_connection::udp_associate(const asio::ip::tcp::endpoint& target) + { + std::printf("socks_connection::udp_associate(%s): %s:%d\n" + , command(), target.address().to_string().c_str(), target.port()); + + m_udp_associate_ep.address(target.address()); + m_udp_associate_ep.port(target.port()); + + error_code ec; + m_udp_associate.open(m_udp_associate_ep.protocol(), ec); + if (ec) + { + std::printf("ERROR: open UDP associate socket failed: (%d) %s\n", ec.value() + , ec.message().c_str()); + } + else + { + m_udp_associate.bind(udp::endpoint(address_v4(), std::uint16_t(m_bind_port++)), ec); + if (ec) + { + std::printf("ERROR: binding socket failed: (%d) %s\n" + , ec.value(), ec.message().c_str()); + } + else + { + m_udp_associate.non_blocking(true); + m_udp_associate.async_receive_from(boost::asio::buffer(m_udp_buffer) + , m_udp_from, 0, std::bind(&socks_connection::on_read_udp, this, std::placeholders::_1, std::placeholders::_2)); + } + } + + int const response = ec ? 1 : 0; + udp::endpoint ep = m_udp_associate.local_bound_to(); + int const len = (m_flags & udp_associate_respond_empty_hostname) + ? format_hostname_response("foobar", ep.port(), response) + : format_response(ep.address(), ep.port(), response); + + if (ec) + { + auto self = shared_from_this(); + + asio::async_write(m_client_connection + , asio::buffer(&m_in_buffer[0], len) + , [=](boost::system::error_code const&, size_t) + { + self->close_connection(); + }); + return; + } + + // send response + asio::async_write(m_client_connection, asio::buffer(&m_in_buffer[0], len) + , std::bind(&socks_connection::wait_for_eof, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); + } + + void socks_connection::wait_for_eof(boost::system::error_code const& ec, std::size_t) + { + if (ec) + { + std::printf("socks_connection::wait_for_eof: %s\n", ec.message().c_str()); + m_udp_associate.close(); + m_udp_associate_ep = udp::endpoint(); + m_client_connection.close(); + return; + } + + if (m_flags & socks_flag::disconnect_udp_associate) + { + std::printf("socks_connection::wait_for_eof: closing connection prematurely\n"); + m_client_connection.close(); + return; + } + + m_client_connection.async_read_some( + asio::buffer(m_out_buffer) + , std::bind(&socks_connection::wait_for_eof, shared_from_this() + , std::placeholders::_1, std::placeholders::_2)); + } + + void socks_connection::on_read_udp(boost::system::error_code const& ec + , std::size_t bytes_transferred) + { + std::printf("socks_connection::on_read_udp\n"); + if (ec) + { + std::printf("socks_connection::on_read_udp: %s\n", ec.message().c_str()); + return; + } + + // if the client didn't specify an IP and port it would send packets from, + // we assumed the same IP as the TCP connection and assume the port is the + // same as the first UDP packet from that host + if (m_udp_associate_ep.port() == 0 + && m_udp_from.address() == m_udp_associate_ep.address()) + { + m_udp_associate_ep.port(m_udp_from.port()); + } + + if (m_udp_from == m_udp_associate_ep) + { + // read UDP ASSICOATE header and forward outgoing packet + // +----+------+------+----------+----------+----------+ + // |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | + // +----+------+------+----------+----------+----------+ + // | 2 | 1 | 1 | Variable | 2 | Variable | + // +----+------+------+----------+----------+----------+ + + char const* buf = m_udp_buffer.data(); + if (buf[2] != 0) std::printf("fragment != 0, not supported\n"); + + int const atyp = buf[3]; + if (atyp == 3) + { + // hostname + int const len = buf[4]; + + buf += 5; + bytes_transferred -= 5; + std::string const hostname(buf, len); + buf += len; + bytes_transferred -= len; + + std::uint16_t port = buf[0] & 0xff; + port <<= 8; + port |= buf[1] & 0xff; + buf += 2; + bytes_transferred -= 2; + + auto it = m_name_mapping.right.find(hostname); + if (it != m_name_mapping.right.end()) + { + error_code err; + m_udp_associate.send_to(boost::asio::buffer(buf, bytes_transferred) + , udp::endpoint(it->second, port), 0, err); + if (err) std::printf("send_to failed: %s\n", err.message().c_str()); + return; + } + + std::vector forward_buffer(buf, buf + bytes_transferred); + + m_udp_resolver.async_resolve(hostname.c_str(), std::to_string(port).c_str() + , [buf=std::move(forward_buffer), hostname, this] + (error_code const& ec, asio::ip::udp::resolver::results_type ips) + { + if (ec) + { + std::printf("resolve failed: %s\n", ec.message().c_str()); + return; + } + + for (auto const& ip : ips) + { + auto const target = ip.endpoint(); + error_code err; + m_udp_associate.send_to(boost::asio::buffer(buf) + , target, 0, err); + if (!err) + { + m_name_mapping.insert({target.address(), hostname}); + break; + } + std::printf("send_to failed: %s\n", err.message().c_str()); + } + }); + } + else if (atyp == 1) + { + // IPv4 + std::uint32_t addr = buf[4] & 0xff; + addr <<= 8; + addr |= buf[5] & 0xff; + addr <<= 8; + addr |= buf[6] & 0xff; + addr <<= 8; + addr |= buf[7] & 0xff; + + std::uint16_t port = buf[8] & 0xff; + port <<= 8; + port |= buf[9] & 0xff; + + buf += 10; + bytes_transferred -= 10; + + asio::ip::udp::endpoint const target(address_v4(addr), port); + + error_code err; + m_udp_associate.send_to(boost::asio::buffer(buf, bytes_transferred), target, 0, err); + if (err) std::printf("send_to failed: %s\n", err.message().c_str()); + } + else + { + std::printf("only supports IPv4 and hostname. ATYP: %d\n", atyp); + } + } + else + { + std::uint16_t const from_port = m_udp_from.port(); + + auto it = m_name_mapping.left.find(m_udp_from.address()); + if (it != m_name_mapping.left.end()) + { + std::vector header(7 + it->second.size()); + header[0] = 0; // RSV + header[1] = 0; + header[2] = 0; // fragment + header[3] = 3; // ATYP + header[4] = static_cast(it->second.size()); + int idx = 5; + std::copy(it->second.begin(), it->second.end(), header.data() + idx); + idx += it->second.size(); + header[idx] = (from_port >> 8) & 0xff; + header[idx + 1] = from_port & 0xff; + + std::array vec{{ + {header.data(), header.size()}, + {m_udp_buffer.data(), bytes_transferred}}}; + + error_code err; + m_udp_associate.send_to(vec, m_udp_associate_ep, 0, err); + if (err) std::printf("send_to failed: %s\n", err.message().c_str()); + } + else + { + // add UDP ASSOCIATE header and forward to client + std::uint32_t const from_addr = m_udp_from.address().to_v4().to_uint(); + std::array header; + header[0] = 0; // RSV + header[1] = 0; + header[2] = 0; // fragment + header[3] = 1; // ATYP + header[4] = (from_addr >> 24) & 0xff; // Address + header[5] = (from_addr >> 16) & 0xff; + header[6] = (from_addr >> 8) & 0xff; + header[7] = (from_addr) & 0xff; + header[8] = (from_port >> 8) & 0xff; + header[9] = from_port & 0xff; + + std::array vec{{ + {header.data(), header.size()}, + {m_udp_buffer.data(), bytes_transferred}}}; + + error_code err; + m_udp_associate.send_to(vec, m_udp_associate_ep, 0, err); + if (err) std::printf("send_to failed: %s\n", err.message().c_str()); + } + } + + m_udp_associate.async_receive_from(boost::asio::buffer(m_udp_buffer) + , m_udp_from, 0, std::bind(&socks_connection::on_read_udp, this, std::placeholders::_1, std::placeholders::_2)); + } + + void socks_connection::start_accept(boost::system::error_code const& ec) + { + if (ec) + { + std::printf("socks_connection(%s): error writing to client: (%d) %s\n" + , command(), ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + m_bind_socket.listen(); + m_bind_socket.async_accept(m_server_connection + , std::bind(&socks_connection::on_connected + , shared_from_this(), std::placeholders::_1)); + + m_client_connection.async_read_some( + sim::asio::buffer(m_out_buffer) + , std::bind(&socks_connection::on_client_receive, shared_from_this() + , std::placeholders::_1, std::placeholders::_2)); + } + + int socks_connection::format_response(address const& addr, int const port + , int const response) + { + int i = 0; + if (m_version == 5) + { +// +----+-----+-------+------+----------+----------+ +// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | +// +----+-----+-------+------+----------+----------+ +// | 1 | 1 | X'00' | 1 | Variable | 2 | +// +----+-----+-------+------+----------+----------+ + + m_in_buffer[i++] = char(m_version); // version + m_in_buffer[i++] = char(response); // response + m_in_buffer[i++] = 0; // reserved + if (addr.is_v4()) + { + m_in_buffer[i++] = 1; // IPv4 + address_v4::bytes_type b = addr.to_v4().to_bytes(); + memcpy(&m_in_buffer[i], &b[0], b.size()); + i += int(b.size()); + } else { + m_in_buffer[i++] = 4; // IPv6 + address_v6::bytes_type b = addr.to_v6().to_bytes(); + memcpy(&m_in_buffer[i], &b[0], b.size()); + i += int(b.size()); + } + + m_in_buffer[i++] = (port >> 8) & 0xff; + m_in_buffer[i++] = port & 0xff; + } + else + { + m_in_buffer[i++] = 0; // response version + m_in_buffer[i++] = char(response); // return code + + assert(addr.is_v4()); + + m_in_buffer[i++] = (port >> 8) & 0xff; + m_in_buffer[i++] = port & 0xff; + + address_v4::bytes_type b = addr.to_v4().to_bytes(); + memcpy(&m_in_buffer[i], &b[0], b.size()); + i += int(b.size()); + } + return i; + } + + int socks_connection::format_hostname_response(char const* hostname, int const port + , int const response) + { + int i = 0; + if (m_version != 5) + { + std::printf("socks_connection: hostname response requires SOCKS v5\n"); + close_connection(); + return 0; + } +// +----+-----+-------+------+----------+----------+ +// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | +// +----+-----+-------+------+----------+----------+ +// | 1 | 1 | X'00' | 1 | Variable | 2 | +// +----+-----+-------+------+----------+----------+ + + m_in_buffer[i++] = char(m_version); // version + m_in_buffer[i++] = char(response); // response + m_in_buffer[i++] = 0; // reserved + m_in_buffer[i++] = 3; // DOMAINNAME + m_in_buffer[i++] = std::uint8_t(::strlen(hostname)); + for (; *hostname != '\0'; ++hostname) + m_in_buffer[i++] = *hostname; + + m_in_buffer[i++] = (port >> 8) & 0xff; + m_in_buffer[i++] = port & 0xff; + return i; + } + + void socks_connection::on_connected(boost::system::error_code const& ec) + { + std::printf("socks_connection(%s): on_connect: (%d) %s\n" + , command(), ec.value(), ec.message().c_str()); + + if (ec == asio::error::operation_aborted + || ec == boost::system::errc::bad_file_descriptor) + { + return; + } + + boost::system::error_code err; + asio::ip::tcp::endpoint const ep = m_server_connection.remote_endpoint(err); + if (!err) + { + std::printf("socks_connection(%s): remote_endpoint: %s %d\n" + , command(), ep.address().to_string().c_str(), ep.port()); + } + + int const response = ec + ? (m_version == 4 ? 91 : 5) + : (m_version == 4 ? 90 : 0); + int const len = format_response(ep.address(), ep.port(), response); + + if (ec) + { + std::printf("socks_connection(%s): failed to connect to/accept from target server: (%d) %s\n" + , command(), ec.value(), ec.message().c_str()); + + auto self = shared_from_this(); + + asio::async_write(m_client_connection + , asio::buffer(&m_in_buffer[0], len) + , [=](boost::system::error_code const&, size_t) + { + self->close_connection(); + }); + return; + } + + auto self = shared_from_this(); + + asio::async_write(m_client_connection + , asio::buffer(&m_in_buffer[0], len) + , [=](boost::system::error_code const& ec, size_t) + { + if (ec) + { + std::printf("socks_connection(%s): error writing to client: (%d) %s\n" + , command(), ec.value(), ec.message().c_str()); + return; + } + + // read from the client and from the server + self->m_server_connection.async_read_some( + sim::asio::buffer(m_in_buffer) + , std::bind(&socks_connection::on_server_receive, self + , std::placeholders::_1, std::placeholders::_2)); + self->m_client_connection.async_read_some( + sim::asio::buffer(m_out_buffer) + , std::bind(&socks_connection::on_client_receive, self + , std::placeholders::_1, std::placeholders::_2)); + }); + } + + // we received some data from the client, forward it to the server + void socks_connection::on_client_receive(boost::system::error_code const& ec + , std::size_t bytes_transferred) + { + // bad file descriptor means the socket has been closed. Whoever closed + // the socket will have opened a new one, we cannot call + // close_connection() + if (ec == asio::error::operation_aborted + || ec == boost::system::errc::bad_file_descriptor) + return; + + if (ec) + { + std::printf("socks_connection (%s): error reading from client: (%d) %s\n" + , command(), ec.value(), ec.message().c_str()); + close_connection(); + return; + } + asio::async_write(m_server_connection, asio::buffer(&m_out_buffer[0], bytes_transferred) + , std::bind(&socks_connection::on_client_forward, shared_from_this() + , std::placeholders::_1, std::placeholders::_2)); + } + + void socks_connection::on_client_forward(error_code const& ec + , size_t /* bytes_transferred */) + { + if (ec) + { + std::printf("socks_connection(%s): error writing to server: (%d) %s\n" + , command(), ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + m_client_connection.async_read_some( + sim::asio::buffer(m_out_buffer) + , std::bind(&socks_connection::on_client_receive, shared_from_this() + , std::placeholders::_1, std::placeholders::_2)); + } + + // we received some data from the server, forward it to the server + void socks_connection::on_server_receive(boost::system::error_code const& ec + , std::size_t bytes_transferred) + { + if (ec) + { + std::printf("socks_connection(%s): error reading from server: (%d) %s\n" + , command(), ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + asio::async_write(m_client_connection, asio::buffer(&m_in_buffer[0], bytes_transferred) + , std::bind(&socks_connection::on_server_forward, shared_from_this() + , std::placeholders::_1, std::placeholders::_2)); + } + + void socks_connection::on_server_forward(error_code const& ec + , size_t /* bytes_transferred */) + { + if (ec) + { + std::printf("socks_connection(%s): error writing to client: (%d) %s\n" + , command(), ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + m_server_connection.async_read_some( + sim::asio::buffer(m_in_buffer) + , std::bind(&socks_connection::on_server_receive, shared_from_this() + , std::placeholders::_1, std::placeholders::_2)); + } + + void socks_connection::close_connection() + { + error_code err; + m_client_connection.close(err); + if (err) + { + std::printf("socks_connection::close: failed to close client connection (%d) %s\n" + , err.value(), err.message().c_str()); + } + m_server_connection.close(err); + if (err) + { + std::printf("socks_connection::close: failed to close server connection (%d) %s\n" + , err.value(), err.message().c_str()); + } + + m_bind_socket.close(err); + if (err) + { + std::printf("socks_connection::close: failed to close bind socket (%d) %s\n" + , err.value(), err.message().c_str()); + } + } + + char const* socks_connection::command() const + { + switch (m_command) + { + case 1: return "CONNECT"; + case 2: return "BIND"; + case 3: return "UDP_ASSOCIATE"; + default: return "UNKNOWN"; + } + } +} + + diff --git a/simulation/libsimulator/src/tcp_socket.cpp b/simulation/libsimulator/src/tcp_socket.cpp new file mode 100644 index 0000000..302fdcf --- /dev/null +++ b/simulation/libsimulator/src/tcp_socket.cpp @@ -0,0 +1,884 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/packet.hpp" +#include "simulator/pcap.hpp" +#include "simulator/handler_allocator.hpp" + +#include +#include +#include // for printf + +#include "simulator/push_warnings.hpp" +#include +#include +#include "simulator/pop_warnings.hpp" + +typedef sim::chrono::high_resolution_clock::time_point time_point; +typedef sim::chrono::high_resolution_clock::duration duration; + +using namespace std::placeholders; + +namespace sim { +namespace asio { +namespace ip { + + tcp::socket::socket(io_context& ios) + : socket_base(ios) + , m_connect_timer(ios) + , m_recv_timer(ios) + {} + + tcp::socket::socket(socket&& s) + : socket_base(std::move(s)) + , m_connect_handler(std::move(s.m_connect_handler)) + , m_connect_timer(std::move(s.m_connect_timer)) + , m_mss(s.m_mss) + , m_send_handler(std::move(s.m_send_handler)) + , m_wait_send_handler(std::move(s.m_wait_send_handler)) + , m_send_buffer(std::move(s.m_send_buffer)) + , m_incoming_queue(std::move(s.m_incoming_queue)) + , m_queue_size(std::move(s.m_queue_size)) + , m_recv_handler(std::move(s.m_recv_handler)) + , m_wait_recv_handler(std::move(s.m_wait_recv_handler)) + , m_recv_buffer(std::move(s.m_recv_buffer)) + , m_recv_timer(std::move(s.m_recv_timer)) + , m_is_v4(std::move(s.m_is_v4)) + , m_recv_null_buffers(std::move(s.m_recv_null_buffers)) + , m_send_null_buffers(std::move(s.m_send_null_buffers)) + , m_channel(std::move(s.m_channel)) + , m_next_outgoing_seq(std::move(s.m_next_outgoing_seq)) + , m_next_incoming_seq(std::move(s.m_next_incoming_seq)) + , m_last_drop_seq(std::move(s.m_last_drop_seq)) + , m_cwnd(std::move(s.m_cwnd)) + , m_bytes_in_flight(std::move(s.m_bytes_in_flight)) + , m_reorder_buffer(std::move(s.m_reorder_buffer)) + , m_outstanding_packet_sizes(std::move(s.m_outstanding_packet_sizes)) + , m_outgoing_packets(std::move(s.m_outgoing_packets)) + { + if (m_forwarder) m_forwarder->reset(this); + s.m_forwarder.reset(); + s.m_open = false; + s.m_bound_to = ip::tcp::endpoint(); + + if (m_bound_to != ip::tcp::endpoint()) + m_io_service.rebind_socket(&s, this, m_bound_to); + } + + tcp::socket::~socket() + { + boost::system::error_code ec; + + m_channel.reset(); + + if (m_bound_to != ip::tcp::endpoint()) + { + m_io_service.unbind_socket(this, m_bound_to); + m_bound_to = ip::tcp::endpoint(); + m_user_bound_to = ip::tcp::endpoint(); + } + m_open = false; + + // prevent any more packets from being delivered to this socket + if (m_forwarder) + { + m_forwarder->reset(); + m_forwarder.reset(); + } + cancel(ec); + } + + void tcp::socket::open(tcp protocol, boost::system::error_code& ec) try + { + close(ec); + m_open = true; + m_is_v4 = (protocol == ip::tcp::v4()); + ec.clear(); + m_forwarder = std::make_shared(this); + } + catch (std::bad_alloc const&) + { + ec = make_error_code(boost::system::errc::not_enough_memory); + } + catch (boost::system::system_error const& err) + { + ec = err.code(); + } + + void tcp::socket::open(tcp protocol) + { + boost::system::error_code ec; + open(protocol, ec); + if (ec) throw boost::system::system_error(ec); + } + + // used to attach an incoming connection to this + void tcp::socket::internal_connect(tcp::endpoint const& bind_ip + , std::shared_ptr const& c + , boost::system::error_code& ec) + { + open(m_is_v4 ? tcp::v4() : tcp::v6(), ec); + if (ec) + { + std::printf("tcp::socket::internal_connect() error: (%d) %s\n" + , ec.value(), ec.message().c_str()); + return; + } + m_bound_to = bind_ip; + m_user_bound_to = bind_ip; + m_channel = c; + assert(m_forwarder); + c->hops[1].replace_last(m_forwarder); + } + + void tcp::socket::bind(ip::tcp::endpoint const& ep + , boost::system::error_code& ec) try + { + if (!m_open) + { + ec = error::bad_descriptor; + return; + } + + if (ep.address().is_v4() != m_is_v4) + { + ec = error::address_family_not_supported; + return; + } + + ip::tcp::endpoint addr = m_io_service.bind_socket(this, ep, ec); + if (ec) return; + m_bound_to = addr; + m_user_bound_to = addr; + } + catch (std::bad_alloc const&) + { + ec = make_error_code(boost::system::errc::not_enough_memory); + } + catch (boost::system::system_error const& err) + { + ec = err.code(); + } + + void tcp::socket::bind(ip::tcp::endpoint const& ep) + { + boost::system::error_code ec; + bind(ep, ec); + if (ec) throw boost::system::system_error(ec); + } + + void tcp::socket::close() + { + boost::system::error_code ec; + close(ec); + if (ec) throw boost::system::system_error(ec); + } + + void tcp::socket::close(boost::system::error_code& ec) try + { + if (m_channel) + { + int const remote = m_channel->remote_idx(m_bound_to); + route hops = m_channel->hops[remote]; + + // if m_connect_handler is still set, it means the connection hasn't + // been established yet, and this channel points to the acceptor + // socket, not another open TCP connection. + if (!hops.empty() && !m_connect_handler) + { + aux::packet p; + p.type = aux::packet::type_t::error; + p.ec = asio::error::eof; + p.from = asio::ip::udp::endpoint( + m_bound_to.address(), m_bound_to.port()); + p.overhead = 40; + p.hops = hops; + p.seq_nr = m_next_outgoing_seq++; + send_packet(std::move(p)); + } + m_channel.reset(); + } + + if (m_bound_to != ip::tcp::endpoint()) + { + m_io_service.unbind_socket(this, m_bound_to); + m_bound_to = ip::tcp::endpoint(); + m_user_bound_to = ip::tcp::endpoint(); + } + m_open = false; + + // prevent any more packets from being delivered to this socket + if (m_forwarder) + { + m_forwarder->reset(); + m_forwarder.reset(); + } + + // reset socket state + m_queue_size = 0; + m_mss = 1475; + m_cwnd = m_mss * 2; + m_bytes_in_flight = 0; + m_outstanding_packet_sizes.clear(); + m_recv_null_buffers = false; + m_send_null_buffers = false; + m_next_incoming_seq = 0; + m_next_outgoing_seq = 0; + m_last_drop_seq = 0; + + cancel(ec); + + ec.clear(); + } + catch (std::bad_alloc const&) + { + ec = make_error_code(boost::system::errc::not_enough_memory); + } + catch (boost::system::system_error const& err) + { + ec = err.code(); + } + + std::size_t tcp::socket::available(boost::system::error_code& ec) const + { + if (!m_open) + { + ec = boost::system::error_code(error::bad_descriptor); + return 0; + } + if (!m_channel) + { + ec = boost::system::error_code(error::not_connected); + return 0; + } + if (m_incoming_queue.empty()) + { + return 0; + } + + std::size_t ret = 0; + for (aux::packet const& p : m_incoming_queue) + { + if (p.type == aux::packet::type_t::error) + { + if (ret > 0) return ret; + + // if the read buffer is drained and there is an error, report that + // error. + ec = p.ec; + return 0; + } + ret += p.buffer.size(); + } + return ret; + } + + std::size_t tcp::socket::available() const + { + boost::system::error_code ec; + std::size_t ret = available(ec); + if (ec) throw boost::system::system_error(ec); + return ret; + } + + void tcp::socket::cancel(boost::system::error_code&) + { + abort_recv_handlers(); + abort_send_handlers(); + + if (m_connect_handler) + { + post(m_io_service, aux::make_malloc(std::bind(std::move(m_connect_handler) + , boost::system::error_code(error::operation_aborted)))); + m_connect_handler = nullptr; + } + } + + void tcp::socket::cancel() + { + boost::system::error_code ec; + cancel(ec); + if (ec) throw boost::system::system_error(ec); + } + + tcp::endpoint tcp::socket::remote_endpoint(boost::system::error_code& ec) const + { + if (!m_open) + { + ec = error::bad_descriptor; + return tcp::endpoint(); + } + + if (!m_channel) + { + ec = error::not_connected; + return tcp::endpoint(); + } + + int const remote = m_channel->remote_idx(m_bound_to); + return m_channel->visible_ep[remote]; + } + + tcp::endpoint tcp::socket::remote_endpoint() const + { + boost::system::error_code ec; + tcp::endpoint ret = remote_endpoint(ec); + if (ec) throw boost::system::system_error(ec); + return ret; + } + + void tcp::socket::async_connect(tcp::endpoint const& target + , aux::function h) + { + if (!m_open) open(target.protocol()); + + assert(h); + assert(!m_connect_handler); + + // find remote socket + boost::system::error_code ec; + if (m_bound_to.address() == ip::address()) + { + auto endpoint = ip::tcp::endpoint(); + if (target.address().is_v4()) { + endpoint.address(ip::address_v4::any()); + } else { + endpoint.address(ip::address_v6::any()); + } + ip::tcp::endpoint addr = m_io_service.bind_socket(this + , endpoint, ec); + if (ec) + { + post(m_io_service, aux::make_malloc(std::bind(std::move(h), ec))); + return; + } + m_bound_to = addr; + m_user_bound_to = addr; + } + if (m_bound_to.address().is_v4() != target.address().is_v4()) + { + post(m_io_service, aux::make_malloc(std::bind(std::move(h), + boost::system::error_code(error::address_family_not_supported)))); + return; + } + m_channel = m_io_service.internal_connect(this, target, ec); + m_mss = m_io_service.get_path_mtu(m_bound_to.address(), target.address()); + m_cwnd = m_mss * 2; + if (ec) + { + m_channel.reset(); + // TODO: ask the policy object what the round-trip to this endpoint is + m_connect_timer.expires_after(chrono::milliseconds(50)); + m_connect_timer.async_wait(aux::make_malloc(std::bind(std::move(h), ec))); + return; + } + + m_connect_handler = std::move(h); + + // the acceptor socket will call internal_connect_complete once the + // connection is established + } + + void tcp::socket::abort_recv_handlers() + { + if (m_recv_handler) post(m_io_service, aux::make_malloc(std::bind(std::move(m_recv_handler) + , boost::system::error_code(error::operation_aborted), std::size_t(0)))); + + if (m_wait_recv_handler) post(m_io_service, aux::make_malloc(std::bind(std::move(m_wait_recv_handler) + , boost::system::error_code(error::operation_aborted)))); + + m_recv_timer.cancel(); + m_recv_handler = nullptr; + m_wait_recv_handler = nullptr; + m_recv_buffer.clear(); + m_recv_null_buffers = false; + } + + void tcp::socket::abort_send_handlers() + { + if (m_send_handler) post(m_io_service, aux::make_malloc(std::bind(std::move(m_send_handler) + , boost::system::error_code(error::operation_aborted), std::size_t(0)))); + + if (m_wait_send_handler) post(m_io_service, aux::make_malloc(std::bind(std::move(m_wait_send_handler) + , boost::system::error_code(error::operation_aborted)))); + + m_send_handler = nullptr; + m_wait_send_handler = nullptr; + m_send_buffer.clear(); + m_send_null_buffers = false; + } + + void tcp::socket::async_write_some_impl(std::vector const& bufs + , aux::function handler) + { + int buf_size = 0; + for (int i = 0; i < int(bufs.size()); ++i) + buf_size += int(bufs[i].size()); + + boost::system::error_code ec; + std::size_t const bytes_transferred = write_some_impl(bufs, ec); + if (ec == boost::system::error_code(error::would_block)) + { + m_send_handler = std::move(handler); + m_send_buffer = bufs; + return; + } + + if (ec) + { + post(m_io_service, aux::make_malloc(std::bind(std::move(handler), ec, std::size_t(0)))); + m_send_handler = nullptr; + m_send_buffer.clear(); + return; + } + + boost::system::error_code no_error; + post(m_io_service, aux::make_malloc(std::bind(std::move(handler), no_error + , bytes_transferred))); + m_send_handler = nullptr; + m_send_buffer.clear(); + } + + std::size_t tcp::socket::write_some_impl( + std::vector const& bufs + , boost::system::error_code& ec) + { + if (!m_open) + { + ec = boost::system::error_code(error::bad_descriptor); + return 0; + } + if (!m_channel) + { + ec = boost::system::error_code(error::not_connected); + return 0; + } + + // the connect handler is used as proxy that this socket has not competed + // the connection yet. We're still waiting for SYN+ACK + if (m_connect_handler) + { + ec = boost::system::error_code(error::would_block); + return 0; + } + + int const remote = m_channel->remote_idx(m_bound_to); + route hops = m_channel->hops[remote]; + if (hops.empty()) + { + ec = boost::system::error_code(error::not_connected); + return 0; + } + + if (m_bytes_in_flight + m_mss > m_cwnd) + { + // this indicates that the send buffer is very large, we should + // probably not be able to stuff more bytes down it + // wait for the receiving end to pop some bytes off + ec = boost::system::error_code(error::would_block); + return 0; + } + + std::size_t ret = 0; + + for (auto const& buf : bufs) + { + // split up in packets + int buf_size = int(buf.size()); + std::uint8_t const* ptr = static_cast(buf.data()); + while (buf_size > 0) + { + int packet_size = (std::min)(buf_size, m_mss); + aux::packet p; + p.type = aux::packet::type_t::payload; + p.buffer.assign(ptr, ptr + packet_size); + p.from = asio::ip::udp::endpoint( + m_bound_to.address(), m_bound_to.port()); + p.overhead = 40; + p.hops = hops; + p.seq_nr = m_next_outgoing_seq++; + p.drop_fun = std::bind(&tcp::socket::packet_dropped, this, _1); + + send_packet(std::move(p)); + ptr += packet_size; + buf_size -= packet_size; + ret += packet_size; + + if (m_bytes_in_flight + m_mss > m_cwnd) + { + // the congestion window is full + if (ret == 0) + { + ec = boost::system::error_code(error::would_block); + return 0; + } + + return ret; + } + } + } + + return ret; + } + + std::size_t tcp::socket::read_some_impl( + std::vector const& bufs + , boost::system::error_code& ec) + { + assert(!bufs.empty()); + if (!m_open) + { + ec = boost::system::error_code(error::bad_descriptor); + return 0; + } + if (!m_channel) + { + ec = boost::system::error_code(error::not_connected); + return 0; + } + if (m_connect_handler) + { + // the socket is not done connecting yet + ec = boost::system::error_code(error::would_block); + return 0; + } + + if (m_incoming_queue.empty()) + { + ec = boost::system::error_code(error::would_block); + return 0; + } + + typedef std::vector buffers_t; + m_recv_buffer = bufs; + buffers_t::iterator recv_iter = m_recv_buffer.begin(); + int total_received = 0; + // the offset in the current receive buffer we're writing to. i.e. the + // buffer recv_iter points to + int buf_offset = 0; + + while (!m_incoming_queue.empty()) + { + aux::packet& p = m_incoming_queue.front(); + + if (p.type == aux::packet::type_t::error) + { + // if we have received bytes also, first deliver those. In the next + // read, deliver the error + if (total_received > 0) break; + + assert(p.ec); + ec = p.ec; + m_incoming_queue.erase(m_incoming_queue.begin()); + m_channel.reset(); + return 0; + } + else if (p.type == aux::packet::type_t::payload) + { + // copy bytes from the incoming queue into the receive buffer. + // both are vectors of buffer, so it can get a bit hairy + while (recv_iter != m_recv_buffer.end()) + { + int const buf_size = int(recv_iter->size()); + int const copy_size = (std::min)(int(p.buffer.size()) + , buf_size - buf_offset); + + memcpy(static_cast(recv_iter->data()) + buf_offset + , p.buffer.data(), copy_size); + + p.buffer.erase(p.buffer.begin(), p.buffer.begin() + copy_size); + m_queue_size -= copy_size; + + buf_offset += copy_size; + assert(buf_offset <= buf_size); + total_received += copy_size; + if (buf_offset == buf_size) + { + ++recv_iter; + buf_offset = 0; + } + + if (p.buffer.empty()) + { + m_incoming_queue.erase(m_incoming_queue.begin()); + break; + } + } + } + else + { + assert(false); + } + + if (recv_iter == m_recv_buffer.end()) + break; + } + + assert(total_received > 0); + + ec.clear(); + return total_received; + } + + void tcp::socket::async_read_some_impl(std::vector const& bufs + , aux::function handler) + { + assert(!bufs.empty()); + assert(bufs[0].size()); + + boost::system::error_code ec; + std::size_t bytes_transferred = read_some_impl(bufs, ec); + if (ec == boost::system::error_code(error::would_block)) + { + assert(m_incoming_queue.empty()); + + m_recv_buffer = bufs; + m_recv_handler = std::move(handler); + m_recv_null_buffers = false; + return; + } + + if (ec) + { + post(m_io_service, aux::make_malloc(std::bind(std::move(handler), ec, std::size_t(0)))); + m_recv_handler = nullptr; + m_recv_buffer.clear(); + return; + } + + post(m_io_service, aux::make_malloc(std::bind(std::move(handler), ec, bytes_transferred))); + m_recv_handler = nullptr; + m_recv_buffer.clear(); + } + + void tcp::socket::async_wait_read_impl( + aux::function handler) + { + boost::system::error_code ec; + // null_buffers notifies the handler when data is available, without + // reading any + int const bytes = int(available(ec)); + if (ec) + { + post(m_io_service, aux::make_malloc(std::bind(std::move(handler), ec))); + m_recv_handler = nullptr; + m_recv_buffer.clear(); + return; + } + + if (bytes > 0) + { + post(m_io_service, aux::make_malloc(std::bind(std::move(handler), ec))); + m_recv_handler = nullptr; + m_recv_buffer.clear(); + return; + } + + m_wait_recv_handler = std::move(handler); + m_recv_null_buffers = true; + } + + // if there is an outstanding read operation, and this was the first incoming + // operation since we last drained, wake up the reader + void tcp::socket::maybe_wakeup_reader() + { + if (m_incoming_queue.size() != 1 || (!m_recv_handler && !m_wait_recv_handler)) return; + + if (m_recv_null_buffers) + { + async_wait_read_impl(std::move(m_wait_recv_handler)); + } + else + { + // we have an async. read operation outstanding, and we just put one + // packet in our incoming queue. + + // try to read from it and potentially fire the handler + async_read_some_impl(m_recv_buffer, std::move(m_recv_handler)); + } + } + + void tcp::socket::maybe_wakeup_writer() + { + if (!m_send_handler) return; + + if (m_send_null_buffers) + { + assert(false && "not supported yet"); +// async_wait_write_impl(m_recv_handler); + } + else + { + // we have an async. write operation outstanding + async_write_some_impl(m_send_buffer, std::move(m_send_handler)); + } + } + + bool tcp::socket::internal_is_listening() { return false; } + + void tcp::socket::send_packet(aux::packet p) + { + m_bytes_in_flight += int(p.buffer.size()); + m_outstanding_packet_sizes[p.seq_nr] = int(p.buffer.size()); + + int const idx = m_channel->self_idx(m_bound_to); + p.byte_counter = m_channel->bytes_sent[idx]; + m_channel->bytes_sent[idx] += std::uint32_t(p.buffer.size()); + + auto* log = m_io_service.sim().get_pcap(); + if (log) + { + int const remote = m_channel->remote_idx(m_bound_to); + log->log_tcp(p, m_bound_to, m_channel->ep[remote]); + } + + forward_packet(std::move(p)); + } + + void tcp::socket::packet_dropped(aux::packet p) + { + int remote = m_channel->remote_idx(m_bound_to); + p.hops = m_channel->hops[remote]; + m_outgoing_packets.push_back(std::move(p)); + + const int packets_in_cwnd = m_cwnd / m_mss; + + // we just recently dropped a packet and cut the cwnd in half, + // don't do it again already + if (m_last_drop_seq > 0 && p.seq_nr < m_last_drop_seq + packets_in_cwnd) return; + + m_cwnd /= 2; + m_last_drop_seq = p.seq_nr; + + // TODO: this should really happen one second later to be accurate + if (m_cwnd < m_mss) m_cwnd = m_mss; + } + + void tcp::socket::incoming_packet(aux::packet p) + { + switch (p.type) + { + case aux::packet::type_t::uninitialized: + { + assert(false && "uninitialized packet"); + return; + } + case aux::packet::type_t::ack: + { + // if the socket just became writeable, we need to notify the + // client. First we want to know whether it was not writeable. + const bool was_writeable = m_bytes_in_flight + m_mss > m_cwnd; + + auto it = m_outstanding_packet_sizes.find(p.seq_nr); + assert(it != m_outstanding_packet_sizes.end()); + const int acked_bytes = it->second; + m_outstanding_packet_sizes.erase(it); + assert(m_bytes_in_flight >= acked_bytes); + m_bytes_in_flight -= acked_bytes; + + // potentially resend packets + while (!m_outgoing_packets.empty() + && m_bytes_in_flight + + int(m_outgoing_packets.front().buffer.size()) <= m_cwnd) + { + aux::packet pkt = std::move(m_outgoing_packets.front()); + m_outgoing_packets.erase(m_outgoing_packets.begin()); + send_packet(std::move(pkt)); + } + + // update cwnd based on the number of bytes ACKed. + // every round-trip, increase the window size by one packet + // (MSS) + m_cwnd += m_mss * acked_bytes / m_cwnd; + + // TODO: implement slow-start + + const bool is_writeable = m_bytes_in_flight + m_mss <= m_cwnd; + + if (!was_writeable && is_writeable) + maybe_wakeup_writer(); + + return; + } + case aux::packet::type_t::syn: + { + // TODO: return connection refused + return; + } + case aux::packet::type_t::syn_ack: + { + assert(m_connect_handler); + boost::system::error_code ec; + post(m_io_service, aux::make_malloc(std::bind(std::move(m_connect_handler), ec))); + m_connect_handler = nullptr; + if (ec) m_channel.reset(); + else maybe_wakeup_writer(); + return; + } + case aux::packet::type_t::error: + case aux::packet::type_t::payload: + { + aux::packet ack; + ack.type = aux::packet::type_t::ack; + ack.seq_nr = p.seq_nr; + + int remote = m_channel->remote_idx(m_bound_to); + ack.hops = m_channel->hops[remote]; + forward_packet(std::move(ack)); + + // if the sequence number is out-of-order, put it in the + // m_incoming_packets queue + if (p.seq_nr != m_next_incoming_seq) + { + if (p.seq_nr < m_next_incoming_seq) + { + std::printf("TCP: incoming sequence number lower (%" PRId64 ") " + "than expected: %" PRId64 "\n", p.seq_nr, m_next_incoming_seq); + } + + m_reorder_buffer.emplace(p.seq_nr, std::move(p)); + return; + } + + // this packet was in-order. increment the expected next sequence + // number. + ++m_next_incoming_seq; + m_incoming_queue.push_back(std::move(p)); + + // also, perhaps there are some packets that arrived out-of-order, + // check to see + auto it = m_reorder_buffer.find(m_next_incoming_seq); + while (it != m_reorder_buffer.end()) + { + aux::packet pkt = std::move(it->second); + m_reorder_buffer.erase(it); + m_incoming_queue.push_back(std::move(pkt)); + ++m_next_incoming_seq; + it = m_reorder_buffer.find(m_next_incoming_seq); + } + + maybe_wakeup_reader(); + return; + } + } + } +} +} +} + diff --git a/simulation/libsimulator/src/udp_socket.cpp b/simulation/libsimulator/src/udp_socket.cpp new file mode 100644 index 0000000..f735c72 --- /dev/null +++ b/simulation/libsimulator/src/udp_socket.cpp @@ -0,0 +1,492 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/packet.hpp" +#include "simulator/pcap.hpp" +#include "simulator/handler_allocator.hpp" + +#include + +#include "simulator/push_warnings.hpp" +#include +#include +#include "simulator/pop_warnings.hpp" + +typedef sim::chrono::high_resolution_clock::time_point time_point; +typedef sim::chrono::high_resolution_clock::duration duration; + +namespace sim { +namespace asio { +namespace ip { + + udp::socket::socket(io_context& ios) + : socket_base(ios) + , m_next_send(chrono::high_resolution_clock::now()) + , m_recv_sender(nullptr) + , m_recv_timer(ios) + , m_send_timer(ios) + , m_recv_null_buffers(0) + , m_queue_size(0) + , m_is_v4(true) + {} + + udp::socket::socket(socket&& s) + : socket_base(std::move(s)) + , m_next_send(std::move(s.m_next_send)) + , m_send_handler(std::move(s.m_send_handler)) + , m_wait_send_handler(std::move(s.m_wait_send_handler)) + , m_recv_handler(std::move(s.m_recv_handler)) + , m_wait_recv_handler(std::move(s.m_wait_recv_handler)) + , m_recv_buffer(std::move(s.m_recv_buffer)) + , m_recv_sender(std::move(s.m_recv_sender)) + , m_recv_timer(std::move(s.m_recv_timer)) + , m_send_timer(std::move(s.m_send_timer)) + , m_incoming_queue(std::move(s.m_incoming_queue)) + , m_recv_null_buffers(std::move(s.m_recv_null_buffers)) + , m_queue_size(std::move(s.m_queue_size)) + , m_is_v4(s.m_is_v4) + { + if (m_forwarder) m_forwarder->reset(this); + s.m_forwarder.reset(); + s.m_open = false; + s.m_bound_to = ip::udp::endpoint(); + if (m_bound_to != ip::udp::endpoint()) + m_io_service.rebind_udp_socket(this, m_bound_to); + } + + udp::socket::~socket() + { + boost::system::error_code ec; + close(ec); + } + + void udp::socket::bind(ip::udp::endpoint const& ep + , boost::system::error_code& ec) try + { + if (!m_open) + { + ec = error::bad_descriptor; + return; + } + + if (ep.address().is_v4() != m_is_v4) + { + ec = error::address_family_not_supported; + return; + } + + ip::udp::endpoint addr = m_io_service.bind_udp_socket(this, ep, ec); + if (ec) return; + m_bound_to = addr; + m_user_bound_to = addr; + } + catch (std::bad_alloc const&) + { + ec = make_error_code(boost::system::errc::not_enough_memory); + } + catch (boost::system::system_error const& err) + { + ec = err.code(); + } + + void udp::socket::bind(ip::udp::endpoint const& ep) + { + boost::system::error_code ec; + bind(ep, ec); + if (ec) throw boost::system::system_error(ec); + } + + void udp::socket::open(udp protocol + , boost::system::error_code& ec) try + { + // TODO: what if it's already open? + close(ec); + m_open = true; + m_is_v4 = (protocol == ip::udp::v4()); + m_forwarder = std::make_shared(this); + } + catch (std::bad_alloc const&) + { + ec = make_error_code(boost::system::errc::not_enough_memory); + } + catch (boost::system::system_error const& err) + { + ec = err.code(); + } + + void udp::socket::open(udp protocol) + { + boost::system::error_code ec; + open(protocol, ec); + if (ec) throw boost::system::system_error(ec); + } + + void udp::socket::close() + { + boost::system::error_code ec; + close(ec); + if (ec) throw boost::system::system_error(ec); + } + + void udp::socket::close(boost::system::error_code& ec) try + { + if (m_bound_to != ip::udp::endpoint()) + { + m_io_service.unbind_udp_socket(this, m_bound_to); + m_bound_to = ip::udp::endpoint(); + m_user_bound_to = ip::udp::endpoint(); + } + m_open = false; + + // prevent any more packets from being delivered to this socket + if (m_forwarder) + { + m_forwarder->reset(); + m_forwarder.reset(); + } + + cancel(ec); + } + catch (std::bad_alloc const&) + { + ec = make_error_code(boost::system::errc::not_enough_memory); + } + catch (boost::system::system_error const& err) + { + ec = err.code(); + } + + void udp::socket::cancel(boost::system::error_code&) + { + // cancel outstanding async operations + abort_recv_handlers(); + abort_send_handlers(); + m_recv_timer.cancel(); + m_send_timer.cancel(); + return; + } + + void udp::socket::cancel() + { + boost::system::error_code ec; + cancel(ec); + if (ec) throw boost::system::system_error(ec); + } + + void udp::socket::abort_send_handlers() + { + if (m_send_handler) + post(m_io_service, make_malloc(std::bind(std::ref(m_send_handler) + , boost::system::error_code(error::operation_aborted), std::size_t(0)))); + + if (m_wait_send_handler) + post(m_io_service, make_malloc(std::bind(std::ref(m_wait_send_handler) + , boost::system::error_code(error::operation_aborted)))); + + m_send_timer.cancel(); + m_send_handler = nullptr; + m_wait_send_handler = nullptr; +// m_send_buffer.clear(); + } + + void udp::socket::abort_recv_handlers() + { + if (m_recv_handler) + post(m_io_service, make_malloc(std::bind(std::move(m_recv_handler) + , boost::system::error_code(error::operation_aborted), std::size_t(0)))); + + if (m_wait_recv_handler) + post(m_io_service, make_malloc(std::bind(std::move(m_wait_recv_handler) + , boost::system::error_code(error::operation_aborted)))); + + m_recv_timer.cancel(); + m_recv_handler = nullptr; + m_wait_recv_handler = nullptr; + m_recv_buffer.clear(); + } + + void udp::socket::async_wait(socket_base::wait_type_t const w + , aux::function handler) + { + if (w == wait_type_t::wait_write) + { + abort_send_handlers(); + + time_point const now = chrono::high_resolution_clock::now(); + boost::system::error_code no_error; + if (m_next_send - now > m_send_queue_time / 2) + { + // our send queue is too large. Defer + m_recv_timer.expires_at(m_next_send + m_send_queue_time / 2); + + m_wait_send_handler = std::move(handler); + m_recv_timer.async_wait(make_malloc(std::bind(std::ref(m_wait_send_handler), no_error))); + return; + } + + // the socket is writable, post the completion handler immediately + post(m_io_service, make_malloc(std::bind(std::move(handler), no_error))); + } + else if (w == wait_type_t::wait_read) + { + abort_recv_handlers(); + async_wait_receive_impl(nullptr, std::move(handler)); + } + } + + std::size_t udp::socket::receive_from_impl( + std::vector const& bufs + , udp::endpoint* sender + , socket_base::message_flags /* flags */ + , boost::system::error_code& ec) + { + assert(!bufs.empty()); + if (!m_open) + { + ec = boost::system::error_code(error::bad_descriptor); + return 0; + } + + if (m_bound_to == udp::endpoint()) + { + ec = boost::system::error_code(error::invalid_argument); + return 0; + } + + if (m_incoming_queue.empty()) + { + ec = boost::system::error_code(error::would_block); + return 0; + } + + aux::packet& p = m_incoming_queue.front(); + if (sender) *sender = p.from; + + int read = 0; + for (auto const& buf : bufs) + { + char* ptr = static_cast(buf.data()); + int const len = int(buf.size()); + int const to_copy = (std::min)(int(p.buffer.size()), len); + memcpy(ptr, p.buffer.data(), to_copy); + read += to_copy; + p.buffer.erase(p.buffer.begin(), p.buffer.begin() + to_copy); + m_queue_size -= to_copy; + if (p.buffer.empty()) break; + } + + m_incoming_queue.erase(m_incoming_queue.begin()); + return read; + } + + void udp::socket::async_wait_receive_impl( + udp::endpoint* sender + , aux::function handler) + { + if (!m_open) + { + post(m_io_service, make_malloc(std::bind(std::move(handler) + , boost::system::error_code(error::bad_descriptor)))); + return; + } + + if (m_bound_to == udp::endpoint()) + { + post(m_io_service, make_malloc(std::bind(std::move(handler) + , boost::system::error_code(error::invalid_argument)))); + return; + } + + if (!m_incoming_queue.empty()) + { + post(m_io_service, make_malloc(std::bind(std::move(handler), boost::system::error_code()))); + return; + } + + m_recv_null_buffers = true; + m_wait_recv_handler = std::move(handler); + m_recv_sender = sender; + } + + void udp::socket::async_receive_from_impl( + std::vector const& bufs + , udp::endpoint* sender + , socket_base::message_flags /* flags */ + , aux::function handler) + { + assert(!bufs.empty()); + + boost::system::error_code ec; + std::size_t bytes_transferred = receive_from_impl(bufs, sender, 0, ec); + if (ec == boost::system::error_code(error::would_block)) + { + m_recv_buffer = bufs; + m_recv_handler = std::move(handler); + m_recv_sender = sender; + m_recv_null_buffers = false; + + return; + } + + if (ec) + { + post(m_io_service, make_malloc(std::bind(std::move(handler), ec, std::size_t(0)))); + m_recv_handler = nullptr; + m_recv_buffer.clear(); + m_recv_sender = nullptr; + m_recv_null_buffers = false; + return; + } + + post(m_io_service, make_malloc(std::bind(std::move(handler), ec, bytes_transferred))); + m_recv_handler = nullptr; + m_recv_buffer.clear(); + m_recv_sender = nullptr; + m_recv_null_buffers = false; + } + + std::size_t udp::socket::send_to_impl(std::vector const& b + , udp::endpoint const& dst, message_flags /* flags */ + , boost::system::error_code& ec) + { + assert(m_non_blocking && "blocking operations not supported"); + + if (m_bound_to == ip::udp::endpoint()) + { + // the socket was not bound, bind to anything + bind(udp::endpoint(), ec); + if (ec) return 0; + } + + ec.clear(); + std::size_t ret = 0; + for (std::vector::const_iterator i = b.begin() + , end(b.end()); i != end; ++i) + { + ret += i->size(); + } + if (ret == 0) + { + ec = boost::system::error_code(error::invalid_argument); + return 0; + } + + time_point now = chrono::high_resolution_clock::now(); + + const int mtu = m_io_service.get_path_mtu(m_bound_to.address(), dst.address()); + + if (int(ret) > 65535) + { + ec = boost::system::error_code(error::message_size); + return 0; + } + + if (m_dont_fragment && int(ret) > mtu) + { + // silently drop packet + ec.clear(); + return ret; + } + + // determine the bandwidth in terms of nanoseconds / byte + const double nanoseconds_per_byte = 1000000000.0 + / double(aux::nic_bandwidth); + + if (m_next_send - now > m_send_queue_time) + { + // our send queue is too large. + ec = boost::system::error_code(asio::error::would_block); + return 0; + } + + route hops = m_io_service.find_udp_socket(*this, dst); + if (hops.empty()) + { + // the packet is silently dropped + // TODO: it would be nice if this would result in a round-trip time + // with an ICMP host unreachable or connection_refused error + return ret; + } + + hops.prepend(m_io_service.get_outgoing_route(m_bound_to.address())); + + m_next_send = std::max(now, m_next_send); + + aux::packet p; + p.overhead = 28; + p.type = aux::packet::type_t::payload; + p.from = m_bound_to; + p.hops = hops; + for (std::vector::const_iterator i = b.begin() + , end(b.end()); i != end; ++i) + { + p.buffer.insert(p.buffer.end(), static_cast(i->data()) + , static_cast(i->data()) + i->size()); + } + + auto* log = m_io_service.sim().get_pcap(); + if (log) log->log_udp(p, m_bound_to, dst); + + int const packet_size = int(p.buffer.size() + p.overhead); + forward_packet(std::move(p)); + + m_next_send += chrono::duration_cast(chrono::nanoseconds( + boost::int64_t(nanoseconds_per_byte * packet_size))); + + return ret; + } + + void udp::socket::incoming_packet(aux::packet p) + { + int const packet_size = int(p.buffer.size() + p.overhead); + + // silent drop. If the application isn't reading fast enough, drop packets + // TODO: make this limit controlled by SO_RECVBUF socket option + if (m_queue_size + packet_size > 256 * 1024) return; + + m_queue_size += int(p.buffer.size()); + m_incoming_queue.push_back(std::move(p)); + + maybe_wakeup_reader(); + } + + void udp::socket::maybe_wakeup_reader() + { + if (m_incoming_queue.size() != 1 || (!m_recv_handler && !m_wait_recv_handler)) return; + + // there is an outstanding operation waiting for an incoming packet + if (m_recv_null_buffers) + { + async_wait_receive_impl(m_recv_sender, std::move(m_wait_recv_handler)); + } + else + { + async_receive_from_impl(m_recv_buffer, m_recv_sender, 0, std::move(m_recv_handler)); + } + +// m_recv_handler = nullptr; +// m_recv_buffer.clear(); +// m_recv_sender = nullptr; + } + +} // ip +} // asio +} // sim + diff --git a/simulation/libsimulator/test/acceptor.cpp b/simulation/libsimulator/test/acceptor.cpp new file mode 100644 index 0000000..b308f3e --- /dev/null +++ b/simulation/libsimulator/test/acceptor.cpp @@ -0,0 +1,183 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include + +#include "catch.hpp" + +#ifdef __GNUC__ +// for CATCH's CHECK macro +#pragma GCC diagnostic ignored "-Wparentheses" +#endif + +using namespace sim::asio; +using namespace sim::chrono; +using sim::simulation; +using sim::default_config; +using namespace std::placeholders; + +namespace { +char send_buffer[10000]; +char recv_buffer[10000]; +int num_received = 0; +int num_sent = 0; + +void on_sent(boost::system::error_code const& ec, std::size_t bytes_transferred + , ip::tcp::socket& sock) +{ + int millis = int(duration_cast(high_resolution_clock::now() + .time_since_epoch()).count()); + if (ec) + { + std::printf("[%4d] send error %s\n", millis, ec.message().c_str()); + return; + } + + num_sent += bytes_transferred; + + std::printf("[%4d] sent %d bytes\n", millis, int(bytes_transferred)); + std::printf("closing\n"); + sock.close(); +} + +void on_receive(boost::system::error_code const& ec + , std::size_t bytes_transferred, ip::tcp::socket& sock) +{ + int millis = int(duration_cast(high_resolution_clock::now() + .time_since_epoch()).count()); + if (ec) + { + std::printf("[%4d] receive error %s\n", millis, ec.message().c_str()); + return; + } + + num_received += bytes_transferred; + + std::printf("[%4d] received %d bytes\n", millis, int(bytes_transferred)); + + sock.async_read_some(sim::asio::buffer(recv_buffer, sizeof(recv_buffer)) + , std::bind(&on_receive, _1, _2, std::ref(sock))); +} + +void incoming_connection(boost::system::error_code const& ec + , ip::tcp::socket& sock, ip::tcp::endpoint const& ep) +{ + int millis = int(duration_cast(high_resolution_clock::now() + .time_since_epoch()).count()); + if (ec) + { + std::printf("[%4d] error while accepting connection: %s\n" + , millis, ec.message().c_str()); + return; + } + + boost::system::error_code err; + ip::tcp::endpoint remote_endpoint = sock.remote_endpoint(err); + REQUIRE(!ec); + ip::tcp::endpoint local_endpoint = sock.local_endpoint(err); + REQUIRE(!ec); + std::printf("[%4d] received incoming connection from: %s:%d. local endpoint: %s:%d\n" + , millis, ep.address().to_string().c_str(), ep.port() + , local_endpoint.address().to_string().c_str(), local_endpoint.port()); + CHECK(local_endpoint.port() == 1337); + CHECK(local_endpoint.address().to_string() == "40.30.20.10"); + CHECK(remote_endpoint.port() != 0); + CHECK(remote_endpoint.address().to_string() == "10.20.30.40"); + + sock.async_read_some(sim::asio::buffer(recv_buffer, sizeof(recv_buffer)) + , std::bind(&on_receive, _1, _2, std::ref(sock))); +} + +void on_connected(boost::system::error_code const& ec + , ip::tcp::socket& sock) +{ + int millis = int(duration_cast(high_resolution_clock::now() + .time_since_epoch()).count()); + if (ec) + { + std::printf("[%4d] error while connecting: %s\n", millis, ec.message().c_str()); + return; + } + + boost::system::error_code err; + ip::tcp::endpoint remote_endpoint = sock.remote_endpoint(err); + REQUIRE(!ec); + ip::tcp::endpoint local_endpoint = sock.local_endpoint(err); + REQUIRE(!ec); + + std::printf("[%4d] made outgoing connection to: %s:%d. local endpoint: %s:%d\n" + , millis + , remote_endpoint.address().to_string().c_str(), remote_endpoint.port() + , local_endpoint.address().to_string().c_str(), local_endpoint.port()); + + CHECK(remote_endpoint.port() == 1337); + CHECK(remote_endpoint.address().to_string() == "40.30.20.10"); + CHECK(local_endpoint.port() != 0); + CHECK(local_endpoint.address().to_string() == "10.20.30.40"); + + std::printf("sending %d bytes\n", int(sizeof(send_buffer))); + sock.async_write_some(sim::asio::buffer(send_buffer, sizeof(send_buffer)) + , std::bind(&on_sent, _1, _2, std::ref(sock))); +} + +} + +TEST_CASE("accept incoming connection on acceptor socket", "[acceptor]") +{ + default_config cfg; + simulation sim(cfg); + io_context incoming_ios(sim, ip::make_address_v4("40.30.20.10")); + io_context outgoing_ios(sim, ip::make_address_v4("10.20.30.40")); + ip::tcp::acceptor listener(incoming_ios); + + int millis = int(duration_cast(high_resolution_clock::now() + .time_since_epoch()).count()); + + boost::system::error_code ec; + listener.open(ip::tcp::v4(), ec); + REQUIRE(!ec); + listener.bind(ip::tcp::endpoint(ip::address(), 1337), ec); + REQUIRE(!ec); + listener.listen(10, ec); + REQUIRE(!ec); + + ip::tcp::socket incoming(incoming_ios); + ip::tcp::endpoint remote_endpoint; + listener.async_accept(incoming, remote_endpoint + , std::bind(&incoming_connection, _1, std::ref(incoming) + , std::cref(remote_endpoint))); + + dump_network_graph(sim, "accept.dot"); + + std::printf("[%4d] connecting\n", millis); + ip::tcp::socket outgoing(outgoing_ios); + outgoing.open(ip::tcp::v4(), ec); + REQUIRE(!ec); + outgoing.async_connect(ip::tcp::endpoint(ip::make_address("40.30.20.10") + , 1337), std::bind(&on_connected, _1, std::ref(outgoing))); + + sim.run(); + + millis = int(duration_cast(high_resolution_clock::now() + .time_since_epoch()).count()); + + CHECK(num_received == num_sent); + CHECK(num_received > 0); +} + diff --git a/simulation/libsimulator/test/catch.hpp b/simulation/libsimulator/test/catch.hpp new file mode 100644 index 0000000..6cc67e7 --- /dev/null +++ b/simulation/libsimulator/test/catch.hpp @@ -0,0 +1,14057 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +// Catch v3.6.0 +// Generated: 2024-05-05 20:53:27.071502 +// ---------------------------------------------------------- +// This file is an amalgamation of multiple different files. +// You probably shouldn't edit it directly. +// ---------------------------------------------------------- +#ifndef CATCH_AMALGAMATED_HPP_INCLUDED +#define CATCH_AMALGAMATED_HPP_INCLUDED + + +/** \file + * This is a convenience header for Catch2. It includes **all** of Catch2 headers. + * + * Generally the Catch2 users should use specific includes they need, + * but this header can be used instead for ease-of-experimentation, or + * just plain convenience, at the cost of (significantly) increased + * compilation times. + * + * When a new header is added to either the top level folder, or to the + * corresponding internal subfolder, it should be added here. Headers + * added to the various subparts (e.g. matchers, generators, etc...), + * should go their respective catch-all headers. + */ + +#ifndef CATCH_ALL_HPP_INCLUDED +#define CATCH_ALL_HPP_INCLUDED + + + +/** \file + * This is a convenience header for Catch2's benchmarking. It includes + * **all** of Catch2 headers related to benchmarking. + * + * Generally the Catch2 users should use specific includes they need, + * but this header can be used instead for ease-of-experimentation, or + * just plain convenience, at the cost of (significantly) increased + * compilation times. + * + * When a new header is added to either the `benchmark` folder, or to + * the corresponding internal (detail) subfolder, it should be added here. + */ + +#ifndef CATCH_BENCHMARK_ALL_HPP_INCLUDED +#define CATCH_BENCHMARK_ALL_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_BENCHMARK_HPP_INCLUDED +#define CATCH_BENCHMARK_HPP_INCLUDED + + + +#ifndef CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED +#define CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + + + +#ifndef CATCH_PLATFORM_HPP_INCLUDED +#define CATCH_PLATFORM_HPP_INCLUDED + +// See e.g.: +// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html +#ifdef __APPLE__ +# ifndef __has_extension +# define __has_extension(x) 0 +# endif +# include +# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ + (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) +# define CATCH_PLATFORM_MAC +# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS + +# if defined( WINAPI_FAMILY ) && ( WINAPI_FAMILY == WINAPI_FAMILY_APP ) +# define CATCH_PLATFORM_WINDOWS_UWP +# endif + +#elif defined(__ORBIS__) || defined(__PROSPERO__) +# define CATCH_PLATFORM_PLAYSTATION + +#endif + +#endif // CATCH_PLATFORM_HPP_INCLUDED + +#ifdef __cplusplus + +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + +# if (__cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) +# define CATCH_CPP20_OR_GREATER +# endif + +#endif + +// Only GCC compiler should be used in this block, so other compilers trying to +// mask themselves as GCC should be ignored. +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) && !defined(__NVCOMPILER) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) + +// This only works on GCC 9+. so we have to also add a global suppression of Wparentheses +// for older versions of GCC. +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "GCC diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \ + _Pragma( "GCC diagnostic ignored \"-Wunused-result\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + _Pragma( "GCC diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + _Pragma( "GCC diagnostic ignored \"-Wuseless-cast\"" ) + +# define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ + _Pragma( "GCC diagnostic ignored \"-Wshadow\"" ) + +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) + +#endif + +#if defined(__NVCOMPILER) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "diag push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "diag pop" ) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS _Pragma( "diag_suppress declared_but_not_referenced" ) +#endif + +#if defined(__CUDACC__) && !defined(__clang__) +# ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +// New pragmas introduced in CUDA 11.5+ +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "nv_diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "nv_diagnostic pop" ) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS _Pragma( "nv_diag_suppress 177" ) +# else +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS _Pragma( "diag_suppress 177" ) +# endif +#endif + +// clang-cl defines _MSC_VER as well as __clang__, which could cause the +// start/stop internal suppression macros to be double defined. +#if defined(__clang__) && !defined(_MSC_VER) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) + +#endif // __clang__ && !_MSC_VER + +#if defined(__clang__) + +// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug +// which results in calls to destructors being emitted for each temporary, +// without a matching initialization. In practice, this can result in something +// like `std::string::~string` being called on an uninitialized value. +// +// For example, this code will likely segfault under IBM XL: +// ``` +// REQUIRE(std::string("12") + "34" == "1234") +// ``` +// +// Similarly, NVHPC's implementation of `__builtin_constant_p` has a bug which +// results in calls to the immediately evaluated lambda expressions to be +// reported as unevaluated lambdas. +// https://developer.nvidia.com/nvidia_bug/3321845. +// +// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. +# if !defined(__ibmxl__) && !defined(__CUDACC__) && !defined( __NVCOMPILER ) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ +# endif + + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) + +# define CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wcomma\"" ) + +# define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wshadow\"" ) + +#endif // __clang__ + + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined( CATCH_PLATFORM_WINDOWS ) || \ + defined( CATCH_PLATFORM_PLAYSTATION ) || \ + defined( __CYGWIN__ ) || \ + defined( __QNX__ ) || \ + defined( __EMSCRIPTEN__ ) || \ + defined( __DJGPP__ ) || \ + defined( __OS400__ ) +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#else +# define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Assume that some platforms do not support getenv. +#if defined( CATCH_PLATFORM_WINDOWS_UWP ) || \ + defined( CATCH_PLATFORM_PLAYSTATION ) || \ + defined( _GAMING_XBOX ) +# define CATCH_INTERNAL_CONFIG_NO_GETENV +#else +# define CATCH_INTERNAL_CONFIG_GETENV +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#if defined(_MSC_VER) + +// We want to defer to nvcc-specific warning suppression if we are compiled +// with nvcc masquerading for MSVC. +# if !defined( __CUDACC__ ) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + __pragma( warning( push ) ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + __pragma( warning( pop ) ) +# endif + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(CATCH_PLATFORM_WINDOWS_UWP) +# define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +# if !defined(__clang__) // Handle Clang masquerading for msvc +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif // MSVC_TRADITIONAL +# endif // __clang__ + +#endif // _MSC_VER + +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +# define CATCH_INTERNAL_CONFIG_USE_ASYNC +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) + #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH + #define CATCH_INTERNAL_CONFIG_NO_ASYNC + #define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 +#endif + +#if !defined(_GLIBCXX_USE_C99_MATH_TR1) +#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Various stdlib support checks that require __has_include +#if defined(__has_include) + // Check if string_view is available and usable + #if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW + #endif + + // Check if optional is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if byte is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # include + # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) + # define CATCH_INTERNAL_CONFIG_CPP17_BYTE + # endif + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if variant is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 + # include + # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # define CATCH_CONFIG_NO_CPP17_VARIANT + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__clang__) && (__clang_major__ < 8) + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // defined(__has_include) + + +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GETENV) && !defined(CATCH_INTERNAL_CONFIG_NO_GETENV) && !defined(CATCH_CONFIG_NO_GETENV) && !defined(CATCH_CONFIG_GETENV) +# define CATCH_CONFIG_GETENV +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +# define CATCH_CONFIG_CPP17_BYTE +#endif + + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined( CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED ) && \ + !defined( CATCH_CONFIG_DISABLE_EXCEPTIONS ) && \ + !defined( CATCH_CONFIG_NO_DISABLE_EXCEPTIONS ) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +# define CATCH_CONFIG_USE_ASYNC +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) +# define CATCH_CONFIG_GLOBAL_NEXTAFTER +#endif + + +// Even if we do not think the compiler has that warning, we still have +// to provide a macro that can be used by the code. +#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +#endif +#if !defined( CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS ) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif +#if !defined( CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS ) +# define CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS +#endif +#if !defined( CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS ) +# define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS +#endif + + +// The goal of this macro is to avoid evaluation of the arguments, but +// still have the compiler warn on problems inside... +#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) +#endif + +#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#elif defined(__clang__) && (__clang_major__ < 5) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif + +#if defined( CATCH_PLATFORM_WINDOWS ) && \ + !defined( CATCH_CONFIG_COLOUR_WIN32 ) && \ + !defined( CATCH_CONFIG_NO_COLOUR_WIN32 ) && \ + !defined( CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 ) +# define CATCH_CONFIG_COLOUR_WIN32 +#endif + +#if defined( CATCH_CONFIG_SHARED_LIBRARY ) && defined( _MSC_VER ) && \ + !defined( CATCH_CONFIG_STATIC ) +# ifdef Catch2_EXPORTS +# define CATCH_EXPORT //__declspec( dllexport ) // not needed +# else +# define CATCH_EXPORT __declspec( dllimport ) +# endif +#else +# define CATCH_EXPORT +#endif + +#endif // CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED + + +#ifndef CATCH_CONTEXT_HPP_INCLUDED +#define CATCH_CONTEXT_HPP_INCLUDED + + +namespace Catch { + + class IResultCapture; + class IConfig; + + class Context { + IConfig const* m_config = nullptr; + IResultCapture* m_resultCapture = nullptr; + + CATCH_EXPORT static Context* currentContext; + friend Context& getCurrentMutableContext(); + friend Context const& getCurrentContext(); + static void createContext(); + friend void cleanUpContext(); + + public: + IResultCapture* getResultCapture() const { return m_resultCapture; } + IConfig const* getConfig() const { return m_config; } + void setResultCapture( IResultCapture* resultCapture ); + void setConfig( IConfig const* config ); + }; + + Context& getCurrentMutableContext(); + + inline Context const& getCurrentContext() { + // We duplicate the logic from `getCurrentMutableContext` here, + // to avoid paying the call overhead in debug mode. + if ( !Context::currentContext ) { Context::createContext(); } + // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) + return *Context::currentContext; + } + + void cleanUpContext(); + + class SimplePcg32; + SimplePcg32& sharedRng(); +} + +#endif // CATCH_CONTEXT_HPP_INCLUDED + + +#ifndef CATCH_MOVE_AND_FORWARD_HPP_INCLUDED +#define CATCH_MOVE_AND_FORWARD_HPP_INCLUDED + +#include + +//! Replacement for std::move with better compile time performance +#define CATCH_MOVE(...) static_cast&&>(__VA_ARGS__) + +//! Replacement for std::forward with better compile time performance +#define CATCH_FORWARD(...) static_cast(__VA_ARGS__) + +#endif // CATCH_MOVE_AND_FORWARD_HPP_INCLUDED + + +#ifndef CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED +#define CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED + +namespace Catch { + + //! Used to signal that an assertion macro failed + struct TestFailureException{}; + //! Used to signal that the remainder of a test should be skipped + struct TestSkipException {}; + + /** + * Outlines throwing of `TestFailureException` into a single TU + * + * Also handles `CATCH_CONFIG_DISABLE_EXCEPTIONS` for callers. + */ + [[noreturn]] void throw_test_failure_exception(); + + /** + * Outlines throwing of `TestSkipException` into a single TU + * + * Also handles `CATCH_CONFIG_DISABLE_EXCEPTIONS` for callers. + */ + [[noreturn]] void throw_test_skip_exception(); + +} // namespace Catch + +#endif // CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED + + +#ifndef CATCH_UNIQUE_NAME_HPP_INCLUDED +#define CATCH_UNIQUE_NAME_HPP_INCLUDED + + + + +/** \file + * Wrapper for the CONFIG configuration option + * + * When generating internal unique names, there are two options. Either + * we mix in the current line number, or mix in an incrementing number. + * We prefer the latter, using `__COUNTER__`, but users might want to + * use the former. + */ + +#ifndef CATCH_CONFIG_COUNTER_HPP_INCLUDED +#define CATCH_CONFIG_COUNTER_HPP_INCLUDED + + +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +#if defined( CATCH_INTERNAL_CONFIG_COUNTER ) && \ + !defined( CATCH_CONFIG_NO_COUNTER ) && \ + !defined( CATCH_CONFIG_COUNTER ) +# define CATCH_CONFIG_COUNTER +#endif + + +#endif // CATCH_CONFIG_COUNTER_HPP_INCLUDED +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#endif // CATCH_UNIQUE_NAME_HPP_INCLUDED + + +#ifndef CATCH_INTERFACES_CAPTURE_HPP_INCLUDED +#define CATCH_INTERFACES_CAPTURE_HPP_INCLUDED + +#include +#include + + + +#ifndef CATCH_STRINGREF_HPP_INCLUDED +#define CATCH_STRINGREF_HPP_INCLUDED + +#include +#include +#include +#include + +#include + +namespace Catch { + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. + class StringRef { + public: + using size_type = std::size_t; + using const_iterator = const char*; + + static constexpr size_type npos{ static_cast( -1 ) }; + + private: + static constexpr char const* const s_empty = ""; + + char const* m_start = s_empty; + size_type m_size = 0; + + public: // construction + constexpr StringRef() noexcept = default; + + StringRef( char const* rawChars ) noexcept; + + constexpr StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + explicit operator std::string() const { + return std::string(m_start, m_size); + } + + public: // operators + auto operator == ( StringRef other ) const noexcept -> bool { + return m_size == other.m_size + && (std::memcmp( m_start, other.m_start, m_size ) == 0); + } + auto operator != (StringRef other) const noexcept -> bool { + return !(*this == other); + } + + constexpr auto operator[] ( size_type index ) const noexcept -> char { + assert(index < m_size); + return m_start[index]; + } + + bool operator<(StringRef rhs) const noexcept; + + public: // named queries + constexpr auto empty() const noexcept -> bool { + return m_size == 0; + } + constexpr auto size() const noexcept -> size_type { + return m_size; + } + + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, size()). + // If start > size(), then the substring is empty. + constexpr StringRef substr(size_type start, size_type length) const noexcept { + if (start < m_size) { + const auto shortened_size = m_size - start; + return StringRef(m_start + start, (shortened_size < length) ? shortened_size : length); + } else { + return StringRef(); + } + } + + // Returns the current start pointer. May not be null-terminated. + constexpr char const* data() const noexcept { + return m_start; + } + + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } + + + friend std::string& operator += (std::string& lhs, StringRef rhs); + friend std::ostream& operator << (std::ostream& os, StringRef str); + friend std::string operator+(StringRef lhs, StringRef rhs); + + /** + * Provides a three-way comparison with rhs + * + * Returns negative number if lhs < rhs, 0 if lhs == rhs, and a positive + * number if lhs > rhs + */ + int compare( StringRef rhs ) const; + }; + + + constexpr auto operator ""_sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } +} // namespace Catch + +constexpr auto operator ""_catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +#endif // CATCH_STRINGREF_HPP_INCLUDED + + +#ifndef CATCH_RESULT_TYPE_HPP_INCLUDED +#define CATCH_RESULT_TYPE_HPP_INCLUDED + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + // TODO: Should explicit skip be considered "not OK" (cf. isOk)? I.e., should it have the failure bit? + ExplicitSkip = 4, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + bool isOk( ResultWas::OfType resultType ); + bool isJustInfo( int flags ); + + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x01, + + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test + }; }; + + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ); + + bool shouldContinueOnFailure( int flags ); + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + bool shouldSuppressFailure( int flags ); + +} // end namespace Catch + +#endif // CATCH_RESULT_TYPE_HPP_INCLUDED + + +#ifndef CATCH_UNIQUE_PTR_HPP_INCLUDED +#define CATCH_UNIQUE_PTR_HPP_INCLUDED + +#include +#include + + +namespace Catch { +namespace Detail { + /** + * A reimplementation of `std::unique_ptr` for improved compilation performance + * + * Does not support arrays nor custom deleters. + */ + template + class unique_ptr { + T* m_ptr; + public: + constexpr unique_ptr(std::nullptr_t = nullptr): + m_ptr{} + {} + explicit constexpr unique_ptr(T* ptr): + m_ptr(ptr) + {} + + template ::value>> + unique_ptr(unique_ptr&& from): + m_ptr(from.release()) + {} + + template ::value>> + unique_ptr& operator=(unique_ptr&& from) { + reset(from.release()); + + return *this; + } + + unique_ptr(unique_ptr const&) = delete; + unique_ptr& operator=(unique_ptr const&) = delete; + + unique_ptr(unique_ptr&& rhs) noexcept: + m_ptr(rhs.m_ptr) { + rhs.m_ptr = nullptr; + } + unique_ptr& operator=(unique_ptr&& rhs) noexcept { + reset(rhs.release()); + + return *this; + } + + ~unique_ptr() { + delete m_ptr; + } + + T& operator*() { + assert(m_ptr); + return *m_ptr; + } + T const& operator*() const { + assert(m_ptr); + return *m_ptr; + } + T* operator->() noexcept { + assert(m_ptr); + return m_ptr; + } + T const* operator->() const noexcept { + assert(m_ptr); + return m_ptr; + } + + T* get() { return m_ptr; } + T const* get() const { return m_ptr; } + + void reset(T* ptr = nullptr) { + delete m_ptr; + m_ptr = ptr; + } + + T* release() { + auto temp = m_ptr; + m_ptr = nullptr; + return temp; + } + + explicit operator bool() const { + return m_ptr; + } + + friend void swap(unique_ptr& lhs, unique_ptr& rhs) { + auto temp = lhs.m_ptr; + lhs.m_ptr = rhs.m_ptr; + rhs.m_ptr = temp; + } + }; + + //! Specialization to cause compile-time error for arrays + template + class unique_ptr; + + template + unique_ptr make_unique(Args&&... args) { + return unique_ptr(new T(CATCH_FORWARD(args)...)); + } + + +} // end namespace Detail +} // end namespace Catch + +#endif // CATCH_UNIQUE_PTR_HPP_INCLUDED + + +#ifndef CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED +#define CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_CLOCK_HPP_INCLUDED +#define CATCH_CLOCK_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + using IDuration = std::chrono::nanoseconds; + using FDuration = std::chrono::duration; + + template + using TimePoint = typename Clock::time_point; + + using default_clock = std::chrono::steady_clock; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_CLOCK_HPP_INCLUDED + +namespace Catch { + + // We cannot forward declare the type with default template argument + // multiple times, so it is split out into a separate header so that + // we can prevent multiple declarations in dependees + template + struct BenchmarkStats; + +} // end namespace Catch + +#endif // CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED + +namespace Catch { + + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct SectionEndInfo; + struct MessageInfo; + struct MessageBuilder; + struct Counts; + struct AssertionReaction; + struct SourceLineInfo; + + class ITransientExpression; + class IGeneratorTracker; + + struct BenchmarkInfo; + + namespace Generators { + class GeneratorUntypedBase; + using GeneratorBasePtr = Catch::Detail::unique_ptr; + } + + + class IResultCapture { + public: + virtual ~IResultCapture(); + + virtual void notifyAssertionStarted( AssertionInfo const& info ) = 0; + virtual bool sectionStarted( StringRef sectionName, + SourceLineInfo const& sectionLineInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionEndInfo&& endInfo ) = 0; + virtual void sectionEndedEarly( SectionEndInfo&& endInfo ) = 0; + + virtual IGeneratorTracker* + acquireGeneratorTracker( StringRef generatorName, + SourceLineInfo const& lineInfo ) = 0; + virtual IGeneratorTracker* + createGeneratorTracker( StringRef generatorName, + SourceLineInfo lineInfo, + Generators::GeneratorBasePtr&& generator ) = 0; + + virtual void benchmarkPreparing( StringRef name ) = 0; + virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; + virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0; + virtual void benchmarkFailed( StringRef error ) = 0; + + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual void emplaceUnscopedMessage( MessageBuilder&& builder ) = 0; + + virtual void handleFatalErrorCondition( StringRef message ) = 0; + + virtual void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) = 0; + virtual void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef message, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string&& message, + AssertionReaction& reaction ) = 0; + virtual void handleIncomplete + ( AssertionInfo const& info ) = 0; + virtual void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) = 0; + + + + virtual bool lastAssertionPassed() = 0; + virtual void assertionPassed() = 0; + + // Deprecated, do not use: + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + virtual void exceptionEarlyReported() = 0; + }; + + IResultCapture& getResultCapture(); +} + +#endif // CATCH_INTERFACES_CAPTURE_HPP_INCLUDED + + +#ifndef CATCH_INTERFACES_CONFIG_HPP_INCLUDED +#define CATCH_INTERFACES_CONFIG_HPP_INCLUDED + + + +#ifndef CATCH_NONCOPYABLE_HPP_INCLUDED +#define CATCH_NONCOPYABLE_HPP_INCLUDED + +namespace Catch { + namespace Detail { + + //! Deriving classes become noncopyable and nonmovable + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable&& ) = delete; + NonCopyable& operator=( NonCopyable const& ) = delete; + NonCopyable& operator=( NonCopyable&& ) = delete; + + protected: + NonCopyable() noexcept = default; + }; + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_NONCOPYABLE_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + + enum class Verbosity { + Quiet = 0, + Normal, + High + }; + + struct WarnAbout { enum What { + Nothing = 0x00, + //! A test case or leaf section did not run any assertions + NoAssertions = 0x01, + //! A command line test spec matched no test cases + UnmatchedTestSpec = 0x02, + }; }; + + enum class ShowDurations { + DefaultForReporter, + Always, + Never + }; + enum class TestRunOrder { + Declared, + LexicographicallySorted, + Randomized + }; + enum class ColourMode : std::uint8_t { + //! Let Catch2 pick implementation based on platform detection + PlatformDefault, + //! Use ANSI colour code escapes + ANSI, + //! Use Win32 console colour API + Win32, + //! Don't use any colour + None + }; + struct WaitForKeypress { enum When { + Never, + BeforeStart = 1, + BeforeExit = 2, + BeforeStartAndExit = BeforeStart | BeforeExit + }; }; + + class TestSpec; + class IStream; + + class IConfig : public Detail::NonCopyable { + public: + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual StringRef name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual bool warnAboutUnmatchedTestSpecs() const = 0; + virtual bool zeroTestsCountAsSuccess() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations showDurations() const = 0; + virtual double minDuration() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual bool hasTestFilters() const = 0; + virtual std::vector const& getTestsOrTags() const = 0; + virtual TestRunOrder runOrder() const = 0; + virtual uint32_t rngSeed() const = 0; + virtual unsigned int shardCount() const = 0; + virtual unsigned int shardIndex() const = 0; + virtual ColourMode defaultColourMode() const = 0; + virtual std::vector const& getSectionsToRun() const = 0; + virtual Verbosity verbosity() const = 0; + + virtual bool skipBenchmarks() const = 0; + virtual bool benchmarkNoAnalysis() const = 0; + virtual unsigned int benchmarkSamples() const = 0; + virtual double benchmarkConfidenceInterval() const = 0; + virtual unsigned int benchmarkResamples() const = 0; + virtual std::chrono::milliseconds benchmarkWarmupTime() const = 0; + }; +} + +#endif // CATCH_INTERFACES_CONFIG_HPP_INCLUDED + + +#ifndef CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED +#define CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED + + +#include + +namespace Catch { + + class TestCaseHandle; + struct TestCaseInfo; + class ITestCaseRegistry; + class IExceptionTranslatorRegistry; + class IExceptionTranslator; + class ReporterRegistry; + class IReporterFactory; + class ITagAliasRegistry; + class ITestInvoker; + class IMutableEnumValuesRegistry; + struct SourceLineInfo; + + class StartupExceptionRegistry; + class EventListenerFactory; + + using IReporterFactoryPtr = Detail::unique_ptr; + + class IRegistryHub { + public: + virtual ~IRegistryHub(); // = default + + virtual ReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; + virtual IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const = 0; + + + virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; + }; + + class IMutableRegistryHub { + public: + virtual ~IMutableRegistryHub(); // = default + virtual void registerReporter( std::string const& name, IReporterFactoryPtr factory ) = 0; + virtual void registerListener( Detail::unique_ptr factory ) = 0; + virtual void registerTest(Detail::unique_ptr&& testInfo, Detail::unique_ptr&& invoker) = 0; + virtual void registerTranslator( Detail::unique_ptr&& translator ) = 0; + virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; + virtual void registerStartupException() noexcept = 0; + virtual IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() = 0; + }; + + IRegistryHub const& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + +#endif // CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED + + +#ifndef CATCH_BENCHMARK_STATS_HPP_INCLUDED +#define CATCH_BENCHMARK_STATS_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_ESTIMATE_HPP_INCLUDED +#define CATCH_ESTIMATE_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + template + struct Estimate { + Type point; + Type lower_bound; + Type upper_bound; + double confidence_interval; + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_ESTIMATE_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_OUTLIER_CLASSIFICATION_HPP_INCLUDED +#define CATCH_OUTLIER_CLASSIFICATION_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + struct OutlierClassification { + int samples_seen = 0; + int low_severe = 0; // more than 3 times IQR below Q1 + int low_mild = 0; // 1.5 to 3 times IQR below Q1 + int high_mild = 0; // 1.5 to 3 times IQR above Q3 + int high_severe = 0; // more than 3 times IQR above Q3 + + int total() const { + return low_severe + low_mild + high_mild + high_severe; + } + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_OUTLIERS_CLASSIFICATION_HPP_INCLUDED +// The fwd decl & default specialization needs to be seen by VS2017 before +// BenchmarkStats itself, or VS2017 will report compilation error. + +#include +#include + +namespace Catch { + + struct BenchmarkInfo { + std::string name; + double estimatedDuration; + int iterations; + unsigned int samples; + unsigned int resamples; + double clockResolution; + double clockCost; + }; + + // We need to keep template parameter for backwards compatibility, + // but we also do not want to use the template paraneter. + template + struct BenchmarkStats { + BenchmarkInfo info; + + std::vector samples; + Benchmark::Estimate mean; + Benchmark::Estimate standardDeviation; + Benchmark::OutlierClassification outliers; + double outlierVariance; + }; + + +} // end namespace Catch + +#endif // CATCH_BENCHMARK_STATS_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_ENVIRONMENT_HPP_INCLUDED +#define CATCH_ENVIRONMENT_HPP_INCLUDED + + +namespace Catch { + namespace Benchmark { + struct EnvironmentEstimate { + FDuration mean; + OutlierClassification outliers; + }; + struct Environment { + EnvironmentEstimate clock_resolution; + EnvironmentEstimate clock_cost; + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_ENVIRONMENT_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_EXECUTION_PLAN_HPP_INCLUDED +#define CATCH_EXECUTION_PLAN_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED +#define CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_CHRONOMETER_HPP_INCLUDED +#define CATCH_CHRONOMETER_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_OPTIMIZER_HPP_INCLUDED +#define CATCH_OPTIMIZER_HPP_INCLUDED + +#if defined(_MSC_VER) || defined(__IAR_SYSTEMS_ICC__) +# include // atomic_thread_fence +#endif + + +#include + +namespace Catch { + namespace Benchmark { +#if defined(__GNUC__) || defined(__clang__) + template + inline void keep_memory(T* p) { + asm volatile("" : : "g"(p) : "memory"); + } + inline void keep_memory() { + asm volatile("" : : : "memory"); + } + + namespace Detail { + inline void optimizer_barrier() { keep_memory(); } + } // namespace Detail +#elif defined(_MSC_VER) || defined(__IAR_SYSTEMS_ICC__) + +#if defined(_MSVC_VER) +#pragma optimize("", off) +#elif defined(__IAR_SYSTEMS_ICC__) +// For IAR the pragma only affects the following function +#pragma optimize=disable +#endif + template + inline void keep_memory(T* p) { + // thanks @milleniumbug + *reinterpret_cast(p) = *reinterpret_cast(p); + } + // TODO equivalent keep_memory() +#if defined(_MSVC_VER) +#pragma optimize("", on) +#endif + + namespace Detail { + inline void optimizer_barrier() { + std::atomic_thread_fence(std::memory_order_seq_cst); + } + } // namespace Detail + +#endif + + template + inline void deoptimize_value(T&& x) { + keep_memory(&x); + } + + template + inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> std::enable_if_t::value> { + deoptimize_value(CATCH_FORWARD(fn) (CATCH_FORWARD(args)...)); + } + + template + inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> std::enable_if_t::value> { + CATCH_FORWARD((fn)) (CATCH_FORWARD(args)...); + } + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_OPTIMIZER_HPP_INCLUDED + + +#ifndef CATCH_META_HPP_INCLUDED +#define CATCH_META_HPP_INCLUDED + +#include + +namespace Catch { + template + struct true_given : std::true_type {}; + + struct is_callable_tester { + template + static true_given()(std::declval()...))> test(int); + template + static std::false_type test(...); + }; + + template + struct is_callable; + + template + struct is_callable : decltype(is_callable_tester::test(0)) {}; + + +#if defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable >= 201703 + // std::result_of is deprecated in C++17 and removed in C++20. Hence, it is + // replaced with std::invoke_result here. + template + using FunctionReturnType = std::remove_reference_t>>; +#else + template + using FunctionReturnType = std::remove_reference_t>>; +#endif + +} // namespace Catch + +namespace mpl_{ + struct na; +} + +#endif // CATCH_META_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + namespace Detail { + struct ChronometerConcept { + virtual void start() = 0; + virtual void finish() = 0; + virtual ~ChronometerConcept(); // = default; + + ChronometerConcept() = default; + ChronometerConcept(ChronometerConcept const&) = default; + ChronometerConcept& operator=(ChronometerConcept const&) = default; + }; + template + struct ChronometerModel final : public ChronometerConcept { + void start() override { started = Clock::now(); } + void finish() override { finished = Clock::now(); } + + IDuration elapsed() const { + return std::chrono::duration_cast( + finished - started ); + } + + TimePoint started; + TimePoint finished; + }; + } // namespace Detail + + struct Chronometer { + public: + template + void measure(Fun&& fun) { measure(CATCH_FORWARD(fun), is_callable()); } + + int runs() const { return repeats; } + + Chronometer(Detail::ChronometerConcept& meter, int repeats_) + : impl(&meter) + , repeats(repeats_) {} + + private: + template + void measure(Fun&& fun, std::false_type) { + measure([&fun](int) { return fun(); }, std::true_type()); + } + + template + void measure(Fun&& fun, std::true_type) { + Detail::optimizer_barrier(); + impl->start(); + for (int i = 0; i < repeats; ++i) invoke_deoptimized(fun, i); + impl->finish(); + Detail::optimizer_barrier(); + } + + Detail::ChronometerConcept* impl; + int repeats; + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_CHRONOMETER_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + struct is_related + : std::is_same, std::decay_t> {}; + + /// We need to reinvent std::function because every piece of code that might add overhead + /// in a measurement context needs to have consistent performance characteristics so that we + /// can account for it in the measurement. + /// Implementations of std::function with optimizations that aren't always applicable, like + /// small buffer optimizations, are not uncommon. + /// This is effectively an implementation of std::function without any such optimizations; + /// it may be slow, but it is consistently slow. + struct BenchmarkFunction { + private: + struct callable { + virtual void call(Chronometer meter) const = 0; + virtual Catch::Detail::unique_ptr clone() const = 0; + virtual ~callable(); // = default; + + callable() = default; + callable(callable const&) = default; + callable& operator=(callable const&) = default; + }; + template + struct model : public callable { + model(Fun&& fun_) : fun(CATCH_MOVE(fun_)) {} + model(Fun const& fun_) : fun(fun_) {} + + Catch::Detail::unique_ptr clone() const override { + return Catch::Detail::make_unique>( *this ); + } + + void call(Chronometer meter) const override { + call(meter, is_callable()); + } + void call(Chronometer meter, std::true_type) const { + fun(meter); + } + void call(Chronometer meter, std::false_type) const { + meter.measure(fun); + } + + Fun fun; + }; + + struct do_nothing { void operator()() const {} }; + + template + BenchmarkFunction(model* c) : f(c) {} + + public: + BenchmarkFunction() + : f(new model{ {} }) {} + + template ::value, int> = 0> + BenchmarkFunction(Fun&& fun) + : f(new model>(CATCH_FORWARD(fun))) {} + + BenchmarkFunction( BenchmarkFunction&& that ) noexcept: + f( CATCH_MOVE( that.f ) ) {} + + BenchmarkFunction(BenchmarkFunction const& that) + : f(that.f->clone()) {} + + BenchmarkFunction& + operator=( BenchmarkFunction&& that ) noexcept { + f = CATCH_MOVE( that.f ); + return *this; + } + + BenchmarkFunction& operator=(BenchmarkFunction const& that) { + f = that.f->clone(); + return *this; + } + + void operator()(Chronometer meter) const { f->call(meter); } + + private: + Catch::Detail::unique_ptr f; + }; + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_REPEAT_HPP_INCLUDED +#define CATCH_REPEAT_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + struct repeater { + void operator()(int k) const { + for (int i = 0; i < k; ++i) { + fun(); + } + } + Fun fun; + }; + template + repeater> repeat(Fun&& fun) { + return { CATCH_FORWARD(fun) }; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_REPEAT_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED +#define CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_MEASURE_HPP_INCLUDED +#define CATCH_MEASURE_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_COMPLETE_INVOKE_HPP_INCLUDED +#define CATCH_COMPLETE_INVOKE_HPP_INCLUDED + + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + struct CompleteType { using type = T; }; + template <> + struct CompleteType { struct type {}; }; + + template + using CompleteType_t = typename CompleteType::type; + + template + struct CompleteInvoker { + template + static Result invoke(Fun&& fun, Args&&... args) { + return CATCH_FORWARD(fun)(CATCH_FORWARD(args)...); + } + }; + template <> + struct CompleteInvoker { + template + static CompleteType_t invoke(Fun&& fun, Args&&... args) { + CATCH_FORWARD(fun)(CATCH_FORWARD(args)...); + return {}; + } + }; + + // invoke and not return void :( + template + CompleteType_t> complete_invoke(Fun&& fun, Args&&... args) { + return CompleteInvoker>::invoke(CATCH_FORWARD(fun), CATCH_FORWARD(args)...); + } + + } // namespace Detail + + template + Detail::CompleteType_t> user_code(Fun&& fun) { + return Detail::complete_invoke(CATCH_FORWARD(fun)); + } + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_COMPLETE_INVOKE_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_TIMING_HPP_INCLUDED +#define CATCH_TIMING_HPP_INCLUDED + + +#include + +namespace Catch { + namespace Benchmark { + template + struct Timing { + IDuration elapsed; + Result result; + int iterations; + }; + template + using TimingOf = Timing>>; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_TIMING_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + TimingOf measure(Fun&& fun, Args&&... args) { + auto start = Clock::now(); + auto&& r = Detail::complete_invoke(fun, CATCH_FORWARD(args)...); + auto end = Clock::now(); + auto delta = end - start; + return { delta, CATCH_FORWARD(r), 1 }; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_MEASURE_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + TimingOf measure_one(Fun&& fun, int iters, std::false_type) { + return Detail::measure(fun, iters); + } + template + TimingOf measure_one(Fun&& fun, int iters, std::true_type) { + Detail::ChronometerModel meter; + auto&& result = Detail::complete_invoke(fun, Chronometer(meter, iters)); + + return { meter.elapsed(), CATCH_MOVE(result), iters }; + } + + template + using run_for_at_least_argument_t = std::conditional_t::value, Chronometer, int>; + + + [[noreturn]] + void throw_optimized_away_error(); + + template + TimingOf> + run_for_at_least(IDuration how_long, + const int initial_iterations, + Fun&& fun) { + auto iters = initial_iterations; + while (iters < (1 << 30)) { + auto&& Timing = measure_one(fun, iters, is_callable()); + + if (Timing.elapsed >= how_long) { + return { Timing.elapsed, CATCH_MOVE(Timing.result), iters }; + } + iters *= 2; + } + throw_optimized_away_error(); + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + struct ExecutionPlan { + int iterations_per_sample; + FDuration estimated_duration; + Detail::BenchmarkFunction benchmark; + FDuration warmup_time; + int warmup_iterations; + + template + std::vector run(const IConfig &cfg, Environment env) const { + // warmup a bit + Detail::run_for_at_least( + std::chrono::duration_cast( warmup_time ), + warmup_iterations, + Detail::repeat( []() { return Clock::now(); } ) + ); + + std::vector times; + const auto num_samples = cfg.benchmarkSamples(); + times.reserve( num_samples ); + for ( size_t i = 0; i < num_samples; ++i ) { + Detail::ChronometerModel model; + this->benchmark( Chronometer( model, iterations_per_sample ) ); + auto sample_time = model.elapsed() - env.clock_cost.mean; + if ( sample_time < FDuration::zero() ) { + sample_time = FDuration::zero(); + } + times.push_back(sample_time / iterations_per_sample); + } + return times; + } + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_EXECUTION_PLAN_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_ESTIMATE_CLOCK_HPP_INCLUDED +#define CATCH_ESTIMATE_CLOCK_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_STATS_HPP_INCLUDED +#define CATCH_STATS_HPP_INCLUDED + + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + using sample = std::vector; + + double weighted_average_quantile( int k, + int q, + double* first, + double* last ); + + OutlierClassification + classify_outliers( double const* first, double const* last ); + + double mean( double const* first, double const* last ); + + double normal_cdf( double x ); + + double erfc_inv(double x); + + double normal_quantile(double p); + + Estimate + bootstrap( double confidence_level, + double* first, + double* last, + sample const& resample, + double ( *estimator )( double const*, double const* ) ); + + struct bootstrap_analysis { + Estimate mean; + Estimate standard_deviation; + double outlier_variance; + }; + + bootstrap_analysis analyse_samples(double confidence_level, + unsigned int n_resamples, + double* first, + double* last); + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_STATS_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + std::vector resolution(int k) { + std::vector> times; + times.reserve(static_cast(k + 1)); + for ( int i = 0; i < k + 1; ++i ) { + times.push_back( Clock::now() ); + } + + std::vector deltas; + deltas.reserve(static_cast(k)); + for ( size_t idx = 1; idx < times.size(); ++idx ) { + deltas.push_back( static_cast( + ( times[idx] - times[idx - 1] ).count() ) ); + } + + return deltas; + } + + constexpr auto warmup_iterations = 10000; + constexpr auto warmup_time = std::chrono::milliseconds(100); + constexpr auto minimum_ticks = 1000; + constexpr auto warmup_seed = 10000; + constexpr auto clock_resolution_estimation_time = std::chrono::milliseconds(500); + constexpr auto clock_cost_estimation_time_limit = std::chrono::seconds(1); + constexpr auto clock_cost_estimation_tick_limit = 100000; + constexpr auto clock_cost_estimation_time = std::chrono::milliseconds(10); + constexpr auto clock_cost_estimation_iterations = 10000; + + template + int warmup() { + return run_for_at_least(warmup_time, warmup_seed, &resolution) + .iterations; + } + template + EnvironmentEstimate estimate_clock_resolution(int iterations) { + auto r = run_for_at_least(clock_resolution_estimation_time, iterations, &resolution) + .result; + return { + FDuration(mean(r.data(), r.data() + r.size())), + classify_outliers(r.data(), r.data() + r.size()), + }; + } + template + EnvironmentEstimate estimate_clock_cost(FDuration resolution) { + auto time_limit = (std::min)( + resolution * clock_cost_estimation_tick_limit, + FDuration(clock_cost_estimation_time_limit)); + auto time_clock = [](int k) { + return Detail::measure([k] { + for (int i = 0; i < k; ++i) { + volatile auto ignored = Clock::now(); + (void)ignored; + } + }).elapsed; + }; + time_clock(1); + int iters = clock_cost_estimation_iterations; + auto&& r = run_for_at_least(clock_cost_estimation_time, iters, time_clock); + std::vector times; + int nsamples = static_cast(std::ceil(time_limit / r.elapsed)); + times.reserve(static_cast(nsamples)); + for ( int s = 0; s < nsamples; ++s ) { + times.push_back( static_cast( + ( time_clock( r.iterations ) / r.iterations ) + .count() ) ); + } + return { + FDuration(mean(times.data(), times.data() + times.size())), + classify_outliers(times.data(), times.data() + times.size()), + }; + } + + template + Environment measure_environment() { +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + static Catch::Detail::unique_ptr env; +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + if (env) { + return *env; + } + + auto iters = Detail::warmup(); + auto resolution = Detail::estimate_clock_resolution(iters); + auto cost = Detail::estimate_clock_cost(resolution.mean); + + env = Catch::Detail::make_unique( Environment{resolution, cost} ); + return *env; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_ESTIMATE_CLOCK_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_ANALYSE_HPP_INCLUDED +#define CATCH_ANALYSE_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED +#define CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED + + +#include + +namespace Catch { + namespace Benchmark { + struct SampleAnalysis { + std::vector samples; + Estimate mean; + Estimate standard_deviation; + OutlierClassification outliers; + double outlier_variance; + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED + + +namespace Catch { + class IConfig; + + namespace Benchmark { + namespace Detail { + SampleAnalysis analyse(const IConfig &cfg, FDuration* first, FDuration* last); + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_ANALYSE_HPP_INCLUDED + +#include +#include +#include +#include +#include + +namespace Catch { + namespace Benchmark { + struct Benchmark { + Benchmark(std::string&& benchmarkName) + : name(CATCH_MOVE(benchmarkName)) {} + + template + Benchmark(std::string&& benchmarkName , FUN &&func) + : fun(CATCH_MOVE(func)), name(CATCH_MOVE(benchmarkName)) {} + + template + ExecutionPlan prepare(const IConfig &cfg, Environment env) const { + auto min_time = env.clock_resolution.mean * Detail::minimum_ticks; + auto run_time = std::max(min_time, std::chrono::duration_cast(cfg.benchmarkWarmupTime())); + auto&& test = Detail::run_for_at_least(std::chrono::duration_cast(run_time), 1, fun); + int new_iters = static_cast(std::ceil(min_time * test.iterations / test.elapsed)); + return { new_iters, test.elapsed / test.iterations * new_iters * cfg.benchmarkSamples(), fun, std::chrono::duration_cast(cfg.benchmarkWarmupTime()), Detail::warmup_iterations }; + } + + template + void run() { + static_assert( Clock::is_steady, + "Benchmarking clock should be steady" ); + auto const* cfg = getCurrentContext().getConfig(); + + auto env = Detail::measure_environment(); + + getResultCapture().benchmarkPreparing(name); + CATCH_TRY{ + auto plan = user_code([&] { + return prepare(*cfg, env); + }); + + BenchmarkInfo info { + CATCH_MOVE(name), + plan.estimated_duration.count(), + plan.iterations_per_sample, + cfg->benchmarkSamples(), + cfg->benchmarkResamples(), + env.clock_resolution.mean.count(), + env.clock_cost.mean.count() + }; + + getResultCapture().benchmarkStarting(info); + + auto samples = user_code([&] { + return plan.template run(*cfg, env); + }); + + auto analysis = Detail::analyse(*cfg, samples.data(), samples.data() + samples.size()); + BenchmarkStats<> stats{ CATCH_MOVE(info), CATCH_MOVE(analysis.samples), analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance }; + getResultCapture().benchmarkEnded(stats); + } CATCH_CATCH_ANON (TestFailureException const&) { + getResultCapture().benchmarkFailed("Benchmark failed due to failed assertion"_sr); + } CATCH_CATCH_ALL{ + getResultCapture().benchmarkFailed(translateActiveException()); + // We let the exception go further up so that the + // test case is marked as failed. + std::rethrow_exception(std::current_exception()); + } + } + + // sets lambda to be used in fun *and* executes benchmark! + template ::value, int> = 0> + Benchmark & operator=(Fun func) { + auto const* cfg = getCurrentContext().getConfig(); + if (!cfg->skipBenchmarks()) { + fun = Detail::BenchmarkFunction(func); + run(); + } + return *this; + } + + explicit operator bool() { + return true; + } + + private: + Detail::BenchmarkFunction fun; + std::string name; + }; + } +} // namespace Catch + +#define INTERNAL_CATCH_GET_1_ARG(arg1, arg2, ...) arg1 +#define INTERNAL_CATCH_GET_2_ARG(arg1, arg2, ...) arg2 + +#define INTERNAL_CATCH_BENCHMARK(BenchmarkName, name, benchmarkIndex)\ + if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \ + BenchmarkName = [&](int benchmarkIndex) + +#define INTERNAL_CATCH_BENCHMARK_ADVANCED(BenchmarkName, name)\ + if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \ + BenchmarkName = [&] + +#if defined(CATCH_CONFIG_PREFIX_ALL) + +#define CATCH_BENCHMARK(...) \ + INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) +#define CATCH_BENCHMARK_ADVANCED(name) \ + INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name) + +#else + +#define BENCHMARK(...) \ + INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) +#define BENCHMARK_ADVANCED(name) \ + INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name) + +#endif + +#endif // CATCH_BENCHMARK_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_CONSTRUCTOR_HPP_INCLUDED +#define CATCH_CONSTRUCTOR_HPP_INCLUDED + + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + struct ObjectStorage + { + ObjectStorage() = default; + + ObjectStorage(const ObjectStorage& other) + { + new(&data) T(other.stored_object()); + } + + ObjectStorage(ObjectStorage&& other) + { + new(data) T(CATCH_MOVE(other.stored_object())); + } + + ~ObjectStorage() { destruct_on_exit(); } + + template + void construct(Args&&... args) + { + new (data) T(CATCH_FORWARD(args)...); + } + + template + std::enable_if_t destruct() + { + stored_object().~T(); + } + + private: + // If this is a constructor benchmark, destruct the underlying object + template + void destruct_on_exit(std::enable_if_t* = nullptr) { destruct(); } + // Otherwise, don't + template + void destruct_on_exit(std::enable_if_t* = nullptr) { } + +#if defined( __GNUC__ ) && __GNUC__ <= 6 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif + T& stored_object() { return *reinterpret_cast( data ); } + + T const& stored_object() const { + return *reinterpret_cast( data ); + } +#if defined( __GNUC__ ) && __GNUC__ <= 6 +# pragma GCC diagnostic pop +#endif + + alignas( T ) unsigned char data[sizeof( T )]{}; + }; + } // namespace Detail + + template + using storage_for = Detail::ObjectStorage; + + template + using destructable_object = Detail::ObjectStorage; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_CONSTRUCTOR_HPP_INCLUDED + +#endif // CATCH_BENCHMARK_ALL_HPP_INCLUDED + + +#ifndef CATCH_APPROX_HPP_INCLUDED +#define CATCH_APPROX_HPP_INCLUDED + + + +#ifndef CATCH_TOSTRING_HPP_INCLUDED +#define CATCH_TOSTRING_HPP_INCLUDED + + +#include +#include +#include +#include + + + + +/** \file + * Wrapper for the WCHAR configuration option + * + * We want to support platforms that do not provide `wchar_t`, so we + * sometimes have to disable providing wchar_t overloads through Catch2, + * e.g. the StringMaker specialization for `std::wstring`. + */ + +#ifndef CATCH_CONFIG_WCHAR_HPP_INCLUDED +#define CATCH_CONFIG_WCHAR_HPP_INCLUDED + + +// We assume that WCHAR should be enabled by default, and only disabled +// for a shortlist (so far only DJGPP) of compilers. + +#if defined(__DJGPP__) +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +#if !defined( CATCH_INTERNAL_CONFIG_NO_WCHAR ) && \ + !defined( CATCH_CONFIG_NO_WCHAR ) && \ + !defined( CATCH_CONFIG_WCHAR ) +# define CATCH_CONFIG_WCHAR +#endif + +#endif // CATCH_CONFIG_WCHAR_HPP_INCLUDED + + +#ifndef CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED +#define CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED + + +#include +#include +#include +#include + +namespace Catch { + + class ReusableStringStream : Detail::NonCopyable { + std::size_t m_index; + std::ostream* m_oss; + public: + ReusableStringStream(); + ~ReusableStringStream(); + + //! Returns the serialized state + std::string str() const; + //! Sets internal state to `str` + void str(std::string const& str); + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +// Old versions of GCC do not understand -Wnonnull-compare +#pragma GCC diagnostic ignored "-Wpragmas" +// Streaming a function pointer triggers Waddress and Wnonnull-compare +// on GCC, because it implicitly converts it to bool and then decides +// that the check it uses (a? true : false) is tautological and cannot +// be null... +#pragma GCC diagnostic ignored "-Waddress" +#pragma GCC diagnostic ignored "-Wnonnull-compare" +#endif + + template + auto operator << ( T const& value ) -> ReusableStringStream& { + *m_oss << value; + return *this; + } + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + auto get() -> std::ostream& { return *m_oss; } + }; +} + +#endif // CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED + + +#ifndef CATCH_VOID_TYPE_HPP_INCLUDED +#define CATCH_VOID_TYPE_HPP_INCLUDED + + +namespace Catch { + namespace Detail { + + template + struct make_void { using type = void; }; + + template + using void_t = typename make_void::type; + + } // namespace Detail +} // namespace Catch + + +#endif // CATCH_VOID_TYPE_HPP_INCLUDED + + +#ifndef CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED +#define CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED + + +#include + +namespace Catch { + + namespace Detail { + struct EnumInfo { + StringRef m_name; + std::vector> m_values; + + ~EnumInfo(); + + StringRef lookup( int value ) const; + }; + } // namespace Detail + + class IMutableEnumValuesRegistry { + public: + virtual ~IMutableEnumValuesRegistry(); // = default; + + virtual Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector const& values ) = 0; + + template + Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::initializer_list values ) { + static_assert(sizeof(int) >= sizeof(E), "Cannot serialize enum to int"); + std::vector intValues; + intValues.reserve( values.size() ); + for( auto enumValue : values ) + intValues.push_back( static_cast( enumValue ) ); + return registerEnum( enumName, allEnums, intValues ); + } + }; + +} // Catch + +#endif // CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +#include +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless +#endif + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy{}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + // Bring in global namespace operator<< for ADL lookup in + // `IsStreamInsertable` below. + using ::operator<<; + + namespace Detail { + + inline std::size_t catch_strnlen(const char *str, std::size_t n) { + auto ret = std::char_traits::find(str, n, '\0'); + if (ret != nullptr) { + return static_cast(ret - str); + } + return n; + } + + constexpr StringRef unprintableString = "{?}"_sr; + + //! Encases `string in quotes, and optionally escapes invisibles + std::string convertIntoString( StringRef string, bool escapeInvisibles ); + + //! Encases `string` in quotes, and escapes invisibles if user requested + //! it via CLI + std::string convertIntoString( StringRef string ); + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template + std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + + template + class IsStreamInsertable { + template + static auto test(int) + -> decltype(std::declval() << std::declval(), std::true_type()); + + template + static auto test(...)->std::false_type; + + public: + static const bool value = decltype(test(0))::value; + }; + + template + std::string convertUnknownEnumToString( E e ); + + template + std::enable_if_t< + !std::is_enum::value && !std::is_base_of::value, + std::string> convertUnstreamable( T const& ) { + return std::string(Detail::unprintableString); + } + template + std::enable_if_t< + !std::is_enum::value && std::is_base_of::value, + std::string> convertUnstreamable(T const& ex) { + return ex.what(); + } + + + template + std::enable_if_t< + std::is_enum::value, + std::string> convertUnstreamable( T const& value ) { + return convertUnknownEnumToString( value ); + } + +#if defined(_MANAGED) + //! Convert a CLR string to a utf8 std::string + template + std::string clrReferenceToString( T^ ref ) { + if (ref == nullptr) + return std::string("null"); + auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString()); + cli::pin_ptr p = &bytes[0]; + return std::string(reinterpret_cast(p), bytes->Length); + } +#endif + + } // namespace Detail + + + template + struct StringMaker { + template + static + std::enable_if_t<::Catch::Detail::IsStreamInsertable::value, std::string> + convert(const Fake& value) { + ReusableStringStream rss; + // NB: call using the function-like syntax to avoid ambiguity with + // user-defined templated operator<< under clang. + rss.operator<<(value); + return rss.str(); + } + + template + static + std::enable_if_t::value, std::string> + convert( const Fake& value ) { +#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) + return Detail::convertUnstreamable(value); +#else + return CATCH_CONFIG_FALLBACK_STRINGIFIER(value); +#endif + } + }; + + namespace Detail { + + // This function dispatches all stringification requests inside of Catch. + // Should be preferably called fully qualified, like ::Catch::Detail::stringify + template + std::string stringify(const T& e) { + return ::Catch::StringMaker>>::convert(e); + } + + template + std::string convertUnknownEnumToString( E e ) { + return ::Catch::Detail::stringify(static_cast>(e)); + } + +#if defined(_MANAGED) + template + std::string stringify( T^ e ) { + return ::Catch::StringMaker::convert(e); + } +#endif + + } // namespace Detail + + // Some predefined specializations + + template<> + struct StringMaker { + static std::string convert(const std::string& str); + }; + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker { + static std::string convert(std::string_view str); + }; +#endif + + template<> + struct StringMaker { + static std::string convert(char const * str); + }; + template<> + struct StringMaker { + static std::string convert(char * str); + }; + +#if defined(CATCH_CONFIG_WCHAR) + template<> + struct StringMaker { + static std::string convert(const std::wstring& wstr); + }; + +# ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker { + static std::string convert(std::wstring_view str); + }; +# endif + + template<> + struct StringMaker { + static std::string convert(wchar_t const * str); + }; + template<> + struct StringMaker { + static std::string convert(wchar_t * str); + }; +#endif // CATCH_CONFIG_WCHAR + + template + struct StringMaker { + static std::string convert(char const* str) { + return Detail::convertIntoString( + StringRef( str, Detail::catch_strnlen( str, SZ ) ) ); + } + }; + template + struct StringMaker { + static std::string convert(signed char const* str) { + auto reinterpreted = reinterpret_cast(str); + return Detail::convertIntoString( + StringRef(reinterpreted, Detail::catch_strnlen(reinterpreted, SZ))); + } + }; + template + struct StringMaker { + static std::string convert(unsigned char const* str) { + auto reinterpreted = reinterpret_cast(str); + return Detail::convertIntoString( + StringRef(reinterpreted, Detail::catch_strnlen(reinterpreted, SZ))); + } + }; + +#if defined(CATCH_CONFIG_CPP17_BYTE) + template<> + struct StringMaker { + static std::string convert(std::byte value); + }; +#endif // defined(CATCH_CONFIG_CPP17_BYTE) + template<> + struct StringMaker { + static std::string convert(int value); + }; + template<> + struct StringMaker { + static std::string convert(long value); + }; + template<> + struct StringMaker { + static std::string convert(long long value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned int value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned long value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned long long value); + }; + + template<> + struct StringMaker { + static std::string convert(bool b) { + using namespace std::string_literals; + return b ? "true"s : "false"s; + } + }; + + template<> + struct StringMaker { + static std::string convert(char c); + }; + template<> + struct StringMaker { + static std::string convert(signed char value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned char value); + }; + + template<> + struct StringMaker { + static std::string convert(std::nullptr_t) { + using namespace std::string_literals; + return "nullptr"s; + } + }; + + template<> + struct StringMaker { + static std::string convert(float value); + CATCH_EXPORT static int precision; + }; + + template<> + struct StringMaker { + static std::string convert(double value); + CATCH_EXPORT static int precision; + }; + + template + struct StringMaker { + template + static std::string convert(U* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + + template + struct StringMaker { + static std::string convert(R C::* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + +#if defined(_MANAGED) + template + struct StringMaker { + static std::string convert( T^ ref ) { + return ::Catch::Detail::clrReferenceToString(ref); + } + }; +#endif + + namespace Detail { + template + std::string rangeToString(InputIterator first, Sentinel last) { + ReusableStringStream rss; + rss << "{ "; + if (first != last) { + rss << ::Catch::Detail::stringify(*first); + for (++first; first != last; ++first) + rss << ", " << ::Catch::Detail::stringify(*first); + } + rss << " }"; + return rss.str(); + } + } + +} // namespace Catch + +////////////////////////////////////////////////////// +// Separate std-lib types stringification, so it can be selectively enabled +// This means that we do not bring in their headers + +#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) +# define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER +# define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER +# define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER +# define CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER +#endif + +// Separate std::pair specialization +#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER) +#include +namespace Catch { + template + struct StringMaker > { + static std::string convert(const std::pair& pair) { + ReusableStringStream rss; + rss << "{ " + << ::Catch::Detail::stringify(pair.first) + << ", " + << ::Catch::Detail::stringify(pair.second) + << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER + +#if defined(CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_OPTIONAL) +#include +namespace Catch { + template + struct StringMaker > { + static std::string convert(const std::optional& optional) { + if (optional.has_value()) { + return ::Catch::Detail::stringify(*optional); + } else { + return "{ }"; + } + } + }; + template <> + struct StringMaker { + static std::string convert(const std::nullopt_t&) { + return "{ }"; + } + }; +} +#endif // CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER + +// Separate std::tuple specialization +#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) +#include +namespace Catch { + namespace Detail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size::value) + > + struct TupleElementPrinter { + static void print(const Tuple& tuple, std::ostream& os) { + os << (N ? ", " : " ") + << ::Catch::Detail::stringify(std::get(tuple)); + TupleElementPrinter::print(tuple, os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct TupleElementPrinter { + static void print(const Tuple&, std::ostream&) {} + }; + + } + + + template + struct StringMaker> { + static std::string convert(const std::tuple& tuple) { + ReusableStringStream rss; + rss << '{'; + Detail::TupleElementPrinter>::print(tuple, rss.get()); + rss << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER + +#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT) +#include +namespace Catch { + template<> + struct StringMaker { + static std::string convert(const std::monostate&) { + return "{ }"; + } + }; + + template + struct StringMaker> { + static std::string convert(const std::variant& variant) { + if (variant.valueless_by_exception()) { + return "{valueless variant}"; + } else { + return std::visit( + [](const auto& value) { + return ::Catch::Detail::stringify(value); + }, + variant + ); + } + } + }; +} +#endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER + +namespace Catch { + // Import begin/ end from std here + using std::begin; + using std::end; + + namespace Detail { + template + struct is_range_impl : std::false_type {}; + + template + struct is_range_impl()))>> : std::true_type {}; + } // namespace Detail + + template + struct is_range : Detail::is_range_impl {}; + +#if defined(_MANAGED) // Managed types are never ranges + template + struct is_range { + static const bool value = false; + }; +#endif + + template + std::string rangeToString( Range const& range ) { + return ::Catch::Detail::rangeToString( begin( range ), end( range ) ); + } + + // Handle vector specially + template + std::string rangeToString( std::vector const& v ) { + ReusableStringStream rss; + rss << "{ "; + bool first = true; + for( bool b : v ) { + if( first ) + first = false; + else + rss << ", "; + rss << ::Catch::Detail::stringify( b ); + } + rss << " }"; + return rss.str(); + } + + template + struct StringMaker::value && !::Catch::Detail::IsStreamInsertable::value>> { + static std::string convert( R const& range ) { + return rangeToString( range ); + } + }; + + template + struct StringMaker { + static std::string convert(T const(&arr)[SZ]) { + return rangeToString(arr); + } + }; + + +} // namespace Catch + +// Separate std::chrono::duration specialization +#include +#include +#include + + +namespace Catch { + +template +struct ratio_string { + static std::string symbol() { + Catch::ReusableStringStream rss; + rss << '[' << Ratio::num << '/' + << Ratio::den << ']'; + return rss.str(); + } +}; + +template <> +struct ratio_string { + static char symbol() { return 'a'; } +}; +template <> +struct ratio_string { + static char symbol() { return 'f'; } +}; +template <> +struct ratio_string { + static char symbol() { return 'p'; } +}; +template <> +struct ratio_string { + static char symbol() { return 'n'; } +}; +template <> +struct ratio_string { + static char symbol() { return 'u'; } +}; +template <> +struct ratio_string { + static char symbol() { return 'm'; } +}; + + //////////// + // std::chrono::duration specializations + template + struct StringMaker> { + static std::string convert(std::chrono::duration const& duration) { + ReusableStringStream rss; + rss << duration.count() << ' ' << ratio_string::symbol() << 's'; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " s"; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " m"; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " h"; + return rss.str(); + } + }; + + //////////// + // std::chrono::time_point specialization + // Generic time_point cannot be specialized, only std::chrono::time_point + template + struct StringMaker> { + static std::string convert(std::chrono::time_point const& time_point) { + return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch"; + } + }; + // std::chrono::time_point specialization + template + struct StringMaker> { + static std::string convert(std::chrono::time_point const& time_point) { + auto converted = std::chrono::system_clock::to_time_t(time_point); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &converted); +#else + std::tm* timeInfo = std::gmtime(&converted); +#endif + + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp, timeStampSize - 1); + } + }; +} + + +#define INTERNAL_CATCH_REGISTER_ENUM( enumName, ... ) \ +namespace Catch { \ + template<> struct StringMaker { \ + static std::string convert( enumName value ) { \ + static const auto& enumInfo = ::Catch::getMutableRegistryHub().getMutableEnumValuesRegistry().registerEnum( #enumName, #__VA_ARGS__, { __VA_ARGS__ } ); \ + return static_cast(enumInfo.lookup( static_cast( value ) )); \ + } \ + }; \ +} + +#define CATCH_REGISTER_ENUM( enumName, ... ) INTERNAL_CATCH_REGISTER_ENUM( enumName, __VA_ARGS__ ) + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // CATCH_TOSTRING_HPP_INCLUDED + +#include + +namespace Catch { + + class Approx { + private: + bool equalityComparisonImpl(double other) const; + // Sets and validates the new margin (margin >= 0) + void setMargin(double margin); + // Sets and validates the new epsilon (0 < epsilon < 1) + void setEpsilon(double epsilon); + + public: + explicit Approx ( double value ); + + static Approx custom(); + + Approx operator-() const; + + template ::value>> + Approx operator()( T const& value ) const { + Approx approx( static_cast(value) ); + approx.m_epsilon = m_epsilon; + approx.m_margin = m_margin; + approx.m_scale = m_scale; + return approx; + } + + template ::value>> + explicit Approx( T const& value ): Approx(static_cast(value)) + {} + + + template ::value>> + friend bool operator == ( const T& lhs, Approx const& rhs ) { + auto lhs_v = static_cast(lhs); + return rhs.equalityComparisonImpl(lhs_v); + } + + template ::value>> + friend bool operator == ( Approx const& lhs, const T& rhs ) { + return operator==( rhs, lhs ); + } + + template ::value>> + friend bool operator != ( T const& lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + template ::value>> + friend bool operator != ( Approx const& lhs, T const& rhs ) { + return !operator==( rhs, lhs ); + } + + template ::value>> + friend bool operator <= ( T const& lhs, Approx const& rhs ) { + return static_cast(lhs) < rhs.m_value || lhs == rhs; + } + + template ::value>> + friend bool operator <= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value < static_cast(rhs) || lhs == rhs; + } + + template ::value>> + friend bool operator >= ( T const& lhs, Approx const& rhs ) { + return static_cast(lhs) > rhs.m_value || lhs == rhs; + } + + template ::value>> + friend bool operator >= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value > static_cast(rhs) || lhs == rhs; + } + + template ::value>> + Approx& epsilon( T const& newEpsilon ) { + const auto epsilonAsDouble = static_cast(newEpsilon); + setEpsilon(epsilonAsDouble); + return *this; + } + + template ::value>> + Approx& margin( T const& newMargin ) { + const auto marginAsDouble = static_cast(newMargin); + setMargin(marginAsDouble); + return *this; + } + + template ::value>> + Approx& scale( T const& newScale ) { + m_scale = static_cast(newScale); + return *this; + } + + std::string toString() const; + + private: + double m_epsilon; + double m_margin; + double m_scale; + double m_value; + }; + +namespace literals { + Approx operator ""_a(long double val); + Approx operator ""_a(unsigned long long val); +} // end namespace literals + +template<> +struct StringMaker { + static std::string convert(Catch::Approx const& value); +}; + +} // end namespace Catch + +#endif // CATCH_APPROX_HPP_INCLUDED + + +#ifndef CATCH_ASSERTION_INFO_HPP_INCLUDED +#define CATCH_ASSERTION_INFO_HPP_INCLUDED + + + +#ifndef CATCH_SOURCE_LINE_INFO_HPP_INCLUDED +#define CATCH_SOURCE_LINE_INFO_HPP_INCLUDED + +#include +#include + +namespace Catch { + + struct SourceLineInfo { + + SourceLineInfo() = delete; + constexpr SourceLineInfo( char const* _file, std::size_t _line ) noexcept: + file( _file ), + line( _line ) + {} + + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + + friend std::ostream& operator << (std::ostream& os, SourceLineInfo const& info); + }; +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) + +#endif // CATCH_SOURCE_LINE_INFO_HPP_INCLUDED + +namespace Catch { + + struct AssertionInfo { + // AssertionInfo() = delete; + + StringRef macroName; + SourceLineInfo lineInfo; + StringRef capturedExpression; + ResultDisposition::Flags resultDisposition; + }; + +} // end namespace Catch + +#endif // CATCH_ASSERTION_INFO_HPP_INCLUDED + + +#ifndef CATCH_ASSERTION_RESULT_HPP_INCLUDED +#define CATCH_ASSERTION_RESULT_HPP_INCLUDED + + + +#ifndef CATCH_LAZY_EXPR_HPP_INCLUDED +#define CATCH_LAZY_EXPR_HPP_INCLUDED + +#include + +namespace Catch { + + class ITransientExpression; + + class LazyExpression { + friend class AssertionHandler; + friend struct AssertionStats; + friend class RunContext; + + ITransientExpression const* m_transientExpression = nullptr; + bool m_isNegated; + public: + LazyExpression( bool isNegated ): + m_isNegated(isNegated) + {} + LazyExpression(LazyExpression const& other) = default; + LazyExpression& operator = ( LazyExpression const& ) = delete; + + explicit operator bool() const { + return m_transientExpression != nullptr; + } + + friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&; + }; + +} // namespace Catch + +#endif // CATCH_LAZY_EXPR_HPP_INCLUDED + +#include + +namespace Catch { + + struct AssertionResultData + { + AssertionResultData() = delete; + + AssertionResultData( ResultWas::OfType _resultType, LazyExpression const& _lazyExpression ); + + std::string message; + mutable std::string reconstructedExpression; + LazyExpression lazyExpression; + ResultWas::OfType resultType; + + std::string reconstructExpression() const; + }; + + class AssertionResult { + public: + AssertionResult() = delete; + AssertionResult( AssertionInfo const& info, AssertionResultData&& data ); + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + StringRef getMessage() const; + SourceLineInfo getSourceInfo() const; + StringRef getTestMacroName() const; + + //protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +#endif // CATCH_ASSERTION_RESULT_HPP_INCLUDED + + +#ifndef CATCH_CONFIG_HPP_INCLUDED +#define CATCH_CONFIG_HPP_INCLUDED + + + +#ifndef CATCH_TEST_SPEC_HPP_INCLUDED +#define CATCH_TEST_SPEC_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + + + +#ifndef CATCH_WILDCARD_PATTERN_HPP_INCLUDED +#define CATCH_WILDCARD_PATTERN_HPP_INCLUDED + + + +#ifndef CATCH_CASE_SENSITIVE_HPP_INCLUDED +#define CATCH_CASE_SENSITIVE_HPP_INCLUDED + +namespace Catch { + + enum class CaseSensitive { Yes, No }; + +} // namespace Catch + +#endif // CATCH_CASE_SENSITIVE_HPP_INCLUDED + +#include + +namespace Catch +{ + class WildcardPattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + + WildcardPattern( std::string const& pattern, CaseSensitive caseSensitivity ); + bool matches( std::string const& str ) const; + + private: + std::string normaliseString( std::string const& str ) const; + CaseSensitive m_caseSensitivity; + WildcardPosition m_wildcard = NoWildcard; + std::string m_pattern; + }; +} + +#endif // CATCH_WILDCARD_PATTERN_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + class IConfig; + struct TestCaseInfo; + class TestCaseHandle; + + class TestSpec { + + class Pattern { + public: + explicit Pattern( std::string const& name ); + virtual ~Pattern(); + virtual bool matches( TestCaseInfo const& testCase ) const = 0; + std::string const& name() const; + private: + virtual void serializeTo( std::ostream& out ) const = 0; + // Writes string that would be reparsed into the pattern + friend std::ostream& operator<<(std::ostream& out, + Pattern const& pattern) { + pattern.serializeTo( out ); + return out; + } + + std::string const m_name; + }; + + class NamePattern : public Pattern { + public: + explicit NamePattern( std::string const& name, std::string const& filterString ); + bool matches( TestCaseInfo const& testCase ) const override; + private: + void serializeTo( std::ostream& out ) const override; + + WildcardPattern m_wildcardPattern; + }; + + class TagPattern : public Pattern { + public: + explicit TagPattern( std::string const& tag, std::string const& filterString ); + bool matches( TestCaseInfo const& testCase ) const override; + private: + void serializeTo( std::ostream& out ) const override; + + std::string m_tag; + }; + + struct Filter { + std::vector> m_required; + std::vector> m_forbidden; + + //! Serializes this filter into a string that would be parsed into + //! an equivalent filter + void serializeTo( std::ostream& out ) const; + friend std::ostream& operator<<(std::ostream& out, Filter const& f) { + f.serializeTo( out ); + return out; + } + + bool matches( TestCaseInfo const& testCase ) const; + }; + + static std::string extractFilterName( Filter const& filter ); + + public: + struct FilterMatch { + std::string name; + std::vector tests; + }; + using Matches = std::vector; + using vectorStrings = std::vector; + + bool hasFilters() const; + bool matches( TestCaseInfo const& testCase ) const; + Matches matchesByFilter( std::vector const& testCases, IConfig const& config ) const; + const vectorStrings & getInvalidSpecs() const; + + private: + std::vector m_filters; + std::vector m_invalidSpecs; + + friend class TestSpecParser; + //! Serializes this test spec into a string that would be parsed into + //! equivalent test spec + void serializeTo( std::ostream& out ) const; + friend std::ostream& operator<<(std::ostream& out, + TestSpec const& spec) { + spec.serializeTo( out ); + return out; + } + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif // CATCH_TEST_SPEC_HPP_INCLUDED + + +#ifndef CATCH_OPTIONAL_HPP_INCLUDED +#define CATCH_OPTIONAL_HPP_INCLUDED + + +#include + +namespace Catch { + + // An optional type + template + class Optional { + public: + Optional(): nullableValue( nullptr ) {} + ~Optional() { reset(); } + + Optional( T const& _value ): + nullableValue( new ( storage ) T( _value ) ) {} + Optional( T&& _value ): + nullableValue( new ( storage ) T( CATCH_MOVE( _value ) ) ) {} + + Optional& operator=( T const& _value ) { + reset(); + nullableValue = new ( storage ) T( _value ); + return *this; + } + Optional& operator=( T&& _value ) { + reset(); + nullableValue = new ( storage ) T( CATCH_MOVE( _value ) ); + return *this; + } + + Optional( Optional const& _other ): + nullableValue( _other ? new ( storage ) T( *_other ) : nullptr ) {} + Optional( Optional&& _other ): + nullableValue( _other ? new ( storage ) T( CATCH_MOVE( *_other ) ) + : nullptr ) {} + + Optional& operator=( Optional const& _other ) { + if ( &_other != this ) { + reset(); + if ( _other ) { nullableValue = new ( storage ) T( *_other ); } + } + return *this; + } + Optional& operator=( Optional&& _other ) { + if ( &_other != this ) { + reset(); + if ( _other ) { + nullableValue = new ( storage ) T( CATCH_MOVE( *_other ) ); + } + } + return *this; + } + + void reset() { + if ( nullableValue ) { nullableValue->~T(); } + nullableValue = nullptr; + } + + T& operator*() { + assert(nullableValue); + return *nullableValue; + } + T const& operator*() const { + assert(nullableValue); + return *nullableValue; + } + T* operator->() { + assert(nullableValue); + return nullableValue; + } + const T* operator->() const { + assert(nullableValue); + return nullableValue; + } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != nullptr; } + bool none() const { return nullableValue == nullptr; } + + bool operator !() const { return nullableValue == nullptr; } + explicit operator bool() const { + return some(); + } + + friend bool operator==(Optional const& a, Optional const& b) { + if (a.none() && b.none()) { + return true; + } else if (a.some() && b.some()) { + return *a == *b; + } else { + return false; + } + } + friend bool operator!=(Optional const& a, Optional const& b) { + return !( a == b ); + } + + private: + T* nullableValue; + alignas(alignof(T)) char storage[sizeof(T)]; + }; + +} // end namespace Catch + +#endif // CATCH_OPTIONAL_HPP_INCLUDED + + +#ifndef CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED +#define CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED + +#include + +namespace Catch { + + enum class GenerateFrom { + Time, + RandomDevice, + //! Currently equivalent to RandomDevice, but can change at any point + Default + }; + + std::uint32_t generateRandomSeed(GenerateFrom from); + +} // end namespace Catch + +#endif // CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED + + +#ifndef CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED +#define CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED + + +#include +#include +#include + +namespace Catch { + + enum class ColourMode : std::uint8_t; + + namespace Detail { + //! Splits the reporter spec into reporter name and kv-pair options + std::vector splitReporterSpec( StringRef reporterSpec ); + + Optional stringToColourMode( StringRef colourMode ); + } + + /** + * Structured reporter spec that a reporter can be created from + * + * Parsing has been validated, but semantics have not. This means e.g. + * that the colour mode is known to Catch2, but it might not be + * compiled into the binary, and the output filename might not be + * openable. + */ + class ReporterSpec { + std::string m_name; + Optional m_outputFileName; + Optional m_colourMode; + std::map m_customOptions; + + friend bool operator==( ReporterSpec const& lhs, + ReporterSpec const& rhs ); + friend bool operator!=( ReporterSpec const& lhs, + ReporterSpec const& rhs ) { + return !( lhs == rhs ); + } + + public: + ReporterSpec( + std::string name, + Optional outputFileName, + Optional colourMode, + std::map customOptions ); + + std::string const& name() const { return m_name; } + + Optional const& outputFile() const { + return m_outputFileName; + } + + Optional const& colourMode() const { return m_colourMode; } + + std::map const& customOptions() const { + return m_customOptions; + } + }; + + /** + * Parses provided reporter spec string into + * + * Returns empty optional on errors, e.g. + * * field that is not first and not a key+value pair + * * duplicated keys in kv pair + * * unknown catch reporter option + * * empty key/value in an custom kv pair + * * ... + */ + Optional parseReporterSpec( StringRef reporterSpec ); + +} + +#endif // CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + + class IStream; + + /** + * `ReporterSpec` but with the defaults filled in. + * + * Like `ReporterSpec`, the semantics are unchecked. + */ + struct ProcessedReporterSpec { + std::string name; + std::string outputFilename; + ColourMode colourMode; + std::map customOptions; + friend bool operator==( ProcessedReporterSpec const& lhs, + ProcessedReporterSpec const& rhs ); + friend bool operator!=( ProcessedReporterSpec const& lhs, + ProcessedReporterSpec const& rhs ) { + return !( lhs == rhs ); + } + }; + + struct ConfigData { + + bool listTests = false; + bool listTags = false; + bool listReporters = false; + bool listListeners = false; + + bool showSuccessfulTests = false; + bool shouldDebugBreak = false; + bool noThrow = false; + bool showHelp = false; + bool showInvisibles = false; + bool filenamesAsTags = false; + bool libIdentify = false; + bool allowZeroTests = false; + + int abortAfter = -1; + uint32_t rngSeed = generateRandomSeed(GenerateFrom::Default); + + unsigned int shardCount = 1; + unsigned int shardIndex = 0; + + bool skipBenchmarks = false; + bool benchmarkNoAnalysis = false; + unsigned int benchmarkSamples = 100; + double benchmarkConfidenceInterval = 0.95; + unsigned int benchmarkResamples = 100'000; + std::chrono::milliseconds::rep benchmarkWarmupTime = 100; + + Verbosity verbosity = Verbosity::Normal; + WarnAbout::What warnings = WarnAbout::Nothing; + ShowDurations showDurations = ShowDurations::DefaultForReporter; + double minDuration = -1; + TestRunOrder runOrder = TestRunOrder::Declared; + ColourMode defaultColourMode = ColourMode::PlatformDefault; + WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; + + std::string defaultOutputFilename; + std::string name; + std::string processName; + std::vector reporterSpecifications; + + std::vector testsOrTags; + std::vector sectionsToRun; + }; + + + class Config : public IConfig { + public: + + Config() = default; + Config( ConfigData const& data ); + ~Config() override; // = default in the cpp file + + bool listTests() const; + bool listTags() const; + bool listReporters() const; + bool listListeners() const; + + std::vector const& getReporterSpecs() const; + std::vector const& + getProcessedReporterSpecs() const; + + std::vector const& getTestsOrTags() const override; + std::vector const& getSectionsToRun() const override; + + TestSpec const& testSpec() const override; + bool hasTestFilters() const override; + + bool showHelp() const; + + // IConfig interface + bool allowThrows() const override; + StringRef name() const override; + bool includeSuccessfulResults() const override; + bool warnAboutMissingAssertions() const override; + bool warnAboutUnmatchedTestSpecs() const override; + bool zeroTestsCountAsSuccess() const override; + ShowDurations showDurations() const override; + double minDuration() const override; + TestRunOrder runOrder() const override; + uint32_t rngSeed() const override; + unsigned int shardCount() const override; + unsigned int shardIndex() const override; + ColourMode defaultColourMode() const override; + bool shouldDebugBreak() const override; + int abortAfter() const override; + bool showInvisibles() const override; + Verbosity verbosity() const override; + bool skipBenchmarks() const override; + bool benchmarkNoAnalysis() const override; + unsigned int benchmarkSamples() const override; + double benchmarkConfidenceInterval() const override; + unsigned int benchmarkResamples() const override; + std::chrono::milliseconds benchmarkWarmupTime() const override; + + private: + // Reads Bazel env vars and applies them to the config + void readBazelEnvVars(); + + ConfigData m_data; + std::vector m_processedReporterSpecs; + TestSpec m_testSpec; + bool m_hasTestFilters = false; + }; +} // end namespace Catch + +#endif // CATCH_CONFIG_HPP_INCLUDED + + +#ifndef CATCH_GET_RANDOM_SEED_HPP_INCLUDED +#define CATCH_GET_RANDOM_SEED_HPP_INCLUDED + +#include + +namespace Catch { + //! Returns Catch2's current RNG seed. + std::uint32_t getSeed(); +} + +#endif // CATCH_GET_RANDOM_SEED_HPP_INCLUDED + + +#ifndef CATCH_MESSAGE_HPP_INCLUDED +#define CATCH_MESSAGE_HPP_INCLUDED + + + + +/** \file + * Wrapper for the CATCH_CONFIG_PREFIX_MESSAGES configuration option + * + * CATCH_CONFIG_PREFIX_ALL can be used to avoid clashes with other macros + * by prepending CATCH_. This may not be desirable if the only clashes are with + * logger macros such as INFO and WARN. In this cases + * CATCH_CONFIG_PREFIX_MESSAGES can be used to only prefix a small subset + * of relevant macros. + * + */ + +#ifndef CATCH_CONFIG_PREFIX_MESSAGES_HPP_INCLUDED +#define CATCH_CONFIG_PREFIX_MESSAGES_HPP_INCLUDED + + +#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_PREFIX_MESSAGES) + #define CATCH_CONFIG_PREFIX_MESSAGES +#endif + +#endif // CATCH_CONFIG_PREFIX_MESSAGES_HPP_INCLUDED + + +#ifndef CATCH_STREAM_END_STOP_HPP_INCLUDED +#define CATCH_STREAM_END_STOP_HPP_INCLUDED + + +namespace Catch { + + // Use this in variadic streaming macros to allow + // << +StreamEndStop + // as well as + // << stuff +StreamEndStop + struct StreamEndStop { + constexpr StringRef operator+() const { return StringRef(); } + + template + constexpr friend T const& operator+( T const& value, StreamEndStop ) { + return value; + } + }; + +} // namespace Catch + +#endif // CATCH_STREAM_END_STOP_HPP_INCLUDED + + +#ifndef CATCH_MESSAGE_INFO_HPP_INCLUDED +#define CATCH_MESSAGE_INFO_HPP_INCLUDED + + +#include + +namespace Catch { + + struct MessageInfo { + MessageInfo( StringRef _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + StringRef macroName; + std::string message; + SourceLineInfo lineInfo; + ResultWas::OfType type; + unsigned int sequence; + + bool operator == (MessageInfo const& other) const { + return sequence == other.sequence; + } + bool operator < (MessageInfo const& other) const { + return sequence < other.sequence; + } + private: + static unsigned int globalCount; + }; + +} // end namespace Catch + +#endif // CATCH_MESSAGE_INFO_HPP_INCLUDED + +#include +#include + +namespace Catch { + + struct SourceLineInfo; + class IResultCapture; + + struct MessageStream { + + template + MessageStream& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + ReusableStringStream m_stream; + }; + + struct MessageBuilder : MessageStream { + MessageBuilder( StringRef macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ): + m_info(macroName, lineInfo, type) {} + + template + MessageBuilder&& operator << ( T const& value ) && { + m_stream << value; + return CATCH_MOVE(*this); + } + + MessageInfo m_info; + }; + + class ScopedMessage { + public: + explicit ScopedMessage( MessageBuilder&& builder ); + ScopedMessage( ScopedMessage& duplicate ) = delete; + ScopedMessage( ScopedMessage&& old ) noexcept; + ~ScopedMessage(); + + MessageInfo m_info; + bool m_moved = false; + }; + + class Capturer { + std::vector m_messages; + IResultCapture& m_resultCapture; + size_t m_captured = 0; + public: + Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ); + + Capturer(Capturer const&) = delete; + Capturer& operator=(Capturer const&) = delete; + + ~Capturer(); + + void captureValue( size_t index, std::string const& value ); + + template + void captureValues( size_t index, T const& value ) { + captureValue( index, Catch::Detail::stringify( value ) ); + } + + template + void captureValues( size_t index, T const& value, Ts const&... values ) { + captureValue( index, Catch::Detail::stringify(value) ); + captureValues( index+1, values... ); + } + }; + +} // end namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \ + catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_CAPTURE( varName, macroName, ... ) \ + Catch::Capturer varName( macroName##_catch_sr, \ + CATCH_INTERNAL_LINEINFO, \ + Catch::ResultWas::Info, \ + #__VA_ARGS__##_catch_sr ); \ + varName.captureValues( 0, __VA_ARGS__ ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( macroName, log ) \ + const Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \ + Catch::getResultCapture().emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) + + +#if defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE) + + #define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) + #define CATCH_UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "CATCH_UNSCOPED_INFO", msg ) + #define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) + #define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE", __VA_ARGS__ ) + +#elif defined(CATCH_CONFIG_PREFIX_MESSAGES) && defined(CATCH_CONFIG_DISABLE) + + #define CATCH_INFO( msg ) (void)(0) + #define CATCH_UNSCOPED_INFO( msg ) (void)(0) + #define CATCH_WARN( msg ) (void)(0) + #define CATCH_CAPTURE( ... ) (void)(0) + +#elif !defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE) + + #define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) + #define UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "UNSCOPED_INFO", msg ) + #define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) + #define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE", __VA_ARGS__ ) + +#elif !defined(CATCH_CONFIG_PREFIX_MESSAGES) && defined(CATCH_CONFIG_DISABLE) + + #define INFO( msg ) (void)(0) + #define UNSCOPED_INFO( msg ) (void)(0) + #define WARN( msg ) (void)(0) + #define CAPTURE( ... ) (void)(0) + +#endif // end of user facing macro declarations + + + + +#endif // CATCH_MESSAGE_HPP_INCLUDED + + +#ifndef CATCH_SECTION_INFO_HPP_INCLUDED +#define CATCH_SECTION_INFO_HPP_INCLUDED + + + +#ifndef CATCH_TOTALS_HPP_INCLUDED +#define CATCH_TOTALS_HPP_INCLUDED + +#include + +namespace Catch { + + struct Counts { + Counts operator - ( Counts const& other ) const; + Counts& operator += ( Counts const& other ); + + std::uint64_t total() const; + bool allPassed() const; + bool allOk() const; + + std::uint64_t passed = 0; + std::uint64_t failed = 0; + std::uint64_t failedButOk = 0; + std::uint64_t skipped = 0; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const; + Totals& operator += ( Totals const& other ); + + Totals delta( Totals const& prevTotals ) const; + + Counts assertions; + Counts testCases; + }; +} + +#endif // CATCH_TOTALS_HPP_INCLUDED + +#include + +namespace Catch { + + struct SectionInfo { + // The last argument is ignored, so that people can write + // SECTION("ShortName", "Proper description that is long") and + // still use the `-c` flag comfortably. + SectionInfo( SourceLineInfo const& _lineInfo, std::string _name, + const char* const = nullptr ): + name(CATCH_MOVE(_name)), + lineInfo(_lineInfo) + {} + + std::string name; + SourceLineInfo lineInfo; + }; + + struct SectionEndInfo { + SectionInfo sectionInfo; + Counts prevAssertions; + double durationInSeconds; + }; + +} // end namespace Catch + +#endif // CATCH_SECTION_INFO_HPP_INCLUDED + + +#ifndef CATCH_SESSION_HPP_INCLUDED +#define CATCH_SESSION_HPP_INCLUDED + + + +#ifndef CATCH_COMMANDLINE_HPP_INCLUDED +#define CATCH_COMMANDLINE_HPP_INCLUDED + + + +#ifndef CATCH_CLARA_HPP_INCLUDED +#define CATCH_CLARA_HPP_INCLUDED + +#if defined( __clang__ ) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wweak-vtables" +# pragma clang diagnostic ignored "-Wshadow" +# pragma clang diagnostic ignored "-Wdeprecated" +#endif + +#if defined( __GNUC__ ) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + +#ifndef CLARA_CONFIG_OPTIONAL_TYPE +# ifdef __has_include +# if __has_include( ) && __cplusplus >= 201703L +# include +# define CLARA_CONFIG_OPTIONAL_TYPE std::optional +# endif +# endif +#endif + + +#include +#include +#include +#include +#include +#include +#include + +namespace Catch { + namespace Clara { + + class Args; + class Parser; + + // enum of result types from a parse + enum class ParseResultType { + Matched, + NoMatch, + ShortCircuitAll, + ShortCircuitSame + }; + + struct accept_many_t {}; + constexpr accept_many_t accept_many {}; + + namespace Detail { + struct fake_arg { + template + operator T(); + }; + + template + struct is_unary_function : std::false_type {}; + + template + struct is_unary_function< + F, + Catch::Detail::void_t()( fake_arg() ) ) + > + > : std::true_type {}; + + // Traits for extracting arg and return type of lambdas (for single + // argument lambdas) + template + struct UnaryLambdaTraits + : UnaryLambdaTraits {}; + + template + struct UnaryLambdaTraits { + static const bool isValid = false; + }; + + template + struct UnaryLambdaTraits { + static const bool isValid = true; + using ArgType = std::remove_const_t>; + using ReturnType = ReturnT; + }; + + class TokenStream; + + // Wraps a token coming from a token stream. These may not directly + // correspond to strings as a single string may encode an option + + // its argument if the : or = form is used + enum class TokenType { Option, Argument }; + struct Token { + TokenType type; + StringRef token; + }; + + // Abstracts iterators into args as a stream of tokens, with option + // arguments uniformly handled + class TokenStream { + using Iterator = std::vector::const_iterator; + Iterator it; + Iterator itEnd; + std::vector m_tokenBuffer; + void loadBuffer(); + + public: + explicit TokenStream( Args const& args ); + TokenStream( Iterator it, Iterator itEnd ); + + explicit operator bool() const { + return !m_tokenBuffer.empty() || it != itEnd; + } + + size_t count() const { + return m_tokenBuffer.size() + ( itEnd - it ); + } + + Token operator*() const { + assert( !m_tokenBuffer.empty() ); + return m_tokenBuffer.front(); + } + + Token const* operator->() const { + assert( !m_tokenBuffer.empty() ); + return &m_tokenBuffer.front(); + } + + TokenStream& operator++(); + }; + + //! Denotes type of a parsing result + enum class ResultType { + Ok, ///< No errors + LogicError, ///< Error in user-specified arguments for + ///< construction + RuntimeError ///< Error in parsing inputs + }; + + class ResultBase { + protected: + ResultBase( ResultType type ): m_type( type ) {} + virtual ~ResultBase(); // = default; + + + ResultBase(ResultBase const&) = default; + ResultBase& operator=(ResultBase const&) = default; + ResultBase(ResultBase&&) = default; + ResultBase& operator=(ResultBase&&) = default; + + virtual void enforceOk() const = 0; + + ResultType m_type; + }; + + template + class ResultValueBase : public ResultBase { + public: + T const& value() const& { + enforceOk(); + return m_value; + } + T&& value() && { + enforceOk(); + return CATCH_MOVE( m_value ); + } + + protected: + ResultValueBase( ResultType type ): ResultBase( type ) {} + + ResultValueBase( ResultValueBase const& other ): + ResultBase( other ) { + if ( m_type == ResultType::Ok ) + new ( &m_value ) T( other.m_value ); + } + ResultValueBase( ResultValueBase&& other ): + ResultBase( other ) { + if ( m_type == ResultType::Ok ) + new ( &m_value ) T( CATCH_MOVE(other.m_value) ); + } + + + ResultValueBase( ResultType, T const& value ): + ResultBase( ResultType::Ok ) { + new ( &m_value ) T( value ); + } + ResultValueBase( ResultType, T&& value ): + ResultBase( ResultType::Ok ) { + new ( &m_value ) T( CATCH_MOVE(value) ); + } + + ResultValueBase& operator=( ResultValueBase const& other ) { + if ( m_type == ResultType::Ok ) + m_value.~T(); + ResultBase::operator=( other ); + if ( m_type == ResultType::Ok ) + new ( &m_value ) T( other.m_value ); + return *this; + } + ResultValueBase& operator=( ResultValueBase&& other ) { + if ( m_type == ResultType::Ok ) m_value.~T(); + ResultBase::operator=( other ); + if ( m_type == ResultType::Ok ) + new ( &m_value ) T( CATCH_MOVE(other.m_value) ); + return *this; + } + + + ~ResultValueBase() override { + if ( m_type == ResultType::Ok ) + m_value.~T(); + } + + union { + T m_value; + }; + }; + + template <> class ResultValueBase : public ResultBase { + protected: + using ResultBase::ResultBase; + }; + + template + class BasicResult : public ResultValueBase { + public: + template + explicit BasicResult( BasicResult const& other ): + ResultValueBase( other.type() ), + m_errorMessage( other.errorMessage() ) { + assert( type() != ResultType::Ok ); + } + + template + static auto ok( U&& value ) -> BasicResult { + return { ResultType::Ok, CATCH_FORWARD(value) }; + } + static auto ok() -> BasicResult { return { ResultType::Ok }; } + static auto logicError( std::string&& message ) + -> BasicResult { + return { ResultType::LogicError, CATCH_MOVE(message) }; + } + static auto runtimeError( std::string&& message ) + -> BasicResult { + return { ResultType::RuntimeError, CATCH_MOVE(message) }; + } + + explicit operator bool() const { + return m_type == ResultType::Ok; + } + auto type() const -> ResultType { return m_type; } + auto errorMessage() const -> std::string const& { + return m_errorMessage; + } + + protected: + void enforceOk() const override { + + // Errors shouldn't reach this point, but if they do + // the actual error message will be in m_errorMessage + assert( m_type != ResultType::LogicError ); + assert( m_type != ResultType::RuntimeError ); + if ( m_type != ResultType::Ok ) + std::abort(); + } + + std::string + m_errorMessage; // Only populated if resultType is an error + + BasicResult( ResultType type, + std::string&& message ): + ResultValueBase( type ), m_errorMessage( CATCH_MOVE(message) ) { + assert( m_type != ResultType::Ok ); + } + + using ResultValueBase::ResultValueBase; + using ResultBase::m_type; + }; + + class ParseState { + public: + ParseState( ParseResultType type, + TokenStream remainingTokens ); + + ParseResultType type() const { return m_type; } + TokenStream const& remainingTokens() const& { + return m_remainingTokens; + } + TokenStream&& remainingTokens() && { + return CATCH_MOVE( m_remainingTokens ); + } + + private: + ParseResultType m_type; + TokenStream m_remainingTokens; + }; + + using Result = BasicResult; + using ParserResult = BasicResult; + using InternalParseResult = BasicResult; + + struct HelpColumns { + std::string left; + StringRef descriptions; + }; + + template + ParserResult convertInto( std::string const& source, T& target ) { + std::stringstream ss( source ); + ss >> target; + if ( ss.fail() ) { + return ParserResult::runtimeError( + "Unable to convert '" + source + + "' to destination type" ); + } else { + return ParserResult::ok( ParseResultType::Matched ); + } + } + ParserResult convertInto( std::string const& source, + std::string& target ); + ParserResult convertInto( std::string const& source, bool& target ); + +#ifdef CLARA_CONFIG_OPTIONAL_TYPE + template + auto convertInto( std::string const& source, + CLARA_CONFIG_OPTIONAL_TYPE& target ) + -> ParserResult { + T temp; + auto result = convertInto( source, temp ); + if ( result ) + target = CATCH_MOVE( temp ); + return result; + } +#endif // CLARA_CONFIG_OPTIONAL_TYPE + + struct BoundRef : Catch::Detail::NonCopyable { + virtual ~BoundRef() = default; + virtual bool isContainer() const; + virtual bool isFlag() const; + }; + struct BoundValueRefBase : BoundRef { + virtual auto setValue( std::string const& arg ) + -> ParserResult = 0; + }; + struct BoundFlagRefBase : BoundRef { + virtual auto setFlag( bool flag ) -> ParserResult = 0; + bool isFlag() const override; + }; + + template struct BoundValueRef : BoundValueRefBase { + T& m_ref; + + explicit BoundValueRef( T& ref ): m_ref( ref ) {} + + ParserResult setValue( std::string const& arg ) override { + return convertInto( arg, m_ref ); + } + }; + + template + struct BoundValueRef> : BoundValueRefBase { + std::vector& m_ref; + + explicit BoundValueRef( std::vector& ref ): m_ref( ref ) {} + + auto isContainer() const -> bool override { return true; } + + auto setValue( std::string const& arg ) + -> ParserResult override { + T temp; + auto result = convertInto( arg, temp ); + if ( result ) + m_ref.push_back( temp ); + return result; + } + }; + + struct BoundFlagRef : BoundFlagRefBase { + bool& m_ref; + + explicit BoundFlagRef( bool& ref ): m_ref( ref ) {} + + ParserResult setFlag( bool flag ) override; + }; + + template struct LambdaInvoker { + static_assert( + std::is_same::value, + "Lambda must return void or clara::ParserResult" ); + + template + static auto invoke( L const& lambda, ArgType const& arg ) + -> ParserResult { + return lambda( arg ); + } + }; + + template <> struct LambdaInvoker { + template + static auto invoke( L const& lambda, ArgType const& arg ) + -> ParserResult { + lambda( arg ); + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + template + auto invokeLambda( L const& lambda, std::string const& arg ) + -> ParserResult { + ArgType temp{}; + auto result = convertInto( arg, temp ); + return !result ? result + : LambdaInvoker::ReturnType>::invoke( lambda, temp ); + } + + template struct BoundLambda : BoundValueRefBase { + L m_lambda; + + static_assert( + UnaryLambdaTraits::isValid, + "Supplied lambda must take exactly one argument" ); + explicit BoundLambda( L const& lambda ): m_lambda( lambda ) {} + + auto setValue( std::string const& arg ) + -> ParserResult override { + return invokeLambda::ArgType>( + m_lambda, arg ); + } + }; + + template struct BoundManyLambda : BoundLambda { + explicit BoundManyLambda( L const& lambda ): BoundLambda( lambda ) {} + bool isContainer() const override { return true; } + }; + + template struct BoundFlagLambda : BoundFlagRefBase { + L m_lambda; + + static_assert( + UnaryLambdaTraits::isValid, + "Supplied lambda must take exactly one argument" ); + static_assert( + std::is_same::ArgType, + bool>::value, + "flags must be boolean" ); + + explicit BoundFlagLambda( L const& lambda ): + m_lambda( lambda ) {} + + auto setFlag( bool flag ) -> ParserResult override { + return LambdaInvoker::ReturnType>::invoke( m_lambda, flag ); + } + }; + + enum class Optionality { Optional, Required }; + + class ParserBase { + public: + virtual ~ParserBase() = default; + virtual auto validate() const -> Result { return Result::ok(); } + virtual auto parse( std::string const& exeName, + TokenStream tokens ) const + -> InternalParseResult = 0; + virtual size_t cardinality() const; + + InternalParseResult parse( Args const& args ) const; + }; + + template + class ComposableParserImpl : public ParserBase { + public: + template + auto operator|( T const& other ) const -> Parser; + }; + + // Common code and state for Args and Opts + template + class ParserRefImpl : public ComposableParserImpl { + protected: + Optionality m_optionality = Optionality::Optional; + std::shared_ptr m_ref; + StringRef m_hint; + StringRef m_description; + + explicit ParserRefImpl( std::shared_ptr const& ref ): + m_ref( ref ) {} + + public: + template + ParserRefImpl( accept_many_t, + LambdaT const& ref, + StringRef hint ): + m_ref( std::make_shared>( ref ) ), + m_hint( hint ) {} + + template ::value>> + ParserRefImpl( T& ref, StringRef hint ): + m_ref( std::make_shared>( ref ) ), + m_hint( hint ) {} + + template ::value>> + ParserRefImpl( LambdaT const& ref, StringRef hint ): + m_ref( std::make_shared>( ref ) ), + m_hint( hint ) {} + + DerivedT& operator()( StringRef description ) & { + m_description = description; + return static_cast( *this ); + } + DerivedT&& operator()( StringRef description ) && { + m_description = description; + return static_cast( *this ); + } + + auto optional() -> DerivedT& { + m_optionality = Optionality::Optional; + return static_cast( *this ); + } + + auto required() -> DerivedT& { + m_optionality = Optionality::Required; + return static_cast( *this ); + } + + auto isOptional() const -> bool { + return m_optionality == Optionality::Optional; + } + + auto cardinality() const -> size_t override { + if ( m_ref->isContainer() ) + return 0; + else + return 1; + } + + StringRef hint() const { return m_hint; } + }; + + } // namespace detail + + + // A parser for arguments + class Arg : public Detail::ParserRefImpl { + public: + using ParserRefImpl::ParserRefImpl; + using ParserBase::parse; + + Detail::InternalParseResult + parse(std::string const&, + Detail::TokenStream tokens) const override; + }; + + // A parser for options + class Opt : public Detail::ParserRefImpl { + protected: + std::vector m_optNames; + + public: + template + explicit Opt(LambdaT const& ref) : + ParserRefImpl( + std::make_shared>(ref)) {} + + explicit Opt(bool& ref); + + template ::value>> + Opt( LambdaT const& ref, StringRef hint ): + ParserRefImpl( ref, hint ) {} + + template + Opt( accept_many_t, LambdaT const& ref, StringRef hint ): + ParserRefImpl( accept_many, ref, hint ) {} + + template ::value>> + Opt( T& ref, StringRef hint ): + ParserRefImpl( ref, hint ) {} + + Opt& operator[]( StringRef optName ) & { + m_optNames.push_back(optName); + return *this; + } + Opt&& operator[]( StringRef optName ) && { + m_optNames.push_back( optName ); + return CATCH_MOVE(*this); + } + + Detail::HelpColumns getHelpColumns() const; + + bool isMatch(StringRef optToken) const; + + using ParserBase::parse; + + Detail::InternalParseResult + parse(std::string const&, + Detail::TokenStream tokens) const override; + + Detail::Result validate() const override; + }; + + // Specifies the name of the executable + class ExeName : public Detail::ComposableParserImpl { + std::shared_ptr m_name; + std::shared_ptr m_ref; + + public: + ExeName(); + explicit ExeName(std::string& ref); + + template + explicit ExeName(LambdaT const& lambda) : ExeName() { + m_ref = std::make_shared>(lambda); + } + + // The exe name is not parsed out of the normal tokens, but is + // handled specially + Detail::InternalParseResult + parse(std::string const&, + Detail::TokenStream tokens) const override; + + std::string const& name() const { return *m_name; } + Detail::ParserResult set(std::string const& newName); + }; + + + // A Combined parser + class Parser : Detail::ParserBase { + mutable ExeName m_exeName; + std::vector m_options; + std::vector m_args; + + public: + + auto operator|=(ExeName const& exeName) -> Parser& { + m_exeName = exeName; + return *this; + } + + auto operator|=(Arg const& arg) -> Parser& { + m_args.push_back(arg); + return *this; + } + + friend Parser& operator|=( Parser& p, Opt const& opt ) { + p.m_options.push_back( opt ); + return p; + } + friend Parser& operator|=( Parser& p, Opt&& opt ) { + p.m_options.push_back( CATCH_MOVE(opt) ); + return p; + } + + Parser& operator|=(Parser const& other); + + template + friend Parser operator|( Parser const& p, T&& rhs ) { + Parser temp( p ); + temp |= rhs; + return temp; + } + + template + friend Parser operator|( Parser&& p, T&& rhs ) { + p |= CATCH_FORWARD(rhs); + return CATCH_MOVE(p); + } + + std::vector getHelpColumns() const; + + void writeToStream(std::ostream& os) const; + + friend auto operator<<(std::ostream& os, Parser const& parser) + -> std::ostream& { + parser.writeToStream(os); + return os; + } + + Detail::Result validate() const override; + + using ParserBase::parse; + Detail::InternalParseResult + parse(std::string const& exeName, + Detail::TokenStream tokens) const override; + }; + + /** + * Wrapper over argc + argv, assumes that the inputs outlive it + */ + class Args { + friend Detail::TokenStream; + StringRef m_exeName; + std::vector m_args; + + public: + Args(int argc, char const* const* argv); + // Helper constructor for testing + Args(std::initializer_list args); + + StringRef exeName() const { return m_exeName; } + }; + + + // Convenience wrapper for option parser that specifies the help option + struct Help : Opt { + Help(bool& showHelpFlag); + }; + + // Result type for parser operation + using Detail::ParserResult; + + namespace Detail { + template + template + Parser + ComposableParserImpl::operator|(T const& other) const { + return Parser() | static_cast(*this) | other; + } + } + + } // namespace Clara +} // namespace Catch + +#if defined( __clang__ ) +# pragma clang diagnostic pop +#endif + +#if defined( __GNUC__ ) +# pragma GCC diagnostic pop +#endif + +#endif // CATCH_CLARA_HPP_INCLUDED + +namespace Catch { + + struct ConfigData; + + Clara::Parser makeCommandLineParser( ConfigData& config ); + +} // end namespace Catch + +#endif // CATCH_COMMANDLINE_HPP_INCLUDED + +namespace Catch { + + class Session : Detail::NonCopyable { + public: + + Session(); + ~Session(); + + void showHelp() const; + void libIdentify(); + + int applyCommandLine( int argc, char const * const * argv ); + #if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE) + int applyCommandLine( int argc, wchar_t const * const * argv ); + #endif + + void useConfigData( ConfigData const& configData ); + + template + int run(int argc, CharT const * const argv[]) { + if (m_startupExceptions) + return 1; + int returnCode = applyCommandLine(argc, argv); + if (returnCode == 0) + returnCode = run(); + return returnCode; + } + + int run(); + + Clara::Parser const& cli() const; + void cli( Clara::Parser const& newParser ); + ConfigData& configData(); + Config& config(); + private: + int runInternal(); + + Clara::Parser m_cli; + ConfigData m_configData; + Detail::unique_ptr m_config; + bool m_startupExceptions = false; + }; + +} // end namespace Catch + +#endif // CATCH_SESSION_HPP_INCLUDED + + +#ifndef CATCH_TAG_ALIAS_HPP_INCLUDED +#define CATCH_TAG_ALIAS_HPP_INCLUDED + + +#include + +namespace Catch { + + struct TagAlias { + TagAlias(std::string const& _tag, SourceLineInfo _lineInfo): + tag(_tag), + lineInfo(_lineInfo) + {} + + std::string tag; + SourceLineInfo lineInfo; + }; + +} // end namespace Catch + +#endif // CATCH_TAG_ALIAS_HPP_INCLUDED + + +#ifndef CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED +#define CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED + + +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#endif // CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED + + +#ifndef CATCH_TEMPLATE_TEST_MACROS_HPP_INCLUDED +#define CATCH_TEMPLATE_TEST_MACROS_HPP_INCLUDED + +// We need this suppression to leak, because it took until GCC 10 +// for the front end to handle local suppression via _Pragma properly +// inside templates (so `TEMPLATE_TEST_CASE` and co). +// **THIS IS DIFFERENT FOR STANDARD TESTS, WHERE GCC 9 IS SUFFICIENT** +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ < 10 +#pragma GCC diagnostic ignored "-Wparentheses" +#endif + + + + +#ifndef CATCH_TEST_MACROS_HPP_INCLUDED +#define CATCH_TEST_MACROS_HPP_INCLUDED + + + +#ifndef CATCH_TEST_MACRO_IMPL_HPP_INCLUDED +#define CATCH_TEST_MACRO_IMPL_HPP_INCLUDED + + + +#ifndef CATCH_ASSERTION_HANDLER_HPP_INCLUDED +#define CATCH_ASSERTION_HANDLER_HPP_INCLUDED + + + +#ifndef CATCH_DECOMPOSER_HPP_INCLUDED +#define CATCH_DECOMPOSER_HPP_INCLUDED + + + +#ifndef CATCH_COMPARE_TRAITS_HPP_INCLUDED +#define CATCH_COMPARE_TRAITS_HPP_INCLUDED + + +#include + +namespace Catch { + namespace Detail { + +#if defined( __GNUC__ ) && !defined( __clang__ ) +# pragma GCC diagnostic push + // GCC likes to complain about comparing bool with 0, in the decltype() + // that defines the comparable traits below. +# pragma GCC diagnostic ignored "-Wbool-compare" + // "ordered comparison of pointer with integer zero" same as above, + // but it does not have a separate warning flag to suppress +# pragma GCC diagnostic ignored "-Wextra" + // Did you know that comparing floats with `0` directly + // is super-duper dangerous in unevaluated context? +# pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +#if defined( __clang__ ) +# pragma clang diagnostic push + // Did you know that comparing floats with `0` directly + // is super-duper dangerous in unevaluated context? +# pragma clang diagnostic ignored "-Wfloat-equal" +#endif + +#define CATCH_DEFINE_COMPARABLE_TRAIT( id, op ) \ + template \ + struct is_##id##_comparable : std::false_type {}; \ + template \ + struct is_##id##_comparable< \ + T, \ + U, \ + void_t() op std::declval() )>> \ + : std::true_type {}; \ + template \ + struct is_##id##_0_comparable : std::false_type {}; \ + template \ + struct is_##id##_0_comparable() op 0 )>> \ + : std::true_type {}; + + // We need all 6 pre-spaceship comparison ops: <, <=, >, >=, ==, != + CATCH_DEFINE_COMPARABLE_TRAIT( lt, < ) + CATCH_DEFINE_COMPARABLE_TRAIT( le, <= ) + CATCH_DEFINE_COMPARABLE_TRAIT( gt, > ) + CATCH_DEFINE_COMPARABLE_TRAIT( ge, >= ) + CATCH_DEFINE_COMPARABLE_TRAIT( eq, == ) + CATCH_DEFINE_COMPARABLE_TRAIT( ne, != ) + +#undef CATCH_DEFINE_COMPARABLE_TRAIT + +#if defined( __GNUC__ ) && !defined( __clang__ ) +# pragma GCC diagnostic pop +#endif +#if defined( __clang__ ) +# pragma clang diagnostic pop +#endif + + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_COMPARE_TRAITS_HPP_INCLUDED + + +#ifndef CATCH_LOGICAL_TRAITS_HPP_INCLUDED +#define CATCH_LOGICAL_TRAITS_HPP_INCLUDED + +#include + +namespace Catch { +namespace Detail { + +#if defined( __cpp_lib_logical_traits ) && __cpp_lib_logical_traits >= 201510 + + using std::conjunction; + using std::disjunction; + using std::negation; + +#else + + template struct conjunction : std::true_type {}; + template struct conjunction : B1 {}; + template + struct conjunction + : std::conditional_t, B1> {}; + + template struct disjunction : std::false_type {}; + template struct disjunction : B1 {}; + template + struct disjunction + : std::conditional_t> {}; + + template + struct negation : std::integral_constant {}; + +#endif + +} // namespace Detail +} // namespace Catch + +#endif // CATCH_LOGICAL_TRAITS_HPP_INCLUDED + +#include +#include + +/** \file + * Why does decomposing look the way it does: + * + * Conceptually, decomposing is simple. We change `REQUIRE( a == b )` into + * `Decomposer{} <= a == b`, so that `Decomposer{} <= a` is evaluated first, + * and our custom operator is used for `a == b`, because `a` is transformed + * into `ExprLhs` and then into `BinaryExpr`. + * + * In practice, decomposing ends up a mess, because we have to support + * various fun things. + * + * 1) Types that are only comparable with literal 0, and they do this by + * comparing against a magic type with pointer constructor and deleted + * other constructors. Example: `REQUIRE((a <=> b) == 0)` in libstdc++ + * + * 2) Types that are only comparable with literal 0, and they do this by + * comparing against a magic type with consteval integer constructor. + * Example: `REQUIRE((a <=> b) == 0)` in current MSVC STL. + * + * 3) Types that have no linkage, and so we cannot form a reference to + * them. Example: some implementations of traits. + * + * 4) Starting with C++20, when the compiler sees `a == b`, it also uses + * `b == a` when constructing the overload set. For us this means that + * when the compiler handles `ExprLhs == b`, it also tries to resolve + * the overload set for `b == ExprLhs`. + * + * To accomodate these use cases, decomposer ended up rather complex. + * + * 1) These types are handled by adding SFINAE overloads to our comparison + * operators, checking whether `T == U` are comparable with the given + * operator, and if not, whether T (or U) are comparable with literal 0. + * If yes, the overload compares T (or U) with 0 literal inline in the + * definition. + * + * Note that for extra correctness, we check that the other type is + * either an `int` (literal 0 is captured as `int` by templates), or + * a `long` (some platforms use 0L for `NULL` and we want to support + * that for pointer comparisons). + * + * 2) For these types, `is_foo_comparable` is true, but letting + * them fall into the overload that actually does `T == int` causes + * compilation error. Handling them requires that the decomposition + * is `constexpr`, so that P2564R3 applies and the `consteval` from + * their accompanying magic type is propagated through the `constexpr` + * call stack. + * + * However this is not enough to handle these types automatically, + * because our default is to capture types by reference, to avoid + * runtime copies. While these references cannot become dangling, + * they outlive the constexpr context and thus the default capture + * path cannot be actually constexpr. + * + * The solution is to capture these types by value, by explicitly + * specializing `Catch::capture_by_value` for them. Catch2 provides + * specialization for `std::foo_ordering`s, but users can specialize + * the trait for their own types as well. + * + * 3) If a type has no linkage, we also cannot capture it by reference. + * The solution is once again to capture them by value. We handle + * the common cases by using `std::is_arithmetic` as the default + * for `Catch::capture_by_value`, but that is only a some-effort + * heuristic. But as with 2), users can specialize `capture_by_value` + * for their own types as needed. + * + * 4) To support C++20 and make the SFINAE on our decomposing operators + * work, the SFINAE has to happen in return type, rather than in + * a template type. This is due to our use of logical type traits + * (`conjunction`/`disjunction`/`negation`), that we use to workaround + * an issue in older (9-) versions of GCC. I still blame C++20 for + * this, because without the comparison order switching, the logical + * traits could still be used in template type. + * + * There are also other side concerns, e.g. supporting both `REQUIRE(a)` + * and `REQUIRE(a == b)`, or making `REQUIRE_THAT(a, IsEqual(b))` slot + * nicely into the same expression handling logic, but these are rather + * straightforward and add only a bit of complexity (e.g. common base + * class for decomposed expressions). + */ + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#pragma warning(disable:4018) // more "signed/unsigned mismatch" +#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) +#pragma warning(disable:4180) // qualifier applied to function type has no meaning +#pragma warning(disable:4800) // Forcing result to true or false +#endif + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#elif defined __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#endif + +#if defined(CATCH_CPP20_OR_GREATER) && __has_include() +# include +# if defined( __cpp_lib_three_way_comparison ) && \ + __cpp_lib_three_way_comparison >= 201907L +# define CATCH_CONFIG_CPP20_COMPARE_OVERLOADS +# endif +#endif + +namespace Catch { + + namespace Detail { + // This was added in C++20, but we require only C++14 for now. + template + using RemoveCVRef_t = std::remove_cv_t>; + } + + // Note: There is nothing that stops us from extending this, + // e.g. to `std::is_scalar`, but the more encompassing + // traits are usually also more expensive. For now we + // keep this as it used to be and it can be changed later. + template + struct capture_by_value + : std::integral_constant{}> {}; + +#if defined( CATCH_CONFIG_CPP20_COMPARE_OVERLOADS ) + template <> + struct capture_by_value : std::true_type {}; + template <> + struct capture_by_value : std::true_type {}; + template <> + struct capture_by_value : std::true_type {}; +#endif + + template + struct always_false : std::false_type {}; + + class ITransientExpression { + bool m_isBinaryExpression; + bool m_result; + + public: + constexpr auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } + constexpr auto getResult() const -> bool { return m_result; } + //! This function **has** to be overriden by the derived class. + virtual void streamReconstructedExpression( std::ostream& os ) const; + + constexpr ITransientExpression( bool isBinaryExpression, bool result ) + : m_isBinaryExpression( isBinaryExpression ), + m_result( result ) + {} + + ITransientExpression() = default; + ITransientExpression(ITransientExpression const&) = default; + ITransientExpression& operator=(ITransientExpression const&) = default; + + friend std::ostream& operator<<(std::ostream& out, ITransientExpression const& expr) { + expr.streamReconstructedExpression(out); + return out; + } + + protected: + ~ITransientExpression() = default; + }; + + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ); + + template + class BinaryExpr : public ITransientExpression { + LhsT m_lhs; + StringRef m_op; + RhsT m_rhs; + + void streamReconstructedExpression( std::ostream &os ) const override { + formatReconstructedExpression + ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) ); + } + + public: + constexpr BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs ) + : ITransientExpression{ true, comparisonResult }, + m_lhs( lhs ), + m_op( op ), + m_rhs( rhs ) + {} + + template + auto operator && ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator || ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator == ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator != ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator > ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator < ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator >= ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator <= ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + }; + + template + class UnaryExpr : public ITransientExpression { + LhsT m_lhs; + + void streamReconstructedExpression( std::ostream &os ) const override { + os << Catch::Detail::stringify( m_lhs ); + } + + public: + explicit constexpr UnaryExpr( LhsT lhs ) + : ITransientExpression{ false, static_cast(lhs) }, + m_lhs( lhs ) + {} + }; + + + template + class ExprLhs { + LhsT m_lhs; + public: + explicit constexpr ExprLhs( LhsT lhs ) : m_lhs( lhs ) {} + +#define CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( id, op ) \ + template \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT&& rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction, \ + Detail::negation>>>::value, \ + BinaryExpr> { \ + return { \ + static_cast( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction, \ + capture_by_value>::value, \ + BinaryExpr> { \ + return { \ + static_cast( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction< \ + Detail::negation>, \ + Detail::is_eq_0_comparable, \ + /* We allow long because we want `ptr op NULL` to be accepted */ \ + Detail::disjunction, \ + std::is_same>>::value, \ + BinaryExpr> { \ + if ( rhs != 0 ) { throw_test_failure_exception(); } \ + return { \ + static_cast( lhs.m_lhs op 0 ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction< \ + Detail::negation>, \ + Detail::is_eq_0_comparable, \ + /* We allow long because we want `ptr op NULL` to be accepted */ \ + Detail::disjunction, \ + std::is_same>>::value, \ + BinaryExpr> { \ + if ( lhs.m_lhs != 0 ) { throw_test_failure_exception(); } \ + return { static_cast( 0 op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } + + CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( eq, == ) + CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( ne, != ) + + #undef CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR + + +#define CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( id, op ) \ + template \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT&& rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction, \ + Detail::negation>>>::value, \ + BinaryExpr> { \ + return { \ + static_cast( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction, \ + capture_by_value>::value, \ + BinaryExpr> { \ + return { \ + static_cast( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction< \ + Detail::negation>, \ + Detail::is_##id##_0_comparable, \ + std::is_same>::value, \ + BinaryExpr> { \ + if ( rhs != 0 ) { throw_test_failure_exception(); } \ + return { \ + static_cast( lhs.m_lhs op 0 ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction< \ + Detail::negation>, \ + Detail::is_##id##_0_comparable, \ + std::is_same>::value, \ + BinaryExpr> { \ + if ( lhs.m_lhs != 0 ) { throw_test_failure_exception(); } \ + return { static_cast( 0 op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } + + CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( lt, < ) + CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( le, <= ) + CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( gt, > ) + CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( ge, >= ) + + #undef CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR + + +#define CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR( op ) \ + template \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT&& rhs ) \ + -> std::enable_if_t< \ + !capture_by_value>::value, \ + BinaryExpr> { \ + return { \ + static_cast( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t::value, \ + BinaryExpr> { \ + return { \ + static_cast( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } + + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(|) + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(&) + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(^) + + #undef CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR + + template + friend auto operator && ( ExprLhs &&, RhsT && ) -> BinaryExpr { + static_assert(always_false::value, + "operator&& is not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + friend auto operator || ( ExprLhs &&, RhsT && ) -> BinaryExpr { + static_assert(always_false::value, + "operator|| is not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + constexpr auto makeUnaryExpr() const -> UnaryExpr { + return UnaryExpr{ m_lhs }; + } + }; + + struct Decomposer { + template >::value, + int> = 0> + constexpr friend auto operator <= ( Decomposer &&, T && lhs ) -> ExprLhs { + return ExprLhs{ lhs }; + } + + template ::value, int> = 0> + constexpr friend auto operator <= ( Decomposer &&, T value ) -> ExprLhs { + return ExprLhs{ value }; + } + }; + +} // end namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + +#endif // CATCH_DECOMPOSER_HPP_INCLUDED + +#include + +namespace Catch { + + struct AssertionReaction { + bool shouldDebugBreak = false; + bool shouldThrow = false; + bool shouldSkip = false; + }; + + class AssertionHandler { + AssertionInfo m_assertionInfo; + AssertionReaction m_reaction; + bool m_completed = false; + IResultCapture& m_resultCapture; + + public: + AssertionHandler + ( StringRef macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ); + ~AssertionHandler() { + if ( !m_completed ) { + m_resultCapture.handleIncomplete( m_assertionInfo ); + } + } + + + template + void handleExpr( ExprLhs const& expr ) { + handleExpr( expr.makeUnaryExpr() ); + } + void handleExpr( ITransientExpression const& expr ); + + void handleMessage(ResultWas::OfType resultType, StringRef message); + + void handleExceptionThrownAsExpected(); + void handleUnexpectedExceptionNotThrown(); + void handleExceptionNotThrownAsExpected(); + void handleThrowingCallSkipped(); + void handleUnexpectedInflightException(); + + void complete(); + + // query + auto allowThrows() const -> bool; + }; + + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str ); + +} // namespace Catch + +#endif // CATCH_ASSERTION_HANDLER_HPP_INCLUDED + + +#ifndef CATCH_PREPROCESSOR_INTERNAL_STRINGIFY_HPP_INCLUDED +#define CATCH_PREPROCESSOR_INTERNAL_STRINGIFY_HPP_INCLUDED + + +#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) + #define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__##_catch_sr +#else + #define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION"_catch_sr +#endif + +#endif // CATCH_PREPROCESSOR_INTERNAL_STRINGIFY_HPP_INCLUDED + +// We need this suppression to leak, because it took until GCC 10 +// for the front end to handle local suppression via _Pragma properly +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ <= 9 + #pragma GCC diagnostic ignored "-Wparentheses" +#endif + +#if !defined(CATCH_CONFIG_DISABLE) + +#if defined(CATCH_CONFIG_FAST_COMPILE) || defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + +/////////////////////////////////////////////////////////////////////////////// +// Another way to speed-up compilation is to omit local try-catch for REQUIRE* +// macros. +#define INTERNAL_CATCH_TRY +#define INTERNAL_CATCH_CATCH( capturer ) + +#else // CATCH_CONFIG_FAST_COMPILE + +#define INTERNAL_CATCH_TRY try +#define INTERNAL_CATCH_CATCH( handler ) catch(...) { (handler).handleUnexpectedInflightException(); } + +#endif + +#define INTERNAL_CATCH_REACT( handler ) handler.complete(); + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \ + do { /* NOLINT(bugprone-infinite-loop) */ \ + /* The expression should not be evaluated, but warnings should hopefully be checked */ \ + CATCH_INTERNAL_IGNORE_BUT_WARN(__VA_ARGS__); \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + INTERNAL_CATCH_TRY { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); /* NOLINT(bugprone-chained-comparison) */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( (void)0, (false) && static_cast( !!(__VA_ARGS__) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look + // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( !Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast(__VA_ARGS__); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleExceptionNotThrownAsExpected(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast(__VA_ARGS__); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast(expr); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( exceptionType const& ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + + + +/////////////////////////////////////////////////////////////////////////////// +// Although this is matcher-based, it can be used with just a string +#define INTERNAL_CATCH_THROWS_STR_MATCHES( macroName, resultDisposition, matcher, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast(__VA_ARGS__); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher ); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +#endif // CATCH_CONFIG_DISABLE + +#endif // CATCH_TEST_MACRO_IMPL_HPP_INCLUDED + + +#ifndef CATCH_SECTION_HPP_INCLUDED +#define CATCH_SECTION_HPP_INCLUDED + + + + +/** \file + * Wrapper for the STATIC_ANALYSIS_SUPPORT configuration option + * + * Some of Catch2's macros can be defined differently to work better with + * static analysis tools, like clang-tidy or coverity. + * Currently the main use case is to show that `SECTION`s are executed + * exclusively, and not all in one run of a `TEST_CASE`. + */ + +#ifndef CATCH_CONFIG_STATIC_ANALYSIS_SUPPORT_HPP_INCLUDED +#define CATCH_CONFIG_STATIC_ANALYSIS_SUPPORT_HPP_INCLUDED + + +#if defined(__clang_analyzer__) || defined(__COVERITY__) + #define CATCH_INTERNAL_CONFIG_STATIC_ANALYSIS_SUPPORT +#endif + +#if defined( CATCH_INTERNAL_CONFIG_STATIC_ANALYSIS_SUPPORT ) && \ + !defined( CATCH_CONFIG_NO_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT ) && \ + !defined( CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT ) +# define CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT +#endif + + +#endif // CATCH_CONFIG_STATIC_ANALYSIS_SUPPORT_HPP_INCLUDED + + +#ifndef CATCH_TIMER_HPP_INCLUDED +#define CATCH_TIMER_HPP_INCLUDED + +#include + +namespace Catch { + + class Timer { + uint64_t m_nanoseconds = 0; + public: + void start(); + auto getElapsedNanoseconds() const -> uint64_t; + auto getElapsedMicroseconds() const -> uint64_t; + auto getElapsedMilliseconds() const -> unsigned int; + auto getElapsedSeconds() const -> double; + }; + +} // namespace Catch + +#endif // CATCH_TIMER_HPP_INCLUDED + +namespace Catch { + + class Section : Detail::NonCopyable { + public: + Section( SectionInfo&& info ); + Section( SourceLineInfo const& _lineInfo, + StringRef _name, + const char* const = nullptr ); + ~Section(); + + // This indicates whether the section should be executed or not + explicit operator bool() const; + + private: + SectionInfo m_info; + + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#if !defined(CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT) +# define INTERNAL_CATCH_SECTION( ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + if ( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( \ + catch_internal_Section ) = \ + Catch::Section( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +# define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + if ( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( \ + catch_internal_Section ) = \ + Catch::SectionInfo( \ + CATCH_INTERNAL_LINEINFO, \ + ( Catch::ReusableStringStream() << __VA_ARGS__ ) \ + .str() ) ) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#else + +// These section definitions imply that at most one section at one level +// will be intered (because only one section's __LINE__ can be equal to +// the dummy `catchInternalSectionHint` variable from `TEST_CASE`). + +namespace Catch { + namespace Detail { + // Intentionally without linkage, as it should only be used as a dummy + // symbol for static analysis. + // The arguments are used as a dummy for checking warnings in the passed + // expressions. + int GetNewSectionHint( StringRef, const char* const = nullptr ); + } // namespace Detail +} // namespace Catch + + +# define INTERNAL_CATCH_SECTION( ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ + if ( [[maybe_unused]] const int catchInternalPreviousSectionHint = \ + catchInternalSectionHint, \ + catchInternalSectionHint = \ + Catch::Detail::GetNewSectionHint(__VA_ARGS__); \ + catchInternalPreviousSectionHint == __LINE__ ) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +# define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ + if ( [[maybe_unused]] const int catchInternalPreviousSectionHint = \ + catchInternalSectionHint, \ + catchInternalSectionHint = Catch::Detail::GetNewSectionHint( \ + ( Catch::ReusableStringStream() << __VA_ARGS__ ).str()); \ + catchInternalPreviousSectionHint == __LINE__ ) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#endif + + +#endif // CATCH_SECTION_HPP_INCLUDED + + +#ifndef CATCH_TEST_REGISTRY_HPP_INCLUDED +#define CATCH_TEST_REGISTRY_HPP_INCLUDED + + + +#ifndef CATCH_INTERFACES_TEST_INVOKER_HPP_INCLUDED +#define CATCH_INTERFACES_TEST_INVOKER_HPP_INCLUDED + +namespace Catch { + + class ITestInvoker { + public: + virtual void invoke() const = 0; + virtual ~ITestInvoker(); // = default + }; + +} // namespace Catch + +#endif // CATCH_INTERFACES_TEST_INVOKER_HPP_INCLUDED + + +#ifndef CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED +#define CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED + +#define INTERNAL_CATCH_EXPAND1( param ) INTERNAL_CATCH_EXPAND2( param ) +#define INTERNAL_CATCH_EXPAND2( ... ) INTERNAL_CATCH_NO##__VA_ARGS__ +#define INTERNAL_CATCH_DEF( ... ) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF + +#define INTERNAL_CATCH_REMOVE_PARENS( ... ) \ + INTERNAL_CATCH_EXPAND1( INTERNAL_CATCH_DEF __VA_ARGS__ ) + +#endif // CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED + +// GCC 5 and older do not properly handle disabling unused-variable warning +// with a _Pragma. This means that we have to leak the suppression to the +// user code as well :-( +#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 5 +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif + + + +namespace Catch { + +template +class TestInvokerAsMethod : public ITestInvoker { + void (C::*m_testAsMethod)(); +public: + TestInvokerAsMethod( void (C::*testAsMethod)() ) noexcept : m_testAsMethod( testAsMethod ) {} + + void invoke() const override { + C obj; + (obj.*m_testAsMethod)(); + } +}; + +Detail::unique_ptr makeTestInvoker( void(*testAsFunction)() ); + +template +Detail::unique_ptr makeTestInvoker( void (C::*testAsMethod)() ) { + return Detail::make_unique>( testAsMethod ); +} + +struct NameAndTags { + constexpr NameAndTags( StringRef name_ = StringRef(), + StringRef tags_ = StringRef() ) noexcept: + name( name_ ), tags( tags_ ) {} + StringRef name; + StringRef tags; +}; + +struct AutoReg : Detail::NonCopyable { + AutoReg( Detail::unique_ptr invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept; +}; + +} // end namespace Catch + +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \ + static inline void TestName() + #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \ + namespace{ \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test(); \ + }; \ + } \ + void TestName::test() +#endif + + +#if !defined(CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ + static void TestName(); \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + namespace{ const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE( ... ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), __VA_ARGS__ ) + +#else // ^^ !CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT | vv CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT + + +// Dummy registrator for the dumy test case macros +namespace Catch { + namespace Detail { + struct DummyUse { + DummyUse( void ( * )( int ), Catch::NameAndTags const& ); + }; + } // namespace Detail +} // namespace Catch + +// Note that both the presence of the argument and its exact name are +// necessary for the section support. + +// We provide a shadowed variable so that a `SECTION` inside non-`TEST_CASE` +// tests can compile. The redefined `TEST_CASE` shadows this with param. +static int catchInternalSectionHint = 0; + +# define INTERNAL_CATCH_TESTCASE2( fname, ... ) \ + static void fname( int ); \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + static const Catch::Detail::DummyUse INTERNAL_CATCH_UNIQUE_NAME( \ + dummyUser )( &(fname), Catch::NameAndTags{ __VA_ARGS__ } ); \ + CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ + static void fname( [[maybe_unused]] int catchInternalSectionHint ) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +# define INTERNAL_CATCH_TESTCASE( ... ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( dummyFunction ), __VA_ARGS__ ) + + +#endif // CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + namespace{ \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test(); \ + }; \ + const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( \ + Catch::makeTestInvoker( &TestName::test ), \ + CATCH_INTERNAL_LINEINFO, \ + #ClassName##_catch_sr, \ + Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + void TestName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), ClassName, __VA_ARGS__ ) + + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + namespace { \ + const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( \ + Catch::makeTestInvoker( &QualifiedMethod ), \ + CATCH_INTERNAL_LINEINFO, \ + "&" #QualifiedMethod##_catch_sr, \ + Catch::NameAndTags{ __VA_ARGS__ } ); \ + } /* NOLINT */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ + do { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + } while(false) + + +#endif // CATCH_TEST_REGISTRY_HPP_INCLUDED + + +// All of our user-facing macros support configuration toggle, that +// forces them to be defined prefixed with CATCH_. We also like to +// support another toggle that can minimize (disable) their implementation. +// Given this, we have 4 different configuration options below + +#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) + + #define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + + #define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) + #define CATCH_REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + + #define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + #define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + #define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + #define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + + #define CATCH_CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) + #define CATCH_CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + + #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) + #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) + #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CATCH_SKIP( ... ) INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + + + #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) + #define CATCH_STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__ , #__VA_ARGS__ ); CATCH_SUCCEED( #__VA_ARGS__ ) + #define CATCH_STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); CATCH_SUCCEED( #__VA_ARGS__ ) + #define CATCH_STATIC_CHECK( ... ) static_assert( __VA_ARGS__ , #__VA_ARGS__ ); CATCH_SUCCEED( #__VA_ARGS__ ) + #define CATCH_STATIC_CHECK_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); CATCH_SUCCEED( #__VA_ARGS__ ) + #else + #define CATCH_STATIC_REQUIRE( ... ) CATCH_REQUIRE( __VA_ARGS__ ) + #define CATCH_STATIC_REQUIRE_FALSE( ... ) CATCH_REQUIRE_FALSE( __VA_ARGS__ ) + #define CATCH_STATIC_CHECK( ... ) CATCH_CHECK( __VA_ARGS__ ) + #define CATCH_STATIC_CHECK_FALSE( ... ) CATCH_CHECK_FALSE( __VA_ARGS__ ) + #endif + + + // "BDD-style" convenience wrappers + #define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) + #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) + #define CATCH_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) + #define CATCH_AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) + #define CATCH_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) + #define CATCH_AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) + #define CATCH_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) + #define CATCH_AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +#elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) // ^^ prefixed, implemented | vv prefixed, disabled + + #define CATCH_REQUIRE( ... ) (void)(0) + #define CATCH_REQUIRE_FALSE( ... ) (void)(0) + + #define CATCH_REQUIRE_THROWS( ... ) (void)(0) + #define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) + #define CATCH_REQUIRE_NOTHROW( ... ) (void)(0) + + #define CATCH_CHECK( ... ) (void)(0) + #define CATCH_CHECK_FALSE( ... ) (void)(0) + #define CATCH_CHECKED_IF( ... ) if (__VA_ARGS__) + #define CATCH_CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) + #define CATCH_CHECK_NOFAIL( ... ) (void)(0) + + #define CATCH_CHECK_THROWS( ... ) (void)(0) + #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) (void)(0) + #define CATCH_CHECK_NOTHROW( ... ) (void)(0) + + #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) + #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) + #define CATCH_METHOD_AS_TEST_CASE( method, ... ) + #define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0) + #define CATCH_SECTION( ... ) + #define CATCH_DYNAMIC_SECTION( ... ) + #define CATCH_FAIL( ... ) (void)(0) + #define CATCH_FAIL_CHECK( ... ) (void)(0) + #define CATCH_SUCCEED( ... ) (void)(0) + #define CATCH_SKIP( ... ) (void)(0) + + #define CATCH_STATIC_REQUIRE( ... ) (void)(0) + #define CATCH_STATIC_REQUIRE_FALSE( ... ) (void)(0) + #define CATCH_STATIC_CHECK( ... ) (void)(0) + #define CATCH_STATIC_CHECK_FALSE( ... ) (void)(0) + + // "BDD-style" convenience wrappers + #define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) + #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), className ) + #define CATCH_GIVEN( desc ) + #define CATCH_AND_GIVEN( desc ) + #define CATCH_WHEN( desc ) + #define CATCH_AND_WHEN( desc ) + #define CATCH_THEN( desc ) + #define CATCH_AND_THEN( desc ) + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) // ^^ prefixed, disabled | vv unprefixed, implemented + + #define REQUIRE( ... ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + + #define REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) + #define REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + + #define CHECK( ... ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + #define CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + #define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + #define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + + #define CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) + #define CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + + #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) + #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) + #define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define SKIP( ... ) INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + + + #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) + #define STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__, #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ ) + #define STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); SUCCEED( "!(" #__VA_ARGS__ ")" ) + #define STATIC_CHECK( ... ) static_assert( __VA_ARGS__, #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ ) + #define STATIC_CHECK_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); SUCCEED( "!(" #__VA_ARGS__ ")" ) + #else + #define STATIC_REQUIRE( ... ) REQUIRE( __VA_ARGS__ ) + #define STATIC_REQUIRE_FALSE( ... ) REQUIRE_FALSE( __VA_ARGS__ ) + #define STATIC_CHECK( ... ) CHECK( __VA_ARGS__ ) + #define STATIC_CHECK_FALSE( ... ) CHECK_FALSE( __VA_ARGS__ ) + #endif + + // "BDD-style" convenience wrappers + #define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) + #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) + #define GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) + #define AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) + #define WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) + #define AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) + #define THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) + #define AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) // ^^ unprefixed, implemented | vv unprefixed, disabled + + #define REQUIRE( ... ) (void)(0) + #define REQUIRE_FALSE( ... ) (void)(0) + + #define REQUIRE_THROWS( ... ) (void)(0) + #define REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) + #define REQUIRE_NOTHROW( ... ) (void)(0) + + #define CHECK( ... ) (void)(0) + #define CHECK_FALSE( ... ) (void)(0) + #define CHECKED_IF( ... ) if (__VA_ARGS__) + #define CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) + #define CHECK_NOFAIL( ... ) (void)(0) + + #define CHECK_THROWS( ... ) (void)(0) + #define CHECK_THROWS_AS( expr, exceptionType ) (void)(0) + #define CHECK_NOTHROW( ... ) (void)(0) + + #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), __VA_ARGS__) + #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) + #define METHOD_AS_TEST_CASE( method, ... ) + #define REGISTER_TEST_CASE( Function, ... ) (void)(0) + #define SECTION( ... ) + #define DYNAMIC_SECTION( ... ) + #define FAIL( ... ) (void)(0) + #define FAIL_CHECK( ... ) (void)(0) + #define SUCCEED( ... ) (void)(0) + #define SKIP( ... ) (void)(0) + + #define STATIC_REQUIRE( ... ) (void)(0) + #define STATIC_REQUIRE_FALSE( ... ) (void)(0) + #define STATIC_CHECK( ... ) (void)(0) + #define STATIC_CHECK_FALSE( ... ) (void)(0) + + // "BDD-style" convenience wrappers + #define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ) ) + #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), className ) + + #define GIVEN( desc ) + #define AND_GIVEN( desc ) + #define WHEN( desc ) + #define AND_WHEN( desc ) + #define THEN( desc ) + #define AND_THEN( desc ) + +#endif // ^^ unprefixed, disabled + +// end of user facing macros + +#endif // CATCH_TEST_MACROS_HPP_INCLUDED + + +#ifndef CATCH_TEMPLATE_TEST_REGISTRY_HPP_INCLUDED +#define CATCH_TEMPLATE_TEST_REGISTRY_HPP_INCLUDED + + + +#ifndef CATCH_PREPROCESSOR_HPP_INCLUDED +#define CATCH_PREPROCESSOR_HPP_INCLUDED + + +#if defined(__GNUC__) +// We need to silence "empty __VA_ARGS__ warning", and using just _Pragma does not work +#pragma GCC system_header +#endif + + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template struct TypeList {};\ + template\ + constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + template class...> struct TemplateTypeList{};\ + template class...Cs>\ + constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ + template\ + struct append;\ + template\ + struct rewrap;\ + template class, typename...>\ + struct create;\ + template class, typename>\ + struct convert;\ + \ + template \ + struct append { using type = T; };\ + template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ + struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ + template< template class L1, typename...E1, typename...Rest>\ + struct append, TypeList, Rest...> { using type = L1; };\ + \ + template< template class Container, template class List, typename...elems>\ + struct rewrap, List> { using type = TypeList>; };\ + template< template class Container, template class List, class...Elems, typename...Elements>\ + struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ + \ + template